Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 36 additions & 23 deletions scripts/npmRelease.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,18 @@
* Usage:
* node scripts/npmRelease.js --version 12.0.1 --tag next
* node scripts/npmRelease.js --version 12.0.1 --tag latest --otp 123456
* node scripts/npmRelease.js --version 12.0.1 --tag latest --token <token>
*
* - Runs `npm dist-tag add` for every non-private workspace (same as CI publish)
* reusing the same OTP so you only get prompted once.
* reusing the same OTP (when provided) so you only get prompted once.
* - If no OTP is provided, npm uses the current auth flow (for example passkey).
* - Token auth can be passed with `--token` or via `NODE_AUTH_TOKEN`/`NPM_TOKEN`.
* - Pass `--dry-run` to see the commands without executing them.
*/
import process from "node:process";
import readline from "node:readline/promises";
import { parseArgs } from "node:util";
import { npm, yarn } from "../lib_dev/process.js";

async function promptForOtp(existingOtp) {
if (existingOtp) {
return existingOtp;
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const answer = await rl.question("npm one-time password: ");
rl.close();
return answer.trim();
}

async function getPublicWorkspaces() {
const { stdout } = await yarn("workspaces", [
"list",
Expand All @@ -41,15 +30,40 @@ async function getPublicWorkspaces() {
.map(entry => entry.name);
}

async function runDistTag(pkgName, version, tag, otp, dryRun) {
function resolveAuth(values) {
const otp = values.otp?.trim() || undefined;
const token =
values.token?.trim() ||
process.env.NODE_AUTH_TOKEN?.trim() ||
process.env.NPM_TOKEN?.trim() ||
undefined;

if (otp && token) {
throw new Error("Use either --otp or token auth, not both.");
}

return { otp, token };
}

async function runDistTag(pkgName, version, tag, otp, token, dryRun) {
const spec = `${pkgName}@${version}`;
const args = ["dist-tag", "add", spec, tag, "--otp", otp];
const args = ["dist-tag", "add", spec, tag];
if (otp) {
args.push("--otp", otp);
}
if (dryRun) {
console.log(`[dry-run] npm ${args.join(" ")}`);
return;
}
console.log(`Tagging ${spec} as ${tag}...`);
await npm("dist-tag", ["add", spec, tag, "--otp", otp], {
const env = token
? {
...process.env,
NODE_AUTH_TOKEN: token,
}
: process.env;
await npm("dist-tag", args.slice(1), {
env,
stdio: "inherit",
throwOnFail: true,
});
Expand All @@ -64,12 +78,13 @@ async function main() {
version: { type: "string", short: "v" },
tag: { type: "string", short: "t" },
otp: { type: "string" },
token: { type: "string" },
"dry-run": { type: "boolean" },
},
});
if (!values.version || !values.tag) {
console.error(
"Usage: node scripts/npmRelease.js --version <version> --tag <tag> [--otp <code>] [--dry-run]",
"Usage: node scripts/npmRelease.js --version <version> --tag <tag> [--otp <code> | --token <token>] [--dry-run]",
);
process.exitCode = 1;
return;
Expand All @@ -79,16 +94,14 @@ async function main() {
throw new Error("No public workspaces found.");
}

const otp = await promptForOtp(values.otp);
if (!otp) {
throw new Error("OTP is required to publish dist-tags.");
}
const { otp, token } = resolveAuth(values);
for (const workspace of workspaces) {
await runDistTag(
workspace,
values.version,
values.tag,
otp,
token,
Boolean(values["dry-run"]),
);
}
Expand Down
Loading