Skip to content

Commit f0b3b3b

Browse files
authored
Update npm release script to accept a token as an alternative to otp (#8269)
1 parent 5ce1b75 commit f0b3b3b

File tree

1 file changed

+36
-23
lines changed

1 file changed

+36
-23
lines changed

scripts/npmRelease.js

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,18 @@
55
* Usage:
66
* node scripts/npmRelease.js --version 12.0.1 --tag next
77
* node scripts/npmRelease.js --version 12.0.1 --tag latest --otp 123456
8+
* node scripts/npmRelease.js --version 12.0.1 --tag latest --token <token>
89
*
910
* - Runs `npm dist-tag add` for every non-private workspace (same as CI publish)
10-
* reusing the same OTP so you only get prompted once.
11+
* reusing the same OTP (when provided) so you only get prompted once.
12+
* - If no OTP is provided, npm uses the current auth flow (for example passkey).
13+
* - Token auth can be passed with `--token` or via `NODE_AUTH_TOKEN`/`NPM_TOKEN`.
1114
* - Pass `--dry-run` to see the commands without executing them.
1215
*/
1316
import process from "node:process";
14-
import readline from "node:readline/promises";
1517
import { parseArgs } from "node:util";
1618
import { npm, yarn } from "../lib_dev/process.js";
1719

18-
async function promptForOtp(existingOtp) {
19-
if (existingOtp) {
20-
return existingOtp;
21-
}
22-
const rl = readline.createInterface({
23-
input: process.stdin,
24-
output: process.stdout,
25-
});
26-
const answer = await rl.question("npm one-time password: ");
27-
rl.close();
28-
return answer.trim();
29-
}
30-
3120
async function getPublicWorkspaces() {
3221
const { stdout } = await yarn("workspaces", [
3322
"list",
@@ -41,15 +30,40 @@ async function getPublicWorkspaces() {
4130
.map(entry => entry.name);
4231
}
4332

44-
async function runDistTag(pkgName, version, tag, otp, dryRun) {
33+
function resolveAuth(values) {
34+
const otp = values.otp?.trim() || undefined;
35+
const token =
36+
values.token?.trim() ||
37+
process.env.NODE_AUTH_TOKEN?.trim() ||
38+
process.env.NPM_TOKEN?.trim() ||
39+
undefined;
40+
41+
if (otp && token) {
42+
throw new Error("Use either --otp or token auth, not both.");
43+
}
44+
45+
return { otp, token };
46+
}
47+
48+
async function runDistTag(pkgName, version, tag, otp, token, dryRun) {
4549
const spec = `${pkgName}@${version}`;
46-
const args = ["dist-tag", "add", spec, tag, "--otp", otp];
50+
const args = ["dist-tag", "add", spec, tag];
51+
if (otp) {
52+
args.push("--otp", otp);
53+
}
4754
if (dryRun) {
4855
console.log(`[dry-run] npm ${args.join(" ")}`);
4956
return;
5057
}
5158
console.log(`Tagging ${spec} as ${tag}...`);
52-
await npm("dist-tag", ["add", spec, tag, "--otp", otp], {
59+
const env = token
60+
? {
61+
...process.env,
62+
NODE_AUTH_TOKEN: token,
63+
}
64+
: process.env;
65+
await npm("dist-tag", args.slice(1), {
66+
env,
5367
stdio: "inherit",
5468
throwOnFail: true,
5569
});
@@ -64,12 +78,13 @@ async function main() {
6478
version: { type: "string", short: "v" },
6579
tag: { type: "string", short: "t" },
6680
otp: { type: "string" },
81+
token: { type: "string" },
6782
"dry-run": { type: "boolean" },
6883
},
6984
});
7085
if (!values.version || !values.tag) {
7186
console.error(
72-
"Usage: node scripts/npmRelease.js --version <version> --tag <tag> [--otp <code>] [--dry-run]",
87+
"Usage: node scripts/npmRelease.js --version <version> --tag <tag> [--otp <code> | --token <token>] [--dry-run]",
7388
);
7489
process.exitCode = 1;
7590
return;
@@ -79,16 +94,14 @@ async function main() {
7994
throw new Error("No public workspaces found.");
8095
}
8196

82-
const otp = await promptForOtp(values.otp);
83-
if (!otp) {
84-
throw new Error("OTP is required to publish dist-tags.");
85-
}
97+
const { otp, token } = resolveAuth(values);
8698
for (const workspace of workspaces) {
8799
await runDistTag(
88100
workspace,
89101
values.version,
90102
values.tag,
91103
otp,
104+
token,
92105
Boolean(values["dry-run"]),
93106
);
94107
}

0 commit comments

Comments
 (0)