Release #3
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: Release | |
| # Fires on a SemVer tag push (`vX.Y.Z`). Builds once, then publishes to | |
| # npm + GitHub Packages and creates a GitHub Release. Use `pnpm version` | |
| # locally to bump and tag in one go, then `git push --follow-tags`. | |
| # Manual `workflow_dispatch` is kept for re-publishing an existing tag. | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Tag to release (e.g. v0.1.0). Tag must already exist on origin." | |
| required: true | |
| permissions: | |
| contents: write # create GitHub Release | |
| packages: write # publish to GitHub Packages | |
| id-token: write # npm provenance (sigstore) | |
| jobs: | |
| # ───────────────────────────────────────────────────────────────────── | |
| # 1. Build & verify the package once. Both publish jobs reuse this artifact. | |
| # ───────────────────────────────────────────────────────────────────── | |
| build: | |
| name: Build & verify | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 12 | |
| outputs: | |
| version: ${{ steps.read.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref }} | |
| - uses: pnpm/action-setup@v5 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm run typecheck | |
| - run: pnpm run build | |
| - name: Read package version | |
| id: read | |
| run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" | |
| - name: Verify tag matches package version | |
| run: | | |
| INPUT="${{ github.event.inputs.tag }}" | |
| TAG="${INPUT:-${GITHUB_REF_NAME#refs/tags/}}" | |
| PKG="v${{ steps.read.outputs.version }}" | |
| echo "tag=$TAG pkg=$PKG" | |
| if [ "$TAG" != "$PKG" ]; then | |
| echo "::error::Tag $TAG does not match package.json version $PKG" | |
| exit 1 | |
| fi | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: dist | |
| path: | | |
| dist/ | |
| package.json | |
| README.md | |
| retention-days: 14 | |
| # ───────────────────────────────────────────────────────────────────── | |
| # 2. Publish to npm (public registry). | |
| # Requires NPM_TOKEN secret in repo → Settings → Secrets & variables. | |
| # ───────────────────────────────────────────────────────────────────── | |
| publish-npm: | |
| name: Publish · npm | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: npm | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: { name: dist } | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| registry-url: "https://registry.npmjs.org" | |
| scope: "@ossrandom" | |
| - name: Publish (with provenance) | |
| # --ignore-scripts: build was already done in the `build` job; | |
| # the prepublishOnly hook would otherwise try to invoke pnpm, | |
| # which isn't installed in this job. | |
| run: npm publish --provenance --access public --ignore-scripts | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| # ───────────────────────────────────────────────────────────────────── | |
| # 3. Publish to GitHub Packages (mirror). | |
| # Uses GITHUB_TOKEN — no secret needed. Same @ossrandom scope as npm, | |
| # only the registry URL changes. | |
| # ───────────────────────────────────────────────────────────────────── | |
| publish-gpr: | |
| name: Publish · GitHub Packages | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: { name: dist } | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| registry-url: "https://npm.pkg.github.com" | |
| scope: "@ossrandom" | |
| - name: Point publishConfig at GitHub Packages | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const p = JSON.parse(fs.readFileSync('package.json','utf8')); | |
| p.publishConfig = { access: 'public', registry: 'https://npm.pkg.github.com' }; | |
| fs.writeFileSync('package.json', JSON.stringify(p, null, 2)); | |
| " | |
| - name: Publish to GitHub Packages | |
| run: npm publish --ignore-scripts | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ───────────────────────────────────────────────────────────────────── | |
| # 4. Create the GitHub Release with auto-generated notes. | |
| # ───────────────────────────────────────────────────────────────────── | |
| github-release: | |
| name: GitHub Release | |
| needs: [build, publish-npm, publish-gpr] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/download-artifact@v8 | |
| with: { name: dist, path: artifact } | |
| - name: Pack tarball | |
| run: | | |
| cd artifact && npm pack --pack-destination ../release-assets | |
| ls -la ../release-assets | |
| - uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: ${{ github.ref_name }} | |
| generate_release_notes: true | |
| files: | | |
| release-assets/*.tgz |