test: run the conformance corpus from Tish, the third target (#12) #28
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
| # Run tests and on main push create prerelease with npm package. | |
| # When you "Set as latest release" (uncheck prerelease), the npm-release workflow publishes to npm. | |
| name: CI (test, prerelease) | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| jobs: | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run tests with coverage | |
| run: npm run test:coverage | |
| - name: Conformance corpus (JS) | |
| run: npm run test:conformance | |
| # The third target. The JS build and the Rust crate were both checked against the corpus; the | |
| # Tish source itself was not, so "one source, three targets" had a leg missing. | |
| - name: Conformance corpus (Tish) | |
| run: npm run test:conformance:tish | |
| - name: Examples | |
| run: npm run examples | |
| - name: Build | |
| run: npm run build | |
| # The Rust crate is emitted from the SAME src/index.tish as dist/deck.js, which is what stops the | |
| # JS host and tish-gba's build-time bake from drifting. Nothing verified that: a change that broke | |
| # the rust-lib emit, or that made the two targets parse differently, would have gone unnoticed | |
| # until release — or until a consumer hit it. `cargo test` here runs the same conformance corpus | |
| # the JS build is checked against, so both targets are held to one contract on every PR. | |
| rust_crate: | |
| name: Rust crate (deckfile) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: crate | |
| - name: Emit the crate | |
| run: | | |
| export PATH="$PWD/node_modules/.bin:$PATH" | |
| npm run build:rust | |
| - name: Conformance corpus (from Rust) | |
| working-directory: crate | |
| run: cargo test | |
| # A crate that cannot be packaged cannot be released; catch that on the PR, not at publish. | |
| - name: Packaging check | |
| working-directory: crate | |
| run: cargo publish --dry-run --allow-dirty | |
| release_check: | |
| name: Release check (semantic-release dry-run) | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - name: Install dependencies (incl. semantic-release) | |
| run: npm ci | |
| - name: Set repository URL | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const p = require('./package.json'); | |
| p.repository = { type: 'git', url: process.env.REPO_URL }; | |
| fs.writeFileSync('./package.json', JSON.stringify(p, null, 2)); | |
| " | |
| env: | |
| REPO_URL: ${{ format('{0}/{1}.git', github.server_url, github.repository) }} | |
| - name: Require incremental release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| npx semantic-release --dry-run --extends ./.releaserc.json 2>&1 | tee release-check.log | |
| if grep -q "There are no relevant changes, so no new version is released." release-check.log; then | |
| echo "No incremental release would be triggered. Push commits that trigger a release (feat/fix/perf/BREAKING CHANGE per conventional commits)." | |
| exit 1 | |
| fi | |
| echo "Release would be triggered - check passed." | |
| release: | |
| name: Release (prerelease branch + GitHub API) | |
| needs: [test, rust_crate, release_check] | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Set repository URL | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const p = require('./package.json'); | |
| p.repository = { type: 'git', url: process.env.REPO_URL }; | |
| fs.writeFileSync('./package.json', JSON.stringify(p, null, 2)); | |
| " | |
| env: | |
| REPO_URL: ${{ format('{0}/{1}.git', github.server_url, github.repository) }} | |
| - name: Get next version (semantic-release dry-run) | |
| id: next_version | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| npx semantic-release --dry-run --extends ./.releaserc.json 2>&1 | tee release.log | |
| VERSION=$(sed -n 's/.*Published release \([0-9.]*\).*/\1/p' release.log | head -1) | |
| if [ -z "$VERSION" ]; then | |
| echo "Could not determine next version from semantic-release output" | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Next version: $VERSION" | |
| - name: Set package version | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const p = require('./package.json'); | |
| p.version = process.env.VERSION; | |
| fs.writeFileSync('./package.json', JSON.stringify(p, null, 2)); | |
| " | |
| env: | |
| VERSION: ${{ steps.next_version.outputs.version }} | |
| - name: Create npm package tarball (@spacedevin/deck) | |
| run: | | |
| npm pack | |
| mv spacedevin-deck-*.tgz spacedevin-deck-npm-package.tgz | |
| - name: Create or update release branch and push | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| BRANCH="release/v${{ steps.next_version.outputs.version }}" | |
| git checkout -B "$BRANCH" | |
| git push origin "$BRANCH" --force | |
| - name: Generate release notes from commits | |
| id: changelog | |
| run: | | |
| LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| if [ -n "$LAST_TAG" ]; then | |
| CHANGELOG=$(git log "$LAST_TAG..HEAD" --pretty=format:"- %s (%h)" --no-merges 2>/dev/null || echo "") | |
| else | |
| CHANGELOG=$(git log -30 --pretty=format:"- %s (%h)" --no-merges 2>/dev/null || echo "") | |
| fi | |
| [ -z "$CHANGELOG" ] && CHANGELOG="- No commits to list" | |
| { | |
| echo "## Changes" | |
| echo "" | |
| echo "$CHANGELOG" | |
| echo "" | |
| } > release-body.md | |
| - name: Create or update GitHub prerelease via API | |
| id: create_release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VERSION: ${{ steps.next_version.outputs.version }} | |
| run: | | |
| TAG="v${VERSION}" | |
| BRANCH="release/v${VERSION}" | |
| BODY=$(cat release-body.md) | |
| RESP=$(curl -s -w "\n%{http_code}" -X POST \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases" \ | |
| -d "{\"tag_name\":\"$TAG\",\"target_commitish\":\"$BRANCH\",\"name\":\"$TAG\",\"body\":$(echo "$BODY" | jq -Rs .),\"prerelease\":true}") | |
| HTTP_CODE=$(echo "$RESP" | tail -n1) | |
| BODY_RESP=$(echo "$RESP" | sed '$d') | |
| if [ "$HTTP_CODE" = "201" ]; then | |
| echo "Created prerelease $TAG" | |
| RELEASE_ID=$(echo "$BODY_RESP" | jq -r .id) | |
| elif [ "$HTTP_CODE" = "422" ]; then | |
| RELEASE_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r .id) | |
| if [ "$RELEASE_ID" != "null" ] && [ -n "$RELEASE_ID" ]; then | |
| curl -s -X PATCH \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID" \ | |
| -d "{\"target_commitish\":\"$BRANCH\",\"body\":$(echo "$BODY" | jq -Rs .),\"prerelease\":true}" | |
| echo "Updated existing prerelease $TAG" | |
| else | |
| echo "Release creation failed (422) and could not find release by tag" | |
| exit 1 | |
| fi | |
| else | |
| echo "Unexpected response: $HTTP_CODE" | |
| echo "$BODY_RESP" | |
| exit 1 | |
| fi | |
| echo "release_id=$RELEASE_ID" >> $GITHUB_OUTPUT | |
| - name: Upload npm package to prerelease | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| RELEASE_ID="${{ steps.create_release.outputs.release_id }}" | |
| REPO="${{ github.repository }}" | |
| UPLOAD_URL="https://uploads.github.com/repos/$REPO/releases/$RELEASE_ID/assets" | |
| curl -s -X POST \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| -H "Content-Type: application/octet-stream" \ | |
| --data-binary @spacedevin-deck-npm-package.tgz \ | |
| "${UPLOAD_URL}?name=spacedevin-deck-npm-package.tgz&label=npm%20package%20(@spacedevin/deck)" |