v1.8.0 #13
Workflow file for this run
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
| # Publish to npm when a GitHub release is promoted from prerelease to full release. | |
| # Downloads the npm package tarball from the release (no rebuild). | |
| # | |
| # AUTH: npm OIDC trusted publishing (no long-lived NPM_TOKEN). One-time setup on | |
| # npmjs.com — needed SEPARATELY for `@spacedevin/deck`, `@spacedevin/deck-synths` AND | |
| # `@spacedevin/deck-player`, since trusted publishers are per package. Settings > Trusted Publisher > GitHub Actions: | |
| # Organization or user: spacedevin | |
| # Repository: deck | |
| # Workflow filename: npm-release.yml | |
| # Environment: (leave blank) | |
| # Requires npm >= 11.5.1 (upgraded below) and the id-token: write permission. | |
| # | |
| # All three packages ship from one release at one version, published in dependency order — | |
| # language, then synths, then player — because each one's dependency is pinned to that exact version. | |
| name: NPM release | |
| on: | |
| release: | |
| types: [published, edited] | |
| # Re-dispatch to publish a release the event never reached — promoting a prerelease does not | |
| # always fire `edited`, and without this the only recovery is editing the release again and hoping. | |
| # Publishing is idempotent: a version already on npm is skipped rather than failing. | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag to publish, e.g. v1.2.6" | |
| required: true | |
| type: string | |
| jobs: | |
| publish: | |
| name: Publish to npm | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # required to update release body via API | |
| id-token: write # required for npm OIDC trusted publishing | |
| env: | |
| # No NODE_AUTH_TOKEN — auth is via OIDC. | |
| # Provenance disabled: packages publish from pre-built tarballs (built in CI, not this run). | |
| NPM_CONFIG_PROVENANCE: "false" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: refs/tags/${{ github.event.release.tag_name || inputs.tag }} | |
| fetch-depth: 0 | |
| - name: Resolve the release | |
| id: rel | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="${{ github.event.release.tag_name || inputs.tag }}" | |
| # `github.event.release.id` is empty on a workflow_dispatch, so look it up by tag — which | |
| # also works for the release event and keeps one code path. | |
| ID=$(curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id') | |
| if [ -z "$ID" ] || [ "$ID" = "null" ]; then | |
| echo "No release found for tag $TAG" | |
| exit 1 | |
| fi | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "id=$ID" >> "$GITHUB_OUTPUT" | |
| - name: Download npm package tarball from release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Look the asset up through the API, NOT `github.event.release.assets`. That payload is a | |
| # snapshot frozen when the release event fired, and ci.yml creates the release BEFORE it | |
| # finishes uploading the tarball — so this races, and a re-run replays the SAME frozen | |
| # payload and fails identically forever. (tishlang/tish#593 — it cost v3.2.1 its npm publish.) | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| DECK_URL=$(curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.rel.outputs.id }}/assets?per_page=100" \ | |
| | jq -r '.[] | select(.name == "spacedevin-deck-npm-package.tgz" and .state == "uploaded") | .url') | |
| [ -n "$DECK_URL" ] && [ "$DECK_URL" != "null" ] && break | |
| echo "tarball not uploaded yet (attempt $attempt/10) — waiting 30s" | |
| sleep 30 | |
| done | |
| if [ -z "$DECK_URL" ] || [ "$DECK_URL" = "null" ]; then | |
| echo "Release has no uploaded asset spacedevin-deck-npm-package.tgz after 5 minutes" | |
| exit 1 | |
| fi | |
| curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/octet-stream" "$DECK_URL" -o spacedevin-deck-npm-package.tgz | |
| # The synths catalog ships in lockstep from the same release. Same retry loop, same reason, | |
| # and the same tolerance: a release predating the package has no such asset, so its absence | |
| # is not fatal. | |
| for attempt in 1 2 3 4 5; do | |
| SYNTHS_URL=$(curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.rel.outputs.id }}/assets?per_page=100" \ | |
| | jq -r '.[] | select(.name == "spacedevin-deck-synths-npm-package.tgz" and .state == "uploaded") | .url') | |
| [ -n "$SYNTHS_URL" ] && [ "$SYNTHS_URL" != "null" ] && break | |
| echo "synths tarball not uploaded yet (attempt $attempt/5) — waiting 30s" | |
| sleep 30 | |
| done | |
| if [ -n "$SYNTHS_URL" ] && [ "$SYNTHS_URL" != "null" ]; then | |
| curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/octet-stream" "$SYNTHS_URL" -o spacedevin-deck-synths-npm-package.tgz | |
| else | |
| echo "No spacedevin-deck-synths-npm-package.tgz on this release — skipping the synths publish." | |
| fi | |
| # The player ships in lockstep from the same release. Same retry loop, same reason. A | |
| # release predating the player has no such asset, so its absence is not fatal — it just | |
| # means there is nothing to publish. | |
| for attempt in 1 2 3 4 5; do | |
| PLAYER_URL=$(curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.rel.outputs.id }}/assets?per_page=100" \ | |
| | jq -r '.[] | select(.name == "spacedevin-deck-player-npm-package.tgz" and .state == "uploaded") | .url') | |
| [ -n "$PLAYER_URL" ] && [ "$PLAYER_URL" != "null" ] && break | |
| echo "player tarball not uploaded yet (attempt $attempt/5) — waiting 30s" | |
| sleep 30 | |
| done | |
| if [ -n "$PLAYER_URL" ] && [ "$PLAYER_URL" != "null" ]; then | |
| curl -sL -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/octet-stream" "$PLAYER_URL" -o spacedevin-deck-player-npm-package.tgz | |
| else | |
| echo "No spacedevin-deck-player-npm-package.tgz on this release — skipping the player publish." | |
| fi | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "24" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Upgrade npm for OIDC trusted publishing (needs >= 11.5.1) | |
| run: | | |
| # Pinned to npm 11.x (not 12.x) — same rationale as tishlang/lattish npm-release.yml. | |
| npm install -g "npm@^11" | |
| npm --version | |
| - name: Publish @spacedevin/deck to npm | |
| run: npm publish spacedevin-deck-npm-package.tgz --access public | |
| # After the language, for the same reason: the synths package's dependency was pinned to this | |
| # exact version at pack time, so it is uninstallable until the language is on the registry. | |
| - name: Publish @spacedevin/deck-synths to npm | |
| run: | | |
| if [ -f spacedevin-deck-synths-npm-package.tgz ]; then | |
| npm publish spacedevin-deck-synths-npm-package.tgz --access public | |
| else | |
| echo "No synths tarball — nothing to publish." | |
| fi | |
| # Second, and only after the language package is live: the player's dependency was pinned to | |
| # this exact version at pack time, so publishing it first would put an uninstallable package on | |
| # the registry for as long as the other step takes. | |
| - name: Publish @spacedevin/deck-player to npm | |
| run: | | |
| if [ -f spacedevin-deck-player-npm-package.tgz ]; then | |
| npm publish spacedevin-deck-player-npm-package.tgz --access public | |
| else | |
| echo "No player tarball — nothing to publish." | |
| fi | |
| - name: Update release description with npm URL | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="${{ steps.rel.outputs.tag }}" | |
| VERSION="${TAG#v}" | |
| NPM_URL="https://www.npmjs.com/package/@spacedevin/deck/v/${VERSION}" | |
| RELEASE_ID="${{ steps.rel.outputs.id }}" | |
| REPO="${{ github.repository }}" | |
| CURRENT_BODY=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/${REPO}/releases/${RELEASE_ID}" | jq -r '.body // ""') | |
| NEW_BODY="${CURRENT_BODY} | |
| --- | |
| Published to npm: ${NPM_URL}" | |
| 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/${REPO}/releases/${RELEASE_ID}" \ | |
| -d "$(printf '%s' "$NEW_BODY" | jq -Rs '{body: .}')" |