Implement the publishing workflow "push v* tag → automatically publish to npmjs" for npm package projects. Authentication goes through npmjs.com's Trusted Publisher(OIDC):within the workflowzero tokens, zero secrets, and provenance is generated automatically. This skill provides two publishing flows:direct flow(tag push publishes immediately, no manual gate,default) and staged flow(CI stages, maintainer approves locally with npm stage approve 2FA, choose according to repository conventions).
Execution notes (important, read first)
In the npm publishing chain, several steps are irreversible once executed (git push --tags、npm publish、npm stage approve), and troubleshooting a 401/403 often requires going back and forth changing the workflow, re-binding the Trusted Publisher, and re-tagging, which is far more expensive than spending an extra ten seconds to double-check. Therefore, every step in this skill is written in the order of "verify first, then execute"; before executing, be sure to:
- Every "completion criterion" must be verified against the actual output of the command; do not judge "it should be fine" from memory of the previous step. For example, every item in the step 1 reconnaissance should be a value filled in after just running the corresponding command and seeing the output with your own eyes, not inferred from the filename or project type.
- Take commands and flags only from the body of this skill,references/workflow-template.yml、references/workflow-template-staged.yml、references/troubleshooting.md do not rewrite or supplement them from impression (especially
npm stagesubcommands andghparameters). These two tools change quickly, and flags pieced together from memory are easily outdated or nonexistent; when unsure, firstnpm stage --help/npm publish --help/gh release create --helpverify existence, then use. - Before irreversible operations such as git push / npm publish / npm stage approve, first state "I am about to execute X, because Y" to the user, and only proceed after obtaining explicit authorization for the current round, do not automatically advance to publishing just because the previous steps went smoothly.
- When encountering an error, first go to references/troubleshooting.md and look it up by symptom; do not just change a flag based on experience and retry—the same error often has several superficially similar causes that require different handling (for example, 401 may be a token permission boundary, or it may be a workflow filename mismatch).
Steps
1. Reconnaissance
Completion criteria:Every item below has actually had its corresponding command executed and its output recorded, rather than being filled in by guessing. Check item by item:
cat package.json:name / version / repository fields / files / scripts (whether there istest, whether the root directory haspackage-lock.json)gh repo view --json visibility:repository visibility (provenance requires public)npm view <name> version:whether the package already exists on npm (determines whether the "new package first release" exception is triggered, see "Customization points" and "new package first release" in troubleshooting)ls .github/workflows/:CI status, whether a workflow with the same name already exists and would conflict- Read
package.json'sscripts.test(and the build scripts it calls): whether it depends on platform-specific features such as Windows PowerShell /System.Drawing, determines theruns-on
2. Confirm publishing method
Completion criteria:The user explicitly confirms which flow to use. Defaultdirect flow. Only choose thestaged flow(stage) when the project AGENTS.md explicitly requires "publishing to be executed personally by the maintainer/manual gate". After selection, record it in the project documentation in step 8; switching from the staged flow to the direct flow (or vice versa) requires explicit user consent and synchronization in step 8.
3. Configure Trusted Publisher (only the user can do this)
Completion criteriaThe user confirms configuration is complete on npmjs.com. List the operations for the user; do not perform them on their behalf:
- Prerequisite: the package already exists in the registry and the repository is public
- npmjs.com → package Access → Publishing access → Trusted publishers, bind the repository owner/repo + workflow file name (must match the file name generated in step 4, e.g.
publish.yml) - Verify Allowed actions according to the chosen flow(npmjs treats
npm stage publishandnpm publishas independent permissions): the direct flow requires checking Allow npm publish, otherwise CI direct publishing will get 401/403 (see troubleshooting "Trusted Publisher did not allow direct publishing"); under the staging flownpm stage publishis always allowed, no additional checkbox needed
4. Generate publish.yml
Completion criteria: the file is in place and every customization point has been checked.
Copy the corresponding template according to the flow selected in step 2 to .github/workflows/:Direct flowUse references/workflow-template.yml,Staging flowUse references/workflow-template-staged.yml, and adjust item by item according to the "customization points". The two templates are identical in structure except for the publish step and permissions.
5. Local verification
Completion criteria: all project tests are green (when the workflow includes a test step); if there are no tests, skip and note this in the delivery description.
Check whether the build script path detection is portable (os.homedir() rather than USERPROFILE, otherwise the Linux runner crashes—see references/troubleshooting.md)。
6. Dry run
Completion criteria: one tag push runs through to publish/staging, and use the actual output of gh run view to confirm success (not "it should have finished").
- determine the version number, then bump. Go through the three semver questions one by one; the level is derived from the conclusions:
- **Breaking?** A contract the caller depends on has been changed (tool/command parameters, schema,
detailsshape, or something was removed) → major; - **New capability?** Callers can invoke something they could not invoke before (new tool/command/parameter/schema/entry point) → minor;
- neither → patch. Fixing wrong signals, type alignment, and purely internal refactoring all count as patch—fixing behavior correctly is exactly the semantics of patch; SDK alignment (sync) releases default to patch.
State the "level + one-sentence reason" to the user (push authorization can be in the same round), then change
package.jsonversion。 Completion criteria: the level is consistent with the conclusions of the three questions and has been stated to the user.
- **Breaking?** A contract the caller depends on has been changed (tool/command parameters, schema,
- bump the version (
package.jsonversion) → commit. For multi-line commit messages usegit commit -F <临时文件>to commit (PowerShell double quotes will break embedded quotes/newlines in multi-line messages);ghthe--json+ jq filter in PowerShell should be wrapped in single quotes to avoid"escaping issues. - Check whether existing tags are signed(
git for-each-ref refs/tags/<上一版本tag> --format="%(contents:signature)", non-empty means signed): if existing tags are signed, directlygit tag -a vX.Y.Z -m "vX.Y.Z"(it can succeed directly when gpg-agent has cached the passphrase; if it fails or times out, handle it according to troubleshooting); if existing tags are not signed, usegit -c tag.gpgsign=false tag -a vX.Y.Z -m "vX.Y.Z"to stay consistent. - Before push, restate to the user in one sentence"About to push
mainbranch + tagvX.Y.Z, which will trigger the publish workflow", and after obtaining authorization for the current round, execute:git push origin main+git push origin refs/tags/vX.Y.Z(push is a restricted operation). gh run watchfollows this run; after the run ends, additionallygh run view --log-failed(even if it shows success, it is recommended to take a look to confirm there are no silently skipped steps).- Direct flow: confirm
npm publishSuccess (step 5 after run ends has already scanned--log-failed) thennpm view <name> versionverify the new version is visible.Staged flow: get the stage-id from the log——gh run view <id> --log | Select-String "staged with id", line format+ <name>@<version> (staged with id <uuid>); the stage-id is handed to the user, and approve/reject is performed by the user themselves (see "subsequent manual steps", requires 2FA).
7. Release Notes
Completion criteria: the GitHub Release has categorized and organized notes, not just the gh release create --generate-notes -generated Full Changelog: vX...vY one-line compare link.
Follow the write-release-notes skill to write release notes: materials, category templates, create/edit, and verification are all there; use the vX.Y.Z bumped out in step 6 directly for the version number. The only npm difference is one item:Direct flowthe workflow below has already created a placeholder Release (--generate-notes); after step 6 publishes successfully, use gh release edit to overwrite it with categorized notes;Staged flowthe workflow below does not create a Release; after the maintainer approves publishing, the agent manually gh release create. If the repository convention is not to create a Release, whether to skip is executed according to the repository convention check in step 1 of write-release-notes.
8. Consolidation
Completion criteria: the AGENTS.md release conventions and related documentation have been synchronized.
Update the release conventions section of AGENTS.md: trigger conditions, who is responsible for bump/tag/push, release gate (direct flow fully automated / staged flow approve 2FA), error rollback (direct flow npm unpublish <version>, can be withdrawn within 72 hours, after expiry use npm deprecate; staged flow npm stage reject can be withdrawn before publishing), release notes are written by the agent in step 7 (rather than relying only on CI auto-generation). When the repository has domain documentation such as GLOSSARY.md, add release flow glossary entries.
Customization points
Check item by item when generating the workflow:
- Trigger tag pattern: the correspondence between the repository convention (e.g.
v*) and the package.json version; keep the tag↔version consistency check in the template - node version:setup-node
node-version; the template defaults to Node 24 (LTS, comes with npm ≥ 11.17).Staged flowrequires npm 11.15.0+ (official requirement; the Node 22 series only comes with npm 10, which does not satisfy it);Direct flowhas no npm 11 threshold (provenance requires npm 9.5.0+) - runner: default
ubuntu-latest; when tests depend on Windows features (PowerShell/System.Drawing)windows-latest - Dependency installation: if there is
package-lock.jsonusenpm ci, otherwisenpm install - Test command: if there are tests, run them in the workflow; if the build script needs a tool path, add env in that step (e.g.
PI_ESBUILD_DIR) - Authentication: under Trusted Publishing there is zero token (since setup-node v7, dummy
NODE_AUTH_TOKENis only needed when the project insists on GAT token authenticationNODE_AUTH_TOKENenv + secret - Release notes: the direct flow template keeps the
gh release create --generate-notesfallback step; the staged flow template does not create one (the version has not truly been published before publishing), and after approve the agent manuallygh release create. The formal categorized notes are written according to step 7, usinggh release editto overwrite the placeholder content
Subsequent manual steps (staged flow)
npm stage download <stage-id> # 可选:发布前检查 tarball
npm stage approve <stage-id> # 2FA 后真正发布
npm stage reject <stage-id> # 错误暂存回滚
Related skills
- my-pi-extension-maintenance — When this release is for releasing a pi SDK sync (version alignment/mirroring semantics follow-up), the acceptance points (user confirmation is required only when behavior changes are unavoidable, README compatibility notes) are in the maintenance skill; use it to check whether the changes are complete before releasing. SDK sync/type fix releases default to patch (see step 6 for the three semver questions).