Verify LuaRocks uploads after API errors #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CLI | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| jobs: | |
| framework: | |
| name: framework release gate | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up isolated Lua 5.4 for LuaRocks | |
| run: | | |
| curl -fsSL https://www.lua.org/ftp/lua-5.4.8.tar.gz | tar xz -C "$RUNNER_TEMP" | |
| make -C "$RUNNER_TEMP/lua-5.4.8/src" posix | |
| echo "TECS_LUA54=$RUNNER_TEMP/lua-5.4.8/src/lua" >> "$GITHUB_ENV" | |
| - uses: leafo/gh-actions-lua@v11 | |
| with: | |
| luaVersion: "luajit" | |
| - uses: leafo/gh-actions-luarocks@v4 | |
| with: | |
| luarocksVersion: "3.12.2" | |
| - name: Set up framework dependencies | |
| run: | | |
| lr() { "$TECS_LUA54" "$(command -v luarocks)" --lua-version=5.1 "$@"; } | |
| lr install busted | |
| lr install luacov | |
| lr --dev install tl | |
| make install-type-deps | |
| - name: Verify framework | |
| run: | | |
| eval "$(luarocks path)" | |
| make clean | |
| make compile | |
| make test | |
| unix: | |
| name: ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| steps: | |
| - name: Check out Tecs | |
| uses: actions/checkout@v7 | |
| - name: Validate release metadata | |
| if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-latest' | |
| run: | | |
| version=$(sed -n 's/^return "\([^"]*\)"/\1/p' cli/tecs_cli/version.lua) | |
| test "$GITHUB_REF_NAME" = "v$version" | |
| grep -F "## [$version] -" CHANGELOG.md | |
| - name: Set up Node (docs bundle) | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: docs/package-lock.json | |
| - name: Build tecs-cli.love | |
| run: cli/scripts/build_love.sh | |
| - name: Prove option 1 from a cold cache | |
| env: | |
| TECS_CACHE_DIR: ${{ runner.temp }}/tecs-cache | |
| TECS_CLI_LOVE: ${{ github.workspace }}/cli/dist/tecs-cli.love | |
| run: | | |
| project="$RUNNER_TEMP/hello-tecs" | |
| launcher="$GITHUB_WORKSPACE/cli/launcher/tecs" | |
| rm -rf "$project" "$TECS_CACHE_DIR" | |
| (cd "$RUNNER_TEMP" && "$launcher" --version) | |
| (cd "$RUNNER_TEMP" && "$launcher" info) | |
| (cd "$RUNNER_TEMP" && "$launcher" new "$project") | |
| test -f "$project/.github/workflows/ci.yml" | |
| # The project pre-approves its own MCP server so `tecs mcp` connects | |
| # with no interactive approval once the folder is trusted. | |
| grep -Fq '"enabledMcpjsonServers"' "$project/.claude/settings.json" | |
| grep -Fq '"tecs"' "$project/.claude/settings.json" | |
| (cd "$project" && TECS_DIR="$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE/cli/launcher/tecs" dev) | |
| (cd "$project" && env -u TECS_DIR "$GITHUB_WORKSPACE/cli/launcher/tecs" check) | |
| (cd "$project" && env -u TECS_DIR "$GITHUB_WORKSPACE/cli/launcher/tecs" build) | |
| test -f "$project/build/main.lua" | |
| test -f "$project/build/tecs2d/init.lua" | |
| test -f "$project/build/tecs2d/assets/fonts/tiny-font.fnt" | |
| test -f "$project/build/tecs2d/assets/fonts/tiny-font.png" | |
| if [ "$(uname -s)" = Darwin ]; then | |
| (cd "$project" && env -u TECS_DIR "$GITHUB_WORKSPACE/cli/launcher/tecs" integ) | |
| printf '%s\n' \ | |
| '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"ci","version":"0"}}}' \ | |
| '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ | |
| '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \ | |
| '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"check","arguments":{}}}' \ | |
| '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"start_game","arguments":{}}}' \ | |
| '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"run_lua","arguments":{"code":"return 1 + 1"}}}' \ | |
| '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"stop_game","arguments":{}}}' \ | |
| > "$RUNNER_TEMP/mcp-session.jsonl" | |
| (cd "$project" && env -u TECS_DIR "$GITHUB_WORKSPACE/cli/launcher/tecs" mcp \ | |
| < "$RUNNER_TEMP/mcp-session.jsonl" > "$RUNNER_TEMP/mcp-out.jsonl") | |
| grep -q '"name":"tecs"' "$RUNNER_TEMP/mcp-out.jsonl" | |
| grep -q '"start_game"' "$RUNNER_TEMP/mcp-out.jsonl" | |
| # The full game tool set is front-loaded from the bundled manifest at | |
| # initialize (a cmd_* tool is advertised before start_game), so the | |
| # first start_game does not swap the list and no tools/list_changed | |
| # fires -- the swap is exactly what made some clients drop the tools. | |
| grep -q '"cmd_archetypes_list"' "$RUNNER_TEMP/mcp-out.jsonl" | |
| if grep -q 'list_changed' "$RUNNER_TEMP/mcp-out.jsonl"; then | |
| echo "unexpected tools/list_changed: front-load should keep the set stable" >&2 | |
| exit 1 | |
| fi | |
| grep -q '"values":\[2\]' "$RUNNER_TEMP/mcp-out.jsonl" | |
| grep -F '\"stopped\":true' "$RUNNER_TEMP/mcp-out.jsonl" > /dev/null | |
| (cd "$project" && env -u TECS_DIR "$GITHUB_WORKSPACE/cli/launcher/tecs" dist) | |
| test -f "$project/dist/hello-tecs.love" | |
| test -f "$project/dist/macos/hello-tecs.app/Contents/Resources/hello-tecs.love" | |
| grep -q "org.tecs2d.hello-tecs" "$project/dist/macos/hello-tecs.app/Contents/Info.plist" | |
| plutil -lint "$project/dist/macos/hello-tecs.app/Contents/Info.plist" | |
| test -f "$project/dist/hello-tecs-macos.zip" | |
| unzip -p "$project/dist/hello-tecs.love" tecs_buildinfo.lua | grep -q "dev = false" | |
| grep -q "dev = true" "$project/build/tecs_buildinfo.lua" | |
| SDL_MAC_BACKGROUND_APP=1 "$project/dist/macos/hello-tecs.app/Contents/MacOS/love" \ | |
| > "$RUNNER_TEMP/dist-app.log" 2>&1 & | |
| APP_PID=$! | |
| sleep 6 | |
| kill -0 "$APP_PID" | |
| if lsof -iTCP:19999 -sTCP:LISTEN | grep -q love; then | |
| echo "MCP unexpectedly listening in a distributed build" >&2 | |
| exit 1 | |
| fi | |
| grep -q "MCP server disabled" "$RUNNER_TEMP/dist-app.log" | |
| kill "$APP_PID" | |
| else | |
| (cd "$project" && env -u TECS_DIR "$GITHUB_WORKSPACE/cli/launcher/tecs" dist windows) | |
| fi | |
| test -f "$project/dist/windows/hello-tecs/hello-tecs.exe" | |
| test -f "$project/dist/hello-tecs-windows.zip" | |
| (cd "$project" && env -u TECS_DIR "$GITHUB_WORKSPACE/cli/launcher/tecs" check --json | grep -F '"ok":true') | |
| (cd "$project" && env -u TECS_DIR "$GITHUB_WORKSPACE/cli/launcher/tecs" info --json | grep -F '"built":true') | |
| cli/launcher/tecs agent list | grep -F tecs-project | |
| test -f "$(cli/launcher/tecs agent path tecs-project)" | |
| cli/launcher/tecs completions bash | grep -F '_tecs()' | |
| cli/launcher/tecs completions zsh > /dev/null | |
| cli/launcher/tecs completions fish > /dev/null | |
| cli/launcher/tecs --version | |
| - name: Package versioned archives | |
| if: matrix.os == 'ubuntu-latest' | |
| run: cli/scripts/package_dist.sh | |
| - name: Create release checksums | |
| if: matrix.os == 'ubuntu-latest' | |
| working-directory: cli/dist | |
| run: | | |
| sha256sum tecs tecs.cmd tecs.ps1 tecs-cli.love install.sh install.ps1 \ | |
| LICENSE CHANGELOG.md tecs-cli-*.tar.gz tecs-cli-*-windows.zip > SHA256SUMS | |
| - name: Upload option 1 package | |
| if: matrix.os == 'ubuntu-latest' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: tecs-cli-option1 | |
| path: | | |
| cli/dist/tecs | |
| cli/dist/tecs.cmd | |
| cli/dist/tecs.ps1 | |
| cli/dist/tecs-cli.love | |
| cli/dist/install.sh | |
| cli/dist/install.ps1 | |
| cli/dist/LICENSE | |
| cli/dist/CHANGELOG.md | |
| cli/dist/SHA256SUMS | |
| cli/dist/tecs-cli-*.tar.gz | |
| cli/dist/tecs-cli-*-windows.zip | |
| windows: | |
| name: windows-latest | |
| runs-on: windows-latest | |
| steps: | |
| - name: Check out Tecs | |
| uses: actions/checkout@v7 | |
| - name: Set up Node (docs bundle) | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: docs/package-lock.json | |
| - name: Build tecs-cli.love | |
| shell: bash | |
| run: cli/scripts/build_love.sh | |
| - name: Prove option 1 from a cold cache | |
| shell: pwsh | |
| env: | |
| TECS_CACHE_DIR: ${{ runner.temp }}\tecs-cache | |
| TECS_CLI_LOVE: ${{ github.workspace }}\cli\dist\tecs-cli.love | |
| run: | | |
| $PSNativeCommandUseErrorActionPreference = $true | |
| $project = Join-Path $env:RUNNER_TEMP "hello-tecs" | |
| $launcher = "$env:GITHUB_WORKSPACE\cli\launcher\tecs.ps1" | |
| Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $project, $env:TECS_CACHE_DIR | |
| Push-Location $env:RUNNER_TEMP | |
| & $launcher --version | |
| & $launcher info | |
| & $launcher new $project | |
| Pop-Location | |
| Push-Location $project | |
| $env:TECS_DIR = $env:GITHUB_WORKSPACE | |
| & "$env:GITHUB_WORKSPACE\cli\launcher\tecs.ps1" dev | |
| Remove-Item Env:TECS_DIR -ErrorAction SilentlyContinue | |
| & "$env:GITHUB_WORKSPACE\cli\launcher\tecs.ps1" check | |
| & "$env:GITHUB_WORKSPACE\cli\launcher\tecs.ps1" build | |
| $checkJson = (& "$env:GITHUB_WORKSPACE\cli\launcher\tecs.ps1" check --json | Out-String) | |
| if ($checkJson -notmatch '"ok":true') { throw "check --json did not report ok" } | |
| $infoJson = (& "$env:GITHUB_WORKSPACE\cli\launcher\tecs.ps1" info --json | Out-String) | |
| if ($infoJson -notmatch '"built":true') { throw "info --json did not report a built project" } | |
| & "$env:GITHUB_WORKSPACE\cli\launcher\tecs.ps1" dist windows | |
| if (-not (Test-Path "dist\windows\hello-tecs\hello-tecs.exe")) { throw "dist did not fuse the exe" } | |
| if (-not (Test-Path "dist\hello-tecs-windows.zip")) { throw "dist did not zip the Windows build" } | |
| Pop-Location | |
| if (-not (Test-Path "$project\build\main.lua")) { throw "main.lua was not built" } | |
| if (-not (Test-Path "$project\build\tecs2d\init.lua")) { throw "tecs2d was not built" } | |
| if (-not (Test-Path "$project\build\tecs2d\assets\fonts\tiny-font.fnt")) { throw "BMFont descriptor was not built" } | |
| if (-not (Test-Path "$project\build\tecs2d\assets\fonts\tiny-font.png")) { throw "BMFont texture was not built" } | |
| $agents = (& cli/launcher/tecs.ps1 agent list | Out-String) | |
| if ($agents -notmatch 'tecs-project') { throw "agent list is missing tecs-project" } | |
| $agentPath = (& cli/launcher/tecs.ps1 agent path tecs-project | Out-String).Trim() | |
| if (-not (Test-Path $agentPath)) { throw "agent path did not write $agentPath" } | |
| $completions = (& cli/launcher/tecs.ps1 completions bash | Out-String) | |
| if ($completions -notmatch '_tecs') { throw "completions bash did not print a script" } | |
| & cli/launcher/tecs.ps1 --version | |
| release: | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| needs: [framework, unix, windows] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| actions: write | |
| steps: | |
| - name: Check out Tecs | |
| uses: actions/checkout@v7 | |
| - name: Download option 1 package | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: tecs-cli-option1 | |
| path: dist | |
| - name: Publish release assets | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| generate_release_notes: true | |
| fail_on_unmatched_files: true | |
| files: | | |
| dist/tecs | |
| dist/tecs.cmd | |
| dist/tecs.ps1 | |
| dist/tecs-cli.love | |
| dist/install.sh | |
| dist/install.ps1 | |
| dist/LICENSE | |
| dist/CHANGELOG.md | |
| dist/SHA256SUMS | |
| dist/tecs-cli-*.tar.gz | |
| dist/tecs-cli-*-windows.zip | |
| - name: Set up LuaJIT for LuaRocks publication | |
| uses: leafo/gh-actions-lua@v11 | |
| with: | |
| luaVersion: "luajit" | |
| - name: Set up LuaRocks | |
| uses: leafo/gh-actions-luarocks@v4 | |
| with: | |
| luarocksVersion: "3.12.2" | |
| - name: Publish versioned Tecs rocks | |
| env: | |
| LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} | |
| run: | | |
| tag="$GITHUB_REF_NAME" | |
| version="${tag#v}" | |
| sed -e "s/^version = .*/version = \"${version}-1\"/" \ | |
| -e "s|branch = \"main\"|tag = \"${tag}\"|" \ | |
| tecs-dev-1.rockspec > "tecs-${version}-1.rockspec" | |
| sed -e "s/^version = .*/version = \"${version}-1\"/" \ | |
| -e "s|branch = \"main\"|tag = \"${tag}\"|" \ | |
| -e "s/\"tecs == dev-1\"/\"tecs == ${version}\"/" \ | |
| tecs2d-dev-1.rockspec > "tecs2d-${version}-1.rockspec" | |
| publish_rock() { | |
| package=$1 | |
| rockspec=$2 | |
| if curl -fsSI "https://luarocks.org/${package}-${version}-1.rockspec" > /dev/null; then | |
| echo "${package} ${version}-1 is already published" | |
| else | |
| if ! luarocks upload --api-key="$LUAROCKS_API_KEY" "$rockspec"; then | |
| # LuaRocks can report an API failure after accepting the rock. | |
| # Trust the immutable public rockspec when it is now present. | |
| curl -fsSI "https://luarocks.org/${package}-${version}-1.rockspec" > /dev/null | |
| fi | |
| fi | |
| } | |
| publish_rock tecs "tecs-${version}-1.rockspec" | |
| publish_rock tecs2d "tecs2d-${version}-1.rockspec" | |
| # Releases published with GITHUB_TOKEN do not fire `release` triggers, | |
| # so start the post-publication smoke run explicitly. | |
| - name: Dispatch release smoke | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: gh workflow run "Release smoke" --repo "$GITHUB_REPOSITORY" -f tag="$GITHUB_REF_NAME" |