mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 09:07:46 +02:00
feat(release): support otp bootstrap publish
This commit is contained in:
1 parent
318718b04a
commit
dcd7612b09
3 files changed
+68
-4
No files matched your search
@@ -203,6 +203,9 @@ 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
|
||||
```
|
||||
|
||||
The helper script:
|
||||
@@ -215,6 +218,7 @@ The helper script:
|
||||
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.
|
||||
|
||||
After that first publish succeeds:
|
||||
|
||||
|
||||
@@ -30,8 +30,10 @@ function usage() {
|
||||
function parseArgs(argv) {
|
||||
const flags = new Set();
|
||||
let selector = null;
|
||||
let otp = null;
|
||||
|
||||
for (const arg of argv) {
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--") {
|
||||
continue;
|
||||
}
|
||||
@@ -41,8 +43,18 @@ function parseArgs(argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--otp") {
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith("--")) {
|
||||
throw new Error("expected a one-time password after --otp");
|
||||
}
|
||||
otp = value;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--help" || arg === "-h") {
|
||||
return { help: true, selector: null, publish: false, skipBuild: false };
|
||||
return { help: true, selector: null, publish: false, skipBuild: false, otp: null };
|
||||
}
|
||||
|
||||
if (arg.startsWith("--")) {
|
||||
@@ -61,6 +73,7 @@ function parseArgs(argv) {
|
||||
selector,
|
||||
publish: flags.has("--publish"),
|
||||
skipBuild: flags.has("--skip-build"),
|
||||
otp,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,6 +105,10 @@ function runChecked(command, args, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatCommand(command, args) {
|
||||
return `${command} ${args.join(" ")}`;
|
||||
}
|
||||
|
||||
function ensureNpmAuth() {
|
||||
const result = runCommand("npm", ["whoami"]);
|
||||
const stdout = result.stdout ?? "";
|
||||
@@ -170,8 +187,38 @@ function printNextSteps(pkg) {
|
||||
);
|
||||
}
|
||||
|
||||
function publishPackage(pkg, otp) {
|
||||
const publishArgs = ["publish", "--access", "public"];
|
||||
if (otp) {
|
||||
publishArgs.push("--otp", otp);
|
||||
}
|
||||
|
||||
const result = runCommand("npm", publishArgs, { cwd: join(repoRoot, pkg.dir) });
|
||||
const stdout = result.stdout ?? "";
|
||||
const stderr = result.stderr ?? "";
|
||||
const output = `${stdout}\n${stderr}`.trim();
|
||||
|
||||
if (stdout) process.stdout.write(stdout);
|
||||
if (stderr) process.stderr.write(stderr);
|
||||
|
||||
if (result.status === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (/\bEOTP\b|one-time password/i.test(output)) {
|
||||
throw new Error(
|
||||
[
|
||||
"npm publish reached the publish-time 2FA check.",
|
||||
"Complete the browser auth URL printed by npm and rerun the helper, or rerun with `--otp <code>` if your npm account uses authenticator-app codes.",
|
||||
].join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`${formatCommand("npm", publishArgs)} failed with status ${result.status ?? "unknown"}`);
|
||||
}
|
||||
|
||||
function main(argv) {
|
||||
const { help, selector, publish, skipBuild } = parseArgs(argv);
|
||||
const { help, selector, publish, skipBuild, otp } = parseArgs(argv);
|
||||
|
||||
if (help) {
|
||||
usage();
|
||||
@@ -219,7 +266,7 @@ function main(argv) {
|
||||
}
|
||||
|
||||
process.stdout.write(`Publishing ${pkg.name}...\n`);
|
||||
runChecked("npm", ["publish", "--access", "public"], { cwd: join(repoRoot, pkg.dir) });
|
||||
publishPackage(pkg, otp);
|
||||
printNextSteps(pkg);
|
||||
}
|
||||
|
||||
@@ -238,5 +285,6 @@ export {
|
||||
ensureNpmAuth,
|
||||
inspectNpmPackage,
|
||||
parseArgs,
|
||||
publishPackage,
|
||||
resolveTargetPackage,
|
||||
};
|
||||
@@ -9,6 +9,17 @@ test("parseArgs recognizes publish and skip-build flags", () => {
|
||||
selector: "@paperclipai/adapter-acpx-local",
|
||||
publish: true,
|
||||
skipBuild: true,
|
||||
otp: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("parseArgs accepts an explicit otp value", () => {
|
||||
assert.deepEqual(parseArgs(["packages/adapters/acpx-local", "--publish", "--otp", "123456"]), {
|
||||
help: false,
|
||||
selector: "packages/adapters/acpx-local",
|
||||
publish: true,
|
||||
skipBuild: false,
|
||||
otp: "123456",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +29,7 @@ test("parseArgs returns help mode", () => {
|
||||
selector: null,
|
||||
publish: false,
|
||||
skipBuild: false,
|
||||
otp: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user