diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ee3419..1f8ae07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,7 +73,7 @@ jobs: - name: Create release package run: npm pack --ignore-scripts --pack-destination release - - name: Publish to npm with provenance + - name: Stage package for npm review run: | set -euo pipefail package_name="$(node -p "require('./package.json').name")" @@ -81,7 +81,7 @@ jobs: if npm view "${package_name}@${package_version}" version --silent >/dev/null 2>&1; then echo "${package_name}@${package_version} is already published; skipping duplicate publish." else - npm publish --provenance --access public + npm stage publish fi - name: Create GitHub Release diff --git a/README.md b/README.md index 8cefb40..ee2b5b5 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ npm version patch git push origin main --follow-tags ``` -The release workflow accepts only `vMAJOR.MINOR.PATCH` tags and requires the tag to match `package.json`. It builds and tests the tagged source, publishes the package to the public npm registry using npm Trusted Publishing/OIDC, and attaches the same tarball to a GitHub Release. +The release workflow accepts only `vMAJOR.MINOR.PATCH` tags and requires the tag to match `package.json`. It builds and tests the tagged source, stages the package for review using npm Trusted Publishing/OIDC, and attaches the same tarball to a GitHub Release. The package becomes public only after a maintainer approves the staged package on npm. Repository administrators should protect `main` and `v*.*.*` tags, require the CI, dependency-review, and CodeQL checks, require pull requests and signed commits where applicable, configure npm Trusted Publishing for this repository/workflow, and configure the `release` environment with approval rules. These repository settings cannot be enforced by files in the repository alone. diff --git a/docs/INSTRUCTIONS.md b/docs/INSTRUCTIONS.md index 8a557c9..fdf4cce 100644 --- a/docs/INSTRUCTIONS.md +++ b/docs/INSTRUCTIONS.md @@ -102,10 +102,10 @@ Organization or user: Ohryzon Repository: apimoq-cli Workflow filename: release.yml Environment: release -Permission: allow npm publish +Permission: allow staged publishing only ``` -Use only `release.yml` as the workflow filename, not the full path. Trusted Publishing uses short-lived OIDC credentials. Do not create or store an npm publish token in GitHub Actions. +Use only `release.yml` as the workflow filename, not the full path. Trusted Publishing uses short-lived OIDC credentials. Do not create or store an npm publish token in GitHub Actions. Leave direct `npm publish` permission unchecked. After verifying the configuration, enable: @@ -159,7 +159,17 @@ Push the commit and tag together: git push origin main --follow-tags ``` -The release workflow then publishes the new version to npm using Trusted Publishing and generates provenance through GitHub Actions OIDC. +The release workflow then stages the new version on npm using Trusted Publishing and generates provenance through GitHub Actions OIDC. It does not become publicly installable until a maintainer approves the staged package. + +Review and approve the staged package: + +```bash +npm stage list apimoq-cli +npm stage view +npm stage approve +``` + +The approval step requires maintainer authentication and two-factor authentication. Review the staged package contents before approving it. ## Release requirements @@ -184,7 +194,7 @@ If any requirement fails, npm publication does not occur. - Do not force-push `main` or release tags. - Do not reuse a published npm version. - Do not publish from an unreviewed branch. -- Prefer Trusted Publishing over long-lived npm tokens. +- Prefer Trusted Publishing with staged publishing over long-lived npm tokens. - Review `npm pack --dry-run` output before the first release and whenever package contents change. ## Useful commands diff --git a/package-lock.json b/package-lock.json index 877d71d..d9e1f08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "apimoq-cli", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "apimoq-cli", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@faker-js/faker": "^10.6.0", diff --git a/package.json b/package.json index affcabb..89f8bfd 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,35 @@ { "name": "apimoq-cli", - "version": "0.1.0", + "version": "0.1.1", "description": "A lightweight, persistent local API simulator for frontend development.", "license": "MIT", - "repository": { "type": "git", "url": "git+ssh://git@github.com/Ohryzon/apimoq-cli.git" }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/Ohryzon/apimoq-cli.git" + }, "homepage": "https://github.com/Ohryzon/apimoq-cli#readme", - "bugs": { "url": "https://github.com/Ohryzon/apimoq-cli/issues" }, - "keywords": ["api", "mock", "mock-server", "frontend", "cli", "development"], - "publishConfig": { "access": "public" }, + "bugs": { + "url": "https://github.com/Ohryzon/apimoq-cli/issues" + }, + "keywords": [ + "api", + "mock", + "mock-server", + "frontend", + "cli", + "development" + ], + "publishConfig": { + "access": "public" + }, "type": "module", - "bin": { "apimoq": "./dist/cli/index.js" }, - "files": ["dist", "templates"], + "bin": { + "apimoq": "./dist/cli/index.js" + }, + "files": [ + "dist", + "templates" + ], "scripts": { "clean": "rimraf dist", "build": "npm run clean && tsc -p tsconfig.json", @@ -22,7 +41,9 @@ "pack:ci": "npm pack --dry-run", "prepublishOnly": "npm run build" }, - "engines": { "node": ">=20" }, + "engines": { + "node": ">=20" + }, "dependencies": { "@faker-js/faker": "^10.6.0", "commander": "^12.1.0",