Skip to content

Commit 6827795

Browse files
committed
chore: verify npm auth token in prepublish script
1 parent a8de281 commit 6827795

File tree

4 files changed

+97
-29
lines changed

4 files changed

+97
-29
lines changed

.ado/scripts/npmAddUser.mjs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,19 @@
11
#!/usr/bin/env node
22
// @ts-check
33

4+
import * as assert from "node:assert/strict";
45
import { exec } from "node:child_process";
56

6-
/**
7-
* @template T
8-
* @param {T} arg
9-
* @param {string} message
10-
* @returns {asserts arg is NonNullable<T>}
11-
*/
12-
function assert(arg, message) {
13-
if (!arg) {
14-
throw new Error(message);
15-
}
16-
}
17-
187
const { [2]: username, [3]: password, [4]: email, [5]: registry } = process.argv;
19-
assert(username, "Please specify username");
20-
assert(password, "Please specify password");
21-
assert(email, "Please specify email");
8+
assert.ok(username, "Please specify username");
9+
assert.ok(password, "Please specify password");
10+
assert.ok(email, "Please specify email");
2211

2312
const child = exec(`npm adduser${registry ? ` --registry ${registry}` : ""}`);
24-
assert(child.stdout, "Missing stdout on child process");
13+
assert.ok(child.stdout, "Missing stdout on child process");
2514

2615
child.stdout.on("data", d => {
27-
assert(child.stdin, "Missing stdin on child process");
16+
assert.ok(child.stdin, "Missing stdin on child process");
2817

2918
process.stdout.write(d);
3019
process.stdout.write("\n");

.ado/scripts/prepublish-check.mjs

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as util from "node:util";
66
const ADO_PUBLISH_PIPELINE = ".ado/templates/npm-publish-steps.yml";
77
const NX_CONFIG_FILE = "nx.json";
88

9+
const NPM_DEFEAULT_REGISTRY = "https://registry.npmjs.org/"
910
const NPM_TAG_NEXT = "next";
1011
const NPM_TAG_NIGHTLY = "nightly";
1112
const RNMACOS_LATEST = "react-native-macos@latest";
@@ -21,8 +22,17 @@ const RNMACOS_NEXT = "react-native-macos@next";
2122
* };
2223
* };
2324
* }} NxConfig;
24-
* @typedef {{ tag?: string; update?: boolean; verbose?: boolean; }} Options;
25-
* @typedef {{ npmTag: string; prerelease?: string; isNewTag?: boolean; }} TagInfo;
25+
* @typedef {{
26+
* "skip-auth"?: boolean;
27+
* tag?: string;
28+
* update?: boolean;
29+
* verbose?: boolean;
30+
* }} Options;
31+
* @typedef {{
32+
* npmTag: string;
33+
* prerelease?: string;
34+
* isNewTag?: boolean;
35+
* }} TagInfo;
2636
*/
2737

2838
/**
@@ -80,6 +90,38 @@ function loadNxConfig(configFile) {
8090
return JSON.parse(nx);
8191
}
8292

93+
function verifyNpmAuth(registry = NPM_DEFEAULT_REGISTRY) {
94+
const npmErrorRegex = /npm error code (\w+)/;
95+
const spawnOptions = {
96+
stdio: /** @type {const} */ ("pipe"),
97+
shell: true,
98+
windowsVerbatimArguments: true,
99+
};
100+
101+
const whoamiArgs = ["whoami", "--registry", registry];
102+
const whoami = spawnSync("npm", whoamiArgs, spawnOptions);
103+
if (whoami.status !== 0) {
104+
const error = whoami.stderr.toString();
105+
const m = error.match(npmErrorRegex);
106+
switch (m && m[1]) {
107+
case "EINVALIDNPMTOKEN":
108+
throw new Error(`Invalid auth token for npm registry: ${registry}`);
109+
case "ENEEDAUTH":
110+
throw new Error(`Missing auth token for npm registry: ${registry}`);
111+
default:
112+
throw new Error(error);
113+
}
114+
}
115+
116+
const tokenArgs = ["token", "list", "--registry", registry];
117+
const token = spawnSync("npm", tokenArgs, spawnOptions);
118+
if (token.status !== 0) {
119+
const error = token.stderr.toString();
120+
const m = error.match(npmErrorRegex);
121+
throw new Error(m ? `Auth token for '${registry}' returned error code ${m[1]}` : error);
122+
}
123+
}
124+
83125
/**
84126
* Returns a numerical value for a given version string.
85127
* @param {string} version
@@ -91,11 +133,27 @@ function versionToNumber(version) {
91133
}
92134

93135
/**
94-
* Returns the currently checked out branch. Note that this function prefers
95-
* predefined CI environment variables over local clone.
136+
* Returns the target branch name. If not targetting any branches (e.g., when
137+
* executing this script locally), `undefined` is returned.
138+
* @returns {string | undefined}
139+
*/
140+
function getTargetBranch() {
141+
// https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
142+
const adoTargetBranchName = process.env["SYSTEM_PULLREQUEST_TARGETBRANCH"];
143+
return adoTargetBranchName?.replace(/^refs\/heads\//, "");
144+
}
145+
146+
/**
147+
* Returns the current branch name. In a pull request, the target branch name is
148+
* returned.
96149
* @returns {string}
97150
*/
98151
function getCurrentBranch() {
152+
const adoTargetBranchName = getTargetBranch();
153+
if (adoTargetBranchName) {
154+
return adoTargetBranchName;
155+
}
156+
99157
// https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
100158
const adoSourceBranchName = process.env["BUILD_SOURCEBRANCHNAME"];
101159
if (adoSourceBranchName) {
@@ -199,9 +257,10 @@ function verifyPublishPipeline(file, tag) {
199257
* @param {NxConfig} config
200258
* @param {string} currentBranch
201259
* @param {TagInfo} tag
260+
* @param {Options} options
202261
* @returns {asserts config is NxConfig["release"]}
203262
*/
204-
function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNewTag }) {
263+
function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNewTag }, options) {
205264
/** @type {string[]} */
206265
const errors = [];
207266

@@ -253,8 +312,18 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
253312
throw new Error("Nx Release is not correctly configured for the current branch");
254313
}
255314

315+
if (options["skip-auth"]) {
316+
info("Skipped npm auth validation");
317+
} else {
318+
verifyNpmAuth();
319+
}
320+
256321
verifyPublishPipeline(ADO_PUBLISH_PIPELINE, tag);
257-
enablePublishingOnAzurePipelines();
322+
323+
// Don't enable publishing in PRs
324+
if (!getTargetBranch()) {
325+
enablePublishingOnAzurePipelines();
326+
}
258327
}
259328

260329
/**
@@ -273,10 +342,11 @@ function main(options) {
273342
const config = loadNxConfig(NX_CONFIG_FILE);
274343
try {
275344
if (isMainBranch(branch)) {
276-
enablePublishing(config, branch, { npmTag: NPM_TAG_NIGHTLY, prerelease: NPM_TAG_NIGHTLY });
345+
const info = { npmTag: NPM_TAG_NIGHTLY, prerelease: NPM_TAG_NIGHTLY };
346+
enablePublishing(config, branch, info, options);
277347
} else if (isStableBranch(branch)) {
278348
const tag = getTagForStableBranch(branch, options, logger);
279-
enablePublishing(config, branch, tag);
349+
enablePublishing(config, branch, tag, options);
280350
}
281351
} catch (e) {
282352
if (options.update) {
@@ -296,6 +366,10 @@ function main(options) {
296366
const { values } = util.parseArgs({
297367
args: process.argv.slice(2),
298368
options: {
369+
"skip-auth": {
370+
type: "boolean",
371+
default: false,
372+
},
299373
tag: {
300374
type: "string",
301375
default: NPM_TAG_NEXT,

.ado/templates/apple-tools-setup.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
steps:
2-
- task: NodeTool@0
2+
- task: UseNode@1
33
inputs:
4-
versionSpec: '23.x'
4+
version: '23.x'
55

66
- script: |
77
brew bundle --file .ado/Brewfile

.ado/templates/npm-publish-steps.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ steps:
1515
displayName: Install npm dependencies
1616
1717
- script: |
18-
node .ado/scripts/prepublish-check.mjs --verbose --tag ${{ parameters['publishTag'] }}
18+
node .ado/scripts/prepublish-check.mjs --verbose --skip-auth --tag ${{ parameters['publishTag'] }}
1919
displayName: Verify release config
2020
2121
- script: |
@@ -26,6 +26,12 @@ steps:
2626
2727
# Disable Nightly publishing on the main branch
2828
- ${{ if endsWith(variables['Build.SourceBranchName'], '-stable') }}:
29+
- script: |
30+
echo "//registry.npmjs.org/:_authToken=$(npmAuthToken)" > ~/.npmrc
31+
node .ado/scripts/prepublish-check.mjs --verbose --tag ${{ parameters['publishTag'] }}
32+
displayName: Set and validate npm auth
33+
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
34+
2935
- script: |
3036
git switch $(Build.SourceBranchName)
3137
yarn nx release --skip-publish --verbose
@@ -35,7 +41,6 @@ steps:
3541
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
3642
3743
- script: |
38-
echo "//registry.npmjs.org/:_authToken=$(npmAuthToken)" > ~/.npmrc
3944
yarn nx release publish --excludeTaskDependencies
4045
displayName: Publish packages
4146
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))

0 commit comments

Comments
 (0)