Skip to content

Commit ea0be54

Browse files
Saadnajmitido64
andauthored
ci(0.76): move "NPM Publish (Dry Run)" to Github Actions (#2536)
- Backport of #2532 - Backport of #2436 (the former change depends on it) - Adds a version plan to publish a new release --------- Co-authored-by: Tommy Nguyen <[email protected]>
1 parent 35a1495 commit ea0be54

12 files changed

+310
-94
lines changed

.ado/apple-pr.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ stages:
2727
jobs:
2828
- template: /.ado/jobs/test-javascript.yml@self
2929

30-
- template: /.ado/jobs/npm-publish-dry-run.yml@self
31-
3230
# - stage: Integration
3331
# dependsOn: []
3432
# jobs:

.ado/jobs/npm-publish-dry-run.yml

Lines changed: 0 additions & 14 deletions
This file was deleted.

.ado/jobs/npm-publish.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
jobs:
2+
- job: NPMPublish
3+
displayName: NPM Publish
4+
pool:
5+
name: cxeiss-ubuntu-20-04-large
6+
image: cxe-ubuntu-20-04-1es-pt
7+
os: linux
8+
variables:
9+
- name: BUILDSECMON_OPT_IN
10+
value: true
11+
timeoutInMinutes: 90
12+
cancelTimeoutInMinutes: 5
13+
templateContext:
14+
outputs:
15+
- output: pipelineArtifact
16+
targetPath: $(System.DefaultWorkingDirectory)
17+
artifactName: github-npm-js-publish
18+
steps:
19+
- checkout: self
20+
clean: true
21+
fetchFilter: blob:none
22+
persistCredentials: true
23+
24+
- template: /.ado/templates/configure-git.yml@self
25+
26+
- script: |
27+
PUBLISH_TAG=$(jq -r '.release.version.generatorOptions.currentVersionResolverMetadata.tag' nx.json)
28+
echo "##vso[task.setvariable variable=publishTag]$PUBLISH_TAG"
29+
echo "Using publish tag from nx.json: $PUBLISH_TAG"
30+
displayName: Read publish tag from nx.json
31+
32+
- script: |
33+
yarn install
34+
displayName: Install npm dependencies
35+
36+
- script: |
37+
node .ado/scripts/prepublish-check.mjs --verbose --skip-auth --tag $(publishTag)
38+
displayName: Verify release config
39+
40+
- script: |
41+
echo Target branch: $(System.PullRequest.TargetBranch)
42+
yarn nx release --dry-run --verbose
43+
displayName: Version and publish packages (dry run)
44+
condition: and(succeeded(), ne(variables['publish_react_native_macos'], '1'))
45+
46+
# Disable Nightly publishing on the main branch
47+
- ${{ if endsWith(variables['Build.SourceBranchName'], '-stable') }}:
48+
- script: |
49+
echo "//registry.npmjs.org/:_authToken=$(npmAuthToken)" > ~/.npmrc
50+
node .ado/scripts/prepublish-check.mjs --verbose --tag $(publishTag)
51+
displayName: Set and validate npm auth
52+
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
53+
54+
- script: |
55+
git switch $(Build.SourceBranchName)
56+
yarn nx release --skip-publish --verbose
57+
env:
58+
GITHUB_TOKEN: $(githubAuthToken)
59+
displayName: Version Packages and Github Release
60+
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
61+
62+
- script: |
63+
if [[ -f .rnm-publish ]]; then
64+
yarn nx release publish --tag ${{ parameters['publishTag'] }} --excludeTaskDependencies
65+
fi
66+
displayName: Publish packages
67+
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
68+
69+
- script: |
70+
rm -f ~/.npmrc
71+
displayName: Remove npmrc if it exists
72+
condition: always()

.ado/scripts/npmAddUser.mjs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env node
2+
// @ts-check
3+
4+
import * as assert from "node:assert/strict";
5+
import { exec } from "node:child_process";
6+
7+
const { [2]: username, [3]: password, [4]: email, [5]: registry } = process.argv;
8+
assert.ok(username, "Please specify username");
9+
assert.ok(password, "Please specify password");
10+
assert.ok(email, "Please specify email");
11+
12+
const child = exec(`npm adduser${registry ? ` --registry ${registry}` : ""}`);
13+
assert.ok(child.stdout, "Missing stdout on child process");
14+
15+
child.stdout.on("data", d => {
16+
assert.ok(child.stdin, "Missing stdin on child process");
17+
18+
process.stdout.write(d);
19+
process.stdout.write("\n");
20+
21+
const data = d.toString();
22+
if (data.match(/username/i)) {
23+
child.stdin.write(username + "\n");
24+
} else if (data.match(/password/i)) {
25+
child.stdin.write(password + "\n");
26+
} else if (data.match(/email/i)) {
27+
child.stdin.write(email + "\n");
28+
} else if (data.match(/logged in as/i)) {
29+
child.stdin.end();
30+
}
31+
});

.ado/scripts/prepublish-check.mjs

Lines changed: 94 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { spawnSync } from "node:child_process";
33
import * as fs from "node:fs";
44
import * as util from "node:util";
55

6-
const ADO_PUBLISH_PIPELINE = ".ado/templates/npm-publish-steps.yml";
76
const NX_CONFIG_FILE = "nx.json";
87

8+
const NPM_DEFEAULT_REGISTRY = "https://registry.npmjs.org/"
99
const NPM_TAG_NEXT = "next";
1010
const NPM_TAG_NIGHTLY = "nightly";
1111
const RNMACOS_LATEST = "react-native-macos@latest";
@@ -21,8 +21,18 @@ const RNMACOS_NEXT = "react-native-macos@next";
2121
* };
2222
* };
2323
* }} NxConfig;
24-
* @typedef {{ tag?: string; update?: boolean; verbose?: boolean; }} Options;
25-
* @typedef {{ npmTag: string; prerelease?: string; isNewTag?: boolean; }} TagInfo;
24+
* @typedef {{
25+
* "mock-branch"?: string;
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,17 +133,39 @@ 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.
149+
* @param {Options} options
96150
* @returns {string}
97151
*/
98-
function getCurrentBranch() {
152+
function getCurrentBranch(options) {
153+
const adoTargetBranchName = getTargetBranch();
154+
if (adoTargetBranchName) {
155+
return adoTargetBranchName;
156+
}
157+
99158
// https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
100159
const adoSourceBranchName = process.env["BUILD_SOURCEBRANCHNAME"];
101160
if (adoSourceBranchName) {
102161
return adoSourceBranchName.replace(/^refs\/heads\//, "");
103162
}
104163

164+
const { "mock-branch": mockBranch } = options;
165+
if (mockBranch) {
166+
return mockBranch;
167+
}
168+
105169
// Depending on how the repo was cloned, HEAD may not exist. We only use this
106170
// method as fallback.
107171
const { stdout } = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -177,31 +241,15 @@ function getTagForStableBranch(branch, { tag }, log) {
177241
return { npmTag: NPM_TAG_NEXT, prerelease: "rc" };
178242
}
179243

180-
/**
181-
* @param {string} file
182-
* @param {string} tag
183-
* @returns {void}
184-
*/
185-
function verifyPublishPipeline(file, tag) {
186-
const data = fs.readFileSync(file, { encoding: "utf-8" });
187-
const m = data.match(/publishTag: '(latest|next|nightly|v\d+\.\d+-stable)'/);
188-
if (!m) {
189-
throw new Error(`${file}: Could not find npm publish tag`);
190-
}
191-
192-
if (m[1] !== tag) {
193-
throw new Error(`${file}: 'publishTag' must be set to '${tag}'`);
194-
}
195-
}
196-
197244
/**
198245
* Verifies the configuration and enables publishing on CI.
199246
* @param {NxConfig} config
200247
* @param {string} currentBranch
201248
* @param {TagInfo} tag
249+
* @param {Options} options
202250
* @returns {asserts config is NxConfig["release"]}
203251
*/
204-
function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNewTag }) {
252+
function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNewTag }, options) {
205253
/** @type {string[]} */
206254
const errors = [];
207255

@@ -244,7 +292,7 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
244292
generatorOptions.fallbackCurrentVersionResolver = "disk";
245293
}
246294
} else if (typeof generatorOptions.fallbackCurrentVersionResolver === "string") {
247-
errors.push("'release.version.generatorOptions.fallbackCurrentVersionResolver' must be unset");
295+
errors.push("'release.version.generatorOptions.fallbackCurrentVersionResolver' must be removed");
248296
generatorOptions.fallbackCurrentVersionResolver = undefined;
249297
}
250298

@@ -253,16 +301,24 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
253301
throw new Error("Nx Release is not correctly configured for the current branch");
254302
}
255303

256-
verifyPublishPipeline(ADO_PUBLISH_PIPELINE, tag);
257-
enablePublishingOnAzurePipelines();
304+
if (options["skip-auth"]) {
305+
info("Skipped npm auth validation");
306+
} else {
307+
verifyNpmAuth();
308+
}
309+
310+
// Don't enable publishing in PRs
311+
if (!getTargetBranch()) {
312+
enablePublishingOnAzurePipelines();
313+
}
258314
}
259315

260316
/**
261317
* @param {Options} options
262318
* @returns {number}
263319
*/
264320
function main(options) {
265-
const branch = getCurrentBranch();
321+
const branch = getCurrentBranch(options);
266322
if (!branch) {
267323
error("Could not get current branch");
268324
return 1;
@@ -273,10 +329,11 @@ function main(options) {
273329
const config = loadNxConfig(NX_CONFIG_FILE);
274330
try {
275331
if (isMainBranch(branch)) {
276-
enablePublishing(config, branch, { npmTag: NPM_TAG_NIGHTLY, prerelease: NPM_TAG_NIGHTLY });
332+
const info = { npmTag: NPM_TAG_NIGHTLY, prerelease: NPM_TAG_NIGHTLY };
333+
enablePublishing(config, branch, info, options);
277334
} else if (isStableBranch(branch)) {
278335
const tag = getTagForStableBranch(branch, options, logger);
279-
enablePublishing(config, branch, tag);
336+
enablePublishing(config, branch, tag, options);
280337
}
281338
} catch (e) {
282339
if (options.update) {
@@ -296,6 +353,13 @@ function main(options) {
296353
const { values } = util.parseArgs({
297354
args: process.argv.slice(2),
298355
options: {
356+
"mock-branch": {
357+
type: "string",
358+
},
359+
"skip-auth": {
360+
type: "boolean",
361+
default: false,
362+
},
299363
tag: {
300364
type: "string",
301365
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: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)