chore(release): require otp for bootstrap publish

This commit is contained in:
Devin Foley committed 2026-05-03 19:13:20 -07:00
1 parent dcd7612b09
commit 68a222080b
3 files changed
+19 -8

No files matched your search

+2 -5
View File
@@ -202,9 +202,6 @@ Example for `@paperclipai/adapter-acpx-local` from the repo root:
pnpm run release:bootstrap-package -- @paperclipai/adapter-acpx-local
# one-time first publish from an authenticated maintainer machine
pnpm run release:bootstrap-package -- @paperclipai/adapter-acpx-local --publish
# if npm requires an authenticator-app code for publish
pnpm run release:bootstrap-package -- @paperclipai/adapter-acpx-local --publish --otp 123456
```
@@ -213,12 +210,12 @@ The helper script:
- checks that the package does not already exist on npm
- builds the target package unless `--skip-build` is passed
- runs `npm pack --dry-run` in the package directory
- only runs the real `npm publish --access public` when `--publish` is provided
- only runs the real `npm publish --access public` when `--publish --otp <code>` is provided
For the real `--publish` step, the maintainer machine must already be authenticated to npm.
If `npm whoami` returns `401`, first run `npm logout --registry=https://registry.npmjs.org/` to clear any stale local auth, then run `npm login` or `npm adduser` locally as an npm org member, and finally rerun the helper.
That local human auth is fine for the one-time bootstrap publish; we just do not want the same auth model inside CI.
If `npm publish` returns `EOTP`, complete the browser auth URL that npm prints or rerun the helper with `--otp <code>` if your npm account uses authenticator-app 2FA.
The helper now requires `--otp <code>` up front for `--publish`, so it fails before the real publish attempt if the one-time password is missing.
After that first publish succeeds:
+7 -3
View File
@@ -17,7 +17,7 @@ function usage() {
process.stderr.write(
[
"Usage:",
" node scripts/bootstrap-npm-package.mjs <package-name-or-dir> [--publish] [--skip-build]",
" node scripts/bootstrap-npm-package.mjs <package-name-or-dir> [--publish --otp <code>] [--skip-build]",
"",
"Examples:",
" node scripts/bootstrap-npm-package.mjs @paperclipai/adapter-acpx-local",
@@ -233,6 +233,10 @@ function main(argv) {
const pkg = resolveTargetPackage(selector);
process.stdout.write(`Selected ${pkg.name} (${pkg.dir})\n`);
if (publish && !otp) {
throw new Error("`--publish` requires `--otp <code>`. Generate a fresh npm one-time password and rerun.");
}
const npmState = inspectNpmPackage(pkg.name);
if (npmState.exists) {
throw new Error(`${pkg.name} already exists on npm at version ${npmState.version}; bootstrap is only for first publish`);
@@ -257,8 +261,8 @@ function main(argv) {
process.stdout.write(
[
"",
`Dry run complete. To perform the first publish from an authenticated maintainer machine, run:`,
`node scripts/bootstrap-npm-package.mjs ${pkg.name} --publish`,
"Dry run complete. To perform the first publish from an authenticated maintainer machine, run:",
`node scripts/bootstrap-npm-package.mjs ${pkg.name} --publish --otp <code>`,
"",
].join("\n"),
);
+10
View File
@@ -23,6 +23,16 @@ test("parseArgs accepts an explicit otp value", () => {
});
});
test("parseArgs leaves otp null when omitted", () => {
assert.deepEqual(parseArgs(["packages/adapters/acpx-local", "--publish"]), {
help: false,
selector: "packages/adapters/acpx-local",
publish: true,
skipBuild: false,
otp: null,
});
});
test("parseArgs returns help mode", () => {
assert.deepEqual(parseArgs(["--help"]), {
help: true,