-
Notifications
You must be signed in to change notification settings - Fork 5
Add npm provenance publish workflow #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| name: publish | ||
|
|
||
| on: | ||
| release: | ||
| types: [published] | ||
|
|
||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
| attestations: write | ||
| artifact-metadata: write | ||
|
|
||
| jobs: | ||
| integration: | ||
| name: integration node v24 macos-15-intel | ||
| runs-on: macos-15-intel | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: 24 | ||
| cache: npm | ||
| - run: npm ci | ||
| - run: npm test | ||
| - run: npm ci --prefix packages/cli | ||
| - run: npm test --prefix packages/cli | ||
| - run: npm run test:integration | ||
|
|
||
| publish: | ||
| name: publish to npmjs | ||
| runs-on: ubuntu-latest | ||
| needs: integration | ||
|
|
||
| steps: | ||
| - name: checkout | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: setup node 24 | ||
| uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: 24 | ||
| registry-url: https://registry.npmjs.org | ||
| cache: npm | ||
|
|
||
| - name: verify publish toolchain | ||
| run: | | ||
| node --version | ||
| npm --version | ||
| node -e "const version = require('node:child_process').execFileSync('npm', ['--version'], { encoding: 'utf8' }).trim(); const [major, minor, patch] = version.split('.').map(Number); if (major < 11 || (major === 11 && (minor < 5 || (minor === 5 && patch < 1)))) throw new Error('npm 11.5.1 or newer is required for trusted publishing')" | ||
|
|
||
| - name: install appdmg dependencies | ||
| run: npm ci | ||
|
|
||
| - name: install cli dependencies | ||
| run: npm ci --prefix packages/cli | ||
|
|
||
| - name: verify cli dependency contract | ||
| run: | | ||
| node -e "const root = require('./package.json'); const cli = require('./packages/cli/package.json'); if (cli.dependencies['@appdmg/appdmg'] !== root.version) throw new Error('@appdmg/cli must depend on the exact @appdmg/appdmg package version')" | ||
|
|
||
| - name: test appdmg | ||
| run: npm test | ||
|
|
||
| - name: test cli | ||
| run: npm test --prefix packages/cli | ||
|
|
||
| - name: audit appdmg dependencies | ||
| run: npm audit --audit-level=moderate | ||
|
|
||
| - name: audit cli dependencies | ||
| run: npm audit --audit-level=moderate --prefix packages/cli | ||
|
|
||
| - name: verify appdmg runtime dependency tree | ||
| run: npm ls --omit=dev --all | ||
|
|
||
| - name: verify cli runtime dependency tree | ||
| run: npm ls --omit=dev --all --prefix packages/cli | ||
|
|
||
| - name: pack appdmg package | ||
| id: pack_appdmg | ||
| run: | | ||
| mkdir -p dist/appdmg | ||
| npm pack --json --pack-destination dist/appdmg > dist/appdmg/pack.json | ||
| tarball="$(find dist/appdmg -maxdepth 1 -name '*.tgz' -print -quit)" | ||
| test -n "$tarball" | ||
| echo "tarball=$tarball" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: pack cli package | ||
| id: pack_cli | ||
| run: | | ||
| mkdir -p dist/cli | ||
| ( | ||
| cd packages/cli | ||
| npm pack --json --pack-destination ../../dist/cli > ../../dist/cli/pack.json | ||
| ) | ||
| tarball="$(find dist/cli -maxdepth 1 -name '*.tgz' -print -quit)" | ||
| test -n "$tarball" | ||
| echo "tarball=$tarball" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: attest npm package artifacts | ||
| uses: actions/attest@v4 | ||
| with: | ||
| subject-path: dist/**/*.tgz | ||
|
|
||
| - name: upload npm package artifacts | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: npm-packages | ||
| path: dist/**/*.tgz | ||
| if-no-files-found: error | ||
|
|
||
| - name: publish appdmg package | ||
| run: npm publish "$TARBALL" --provenance --access public | ||
|
Comment on lines
+113
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The workflow publishes Useful? React with 👍 / 👎. |
||
| env: | ||
| TARBALL: ${{ steps.pack_appdmg.outputs.tarball }} | ||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
|
|
||
| - name: publish cli package | ||
| run: npm publish "$TARBALL" --provenance --access public | ||
| env: | ||
| TARBALL: ${{ steps.pack_cli.outputs.tarball }} | ||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| # npm publishing with provenance | ||
|
|
||
| All `@appdmg/*` packages are published from GitHub Actions. Local npm publish | ||
| is not part of the release process. | ||
|
|
||
| ## Required npm package settings | ||
|
|
||
| Prefer npm Trusted Publishing for every package. Configure the npm package | ||
| Trusted Publisher as GitHub Actions with these values: | ||
|
|
||
| | Package | GitHub repository | Workflow filename | Environment | | ||
| | --- | --- | --- | --- | | ||
| | `@appdmg/bplist-creator` | `appdmg/bplist-creator` | `publish.yml` | blank | | ||
| | `@appdmg/tn1150` | `appdmg/tn1150` | `publish.yml` | blank | | ||
| | `@appdmg/macos-alias` | `appdmg/macos-alias` | `publish.yml` | blank | | ||
| | `@appdmg/ds-store` | `appdmg/ds-store` | `publish.yml` | blank | | ||
| | `@appdmg/appdmg` | `appdmg/appdmg-cli` | `publish.yml` | blank | | ||
| | `@appdmg/cli` | `appdmg/appdmg-cli` | `publish.yml` | blank | | ||
|
|
||
| The repositories and packages must stay public for npm provenance to be | ||
| generated and shown by npm. | ||
|
|
||
| If npm cannot configure Trusted Publishing before a first package publish, add | ||
| a granular `NPM_TOKEN` repository secret with publish permission and 2FA bypass. | ||
| The workflows still publish from GitHub Actions with `npm publish --provenance | ||
| --access public`, so the package gets npm provenance. Remove the token path once | ||
| Trusted Publishing works. | ||
|
|
||
| ## What the workflows attest | ||
|
|
||
| Each publish workflow: | ||
|
|
||
| - runs on a GitHub-hosted runner with `id-token: write`; | ||
| - installs with Node.js 24; | ||
| - runs tests, audit, and runtime dependency checks; | ||
| - creates the exact npm package tarball with `npm pack`; | ||
| - creates a GitHub artifact attestation for that `.tgz`; | ||
| - uploads the `.tgz` as a workflow artifact; | ||
| - publishes the same `.tgz` to npm with provenance enabled. | ||
|
|
||
| The appdmg CLI repository publishes two packages from one workflow. It publishes | ||
| `@appdmg/appdmg` first, then `@appdmg/cli`, and verifies that the CLI depends on | ||
| the exact library package version. | ||
|
|
||
| ## Release order | ||
|
|
||
| Publish packages bottom-up: | ||
|
|
||
| 1. `@appdmg/bplist-creator` | ||
| 2. `@appdmg/tn1150` | ||
| 3. `@appdmg/macos-alias` | ||
| 4. `@appdmg/ds-store` | ||
| 5. `@appdmg/appdmg` and `@appdmg/cli` | ||
|
|
||
| After publishing, verify from a clean project: | ||
|
|
||
| ```sh | ||
| npm install @appdmg/cli | ||
| npm audit signatures | ||
| npx appdmg-cli --version | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
release.types: [published]will also fire for GitHub prereleases, so this workflow can runnpm publishfor prerelease tags. Becausenpm publishhere does not set a non-latestdist-tag, a prerelease run can unintentionally promote prerelease packages to normal consumers. Add a guard (for example, checkinggithub.event.release.prerelease == false) or explicit prerelease tagging logic.Useful? React with 👍 / 👎.