diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f51fbc..92d4215 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,15 +13,14 @@ jobs: publish: runs-on: ubuntu-latest environment: npm - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: - node-version: 22 - cache: npm + node-version: 24 registry-url: https://registry.npmjs.org + package-manager-cache: false + - run: npm install --global npm@latest - run: npm ci - run: npm run verify - run: node scripts/publish-workspace-if-needed.mjs beatapi-client diff --git a/CHANGELOG.md b/CHANGELOG.md index 1963467..0ee210c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project uses [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [0.1.1] - 2026-07-20 + +### Changed + +- Publish both npm packages from GitHub Actions through npm Trusted Publishing + and short-lived OIDC credentials instead of a long-lived automation token. +- Add an approval-gated `npm` deployment environment and automatic npm + provenance for future releases. +- Normalize the CLI binary path in the published package metadata. + ## [0.1.0] - 2026-07-17 ### Added @@ -20,5 +30,6 @@ and this project uses [Semantic Versioning](https://semver.org/). - Structured errors, request IDs, bounded retries, CI, package checks, and npm release automation. -[Unreleased]: https://github.com/BeatAPI/beatapi-cli/compare/v0.1.0...HEAD +[Unreleased]: https://github.com/BeatAPI/beatapi-cli/compare/v0.1.1...HEAD +[0.1.1]: https://github.com/BeatAPI/beatapi-cli/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/BeatAPI/beatapi-cli/releases/tag/v0.1.0 diff --git a/docs/releasing.md b/docs/releasing.md index c303bc8..1e4bc75 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -6,8 +6,20 @@ 2. Log in to npm with the BeatAPI publishing account. 3. Publish or reserve both package names: `beatapi-client` and `beatapi`. 4. In GitHub, create an environment named `npm`. -5. Add an environment secret named `NPM_TOKEN` with publish access to both - packages. Keep required reviewer protection enabled for production releases. +5. Add the publishing owner as a required reviewer for the `npm` environment + and allow release tags matching `v*`. +6. On npmjs.com, configure a GitHub Actions Trusted Publisher separately for + `beatapi-client` and `beatapi` with these exact values: + - organization: `BeatAPI`; + - repository: `beatapi-cli`; + - workflow filename: `release.yml`; + - environment: `npm`; + - allowed action: `npm publish`. + +The release workflow uses npm Trusted Publishing over OIDC. It intentionally +does not store or reference a long-lived `NPM_TOKEN`. GitHub grants the workflow +a short-lived identity for each approved release, and npm automatically records +provenance for public packages published from the public repository. The package names were unregistered when this repository was prepared. npm names are first-come, first-served, so reserve them before announcing the @@ -30,6 +42,11 @@ release. 10. Run `beatapi --version`, `beatapi --help`, and an authenticated `beatapi auth status` smoke test. +The workflow installs the current npm CLI on Node.js 24 because Trusted +Publishing requires npm 11.5.1 or newer and Node.js 22.14.0 or newer. Do not add +an `NPM_TOKEN` fallback: a missing OIDC trust relationship should fail closed +instead of silently using a persistent credential. + ## Rollback npm package versions are immutable. If a release is defective: diff --git a/package-lock.json b/package-lock.json index 020dd17..46a2e9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "beatapi-cli-workspace", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "beatapi-cli-workspace", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "workspaces": [ "packages/*" @@ -1696,10 +1696,10 @@ }, "packages/cli": { "name": "beatapi", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { - "beatapi-client": "0.1.0", + "beatapi-client": "0.1.1", "cross-keychain": "1.1.0" }, "bin": { @@ -1711,7 +1711,7 @@ }, "packages/client": { "name": "beatapi-client", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "engines": { "node": ">=20.19.0 <21 || >=22.12.0" diff --git a/package.json b/package.json index a874565..7973551 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "beatapi-cli-workspace", - "version": "0.1.0", + "version": "0.1.1", "private": true, "description": "Official TypeScript client and CLI for BeatAPI AI video workflows.", "type": "module", diff --git a/packages/cli/package.json b/packages/cli/package.json index 107fffa..6e3a754 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,10 +1,10 @@ { "name": "beatapi", - "version": "0.1.0", + "version": "0.1.1", "description": "Command-line interface for BeatAPI AI music video and ecommerce video workflows.", "type": "module", "bin": { - "beatapi": "./dist/bin.js" + "beatapi": "dist/bin.js" }, "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -19,7 +19,7 @@ "test": "tsx --test test/*.test.ts" }, "dependencies": { - "beatapi-client": "0.1.0", + "beatapi-client": "0.1.1", "cross-keychain": "1.1.0" }, "engines": { diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 5435aa0..7da2eef 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -18,7 +18,7 @@ import { import { promptSecret as defaultPromptSecret } from "./prompt.js"; import { persistWebhookSecret } from "./webhook-secrets.js"; -export const VERSION = "0.1.0"; +export const VERSION = "0.1.1"; const HELP = `BeatAPI CLI ${VERSION} diff --git a/packages/client/package.json b/packages/client/package.json index 327cd00..c3a63c4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "beatapi-client", - "version": "0.1.0", + "version": "0.1.1", "description": "Type-safe JavaScript and TypeScript client for the BeatAPI async video API.", "type": "module", "main": "./dist/index.js", diff --git a/scripts/package-smoke.mjs b/scripts/package-smoke.mjs index bce7bd8..83b100a 100644 --- a/scripts/package-smoke.mjs +++ b/scripts/package-smoke.mjs @@ -45,7 +45,7 @@ try { "utf8", ), ); - if (installedPackage.version !== "0.1.0") { + if (installedPackage.version !== "0.1.1") { throw new Error("Installed CLI package version did not match the release."); } diff --git a/test/release-workflow.test.mjs b/test/release-workflow.test.mjs new file mode 100644 index 0000000..c204e1f --- /dev/null +++ b/test/release-workflow.test.mjs @@ -0,0 +1,20 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; + +const workflow = await readFile( + new URL("../.github/workflows/release.yml", import.meta.url), + "utf8", +); + +test("publishes both packages through approval-gated OIDC", () => { + assert.match(workflow, /id-token:\s*write/); + assert.match(workflow, /environment:\s*npm/); + assert.match(workflow, /node-version:\s*24/); + assert.match(workflow, /npm install --global npm@latest/); + assert.match( + workflow, + /publish-workspace-if-needed\.mjs beatapi-client[\s\S]*publish-workspace-if-needed\.mjs beatapi/, + ); + assert.doesNotMatch(workflow, /NPM_TOKEN|NODE_AUTH_TOKEN/); +});