@@ -3,9 +3,9 @@ import { spawnSync } from "node:child_process";
3
3
import * as fs from "node:fs" ;
4
4
import * as util from "node:util" ;
5
5
6
- const ADO_PUBLISH_PIPELINE = ".ado/templates/npm-publish-steps.yml" ;
7
6
const NX_CONFIG_FILE = "nx.json" ;
8
7
8
+ const NPM_DEFEAULT_REGISTRY = "https://registry.npmjs.org/"
9
9
const NPM_TAG_NEXT = "next" ;
10
10
const NPM_TAG_NIGHTLY = "nightly" ;
11
11
const RNMACOS_LATEST = "react-native-macos@latest" ;
@@ -21,8 +21,18 @@ const RNMACOS_NEXT = "react-native-macos@next";
21
21
* };
22
22
* };
23
23
* }} 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;
26
36
*/
27
37
28
38
/**
@@ -80,6 +90,38 @@ function loadNxConfig(configFile) {
80
90
return JSON . parse ( nx ) ;
81
91
}
82
92
93
+ function verifyNpmAuth ( registry = NPM_DEFEAULT_REGISTRY ) {
94
+ const npmErrorRegex = / n p m e r r o r c o d e ( \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
+
83
125
/**
84
126
* Returns a numerical value for a given version string.
85
127
* @param {string } version
@@ -91,15 +133,65 @@ function versionToNumber(version) {
91
133
}
92
134
93
135
/**
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
+ // Azure Pipelines
142
+ if ( process . env [ "TF_BUILD" ] === "True" ) {
143
+ // https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
144
+ const targetBranch = process . env [ "SYSTEM_PULLREQUEST_TARGETBRANCH" ] ;
145
+ return targetBranch ?. replace ( / ^ r e f s \/ h e a d s \/ / , "" ) ;
146
+ }
147
+
148
+ // GitHub Actions
149
+ if ( process . env [ "GITHUB_ACTIONS" ] === "true" ) {
150
+ // https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
151
+ return process . env [ "GITHUB_BASE_REF" ] ;
152
+ }
153
+
154
+ return undefined ;
155
+ }
156
+
157
+ /**
158
+ * Returns the current branch name. In a pull request, the target branch name is
159
+ * returned.
160
+ * @param {Options } options
96
161
* @returns {string }
97
162
*/
98
- function getCurrentBranch ( ) {
99
- // https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
100
- const adoSourceBranchName = process . env [ "BUILD_SOURCEBRANCHNAME" ] ;
101
- if ( adoSourceBranchName ) {
102
- return adoSourceBranchName . replace ( / ^ r e f s \/ h e a d s \/ / , "" ) ;
163
+ function getCurrentBranch ( options ) {
164
+ const targetBranch = getTargetBranch ( ) ;
165
+ if ( targetBranch ) {
166
+ return targetBranch ;
167
+ }
168
+
169
+ // Azure DevOps Pipelines
170
+ if ( process . env [ "TF_BUILD" ] === "True" ) {
171
+ // https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
172
+ const sourceBranch = process . env [ "BUILD_SOURCEBRANCHNAME" ] ;
173
+ if ( sourceBranch ) {
174
+ return sourceBranch . replace ( / ^ r e f s \/ h e a d s \/ / , "" ) ;
175
+ }
176
+ }
177
+
178
+ // GitHub Actions
179
+ if ( process . env [ "GITHUB_ACTIONS" ] === "true" ) {
180
+ // https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
181
+ const headRef = process . env [ "GITHUB_HEAD_REF" ] ;
182
+ if ( headRef ) {
183
+ return headRef ; // For pull requests
184
+ }
185
+
186
+ const ref = process . env [ "GITHUB_REF" ] ;
187
+ if ( ref ) {
188
+ return ref . replace ( / ^ r e f s \/ h e a d s \/ / , "" ) ; // For push events
189
+ }
190
+ }
191
+
192
+ const { "mock-branch" : mockBranch } = options ;
193
+ if ( mockBranch ) {
194
+ return mockBranch ;
103
195
}
104
196
105
197
// Depending on how the repo was cloned, HEAD may not exist. We only use this
@@ -177,31 +269,15 @@ function getTagForStableBranch(branch, { tag }, log) {
177
269
return { npmTag : NPM_TAG_NEXT , prerelease : "rc" } ;
178
270
}
179
271
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 ( / p u b l i s h T a g : ' ( l a t e s t | n e x t | n i g h t l y | v \d + \. \d + - s t a b l e ) ' / ) ;
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
-
197
272
/**
198
273
* Verifies the configuration and enables publishing on CI.
199
274
* @param {NxConfig } config
200
275
* @param {string } currentBranch
201
276
* @param {TagInfo } tag
277
+ * @param {Options } options
202
278
* @returns {asserts config is NxConfig["release"] }
203
279
*/
204
- function enablePublishing ( config , currentBranch , { npmTag : tag , prerelease, isNewTag } ) {
280
+ function enablePublishing ( config , currentBranch , { npmTag : tag , prerelease, isNewTag } , options ) {
205
281
/** @type {string[] } */
206
282
const errors = [ ] ;
207
283
@@ -244,7 +320,7 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
244
320
generatorOptions . fallbackCurrentVersionResolver = "disk" ;
245
321
}
246
322
} else if ( typeof generatorOptions . fallbackCurrentVersionResolver === "string" ) {
247
- errors . push ( "'release.version.generatorOptions.fallbackCurrentVersionResolver' must be unset " ) ;
323
+ errors . push ( "'release.version.generatorOptions.fallbackCurrentVersionResolver' must be removed " ) ;
248
324
generatorOptions . fallbackCurrentVersionResolver = undefined ;
249
325
}
250
326
@@ -253,16 +329,24 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
253
329
throw new Error ( "Nx Release is not correctly configured for the current branch" ) ;
254
330
}
255
331
256
- verifyPublishPipeline ( ADO_PUBLISH_PIPELINE , tag ) ;
257
- enablePublishingOnAzurePipelines ( ) ;
332
+ if ( options [ "skip-auth" ] ) {
333
+ info ( "Skipped npm auth validation" ) ;
334
+ } else {
335
+ verifyNpmAuth ( ) ;
336
+ }
337
+
338
+ // Don't enable publishing in PRs
339
+ if ( ! getTargetBranch ( ) ) {
340
+ enablePublishingOnAzurePipelines ( ) ;
341
+ }
258
342
}
259
343
260
344
/**
261
345
* @param {Options } options
262
346
* @returns {number }
263
347
*/
264
348
function main ( options ) {
265
- const branch = getCurrentBranch ( ) ;
349
+ const branch = getCurrentBranch ( options ) ;
266
350
if ( ! branch ) {
267
351
error ( "Could not get current branch" ) ;
268
352
return 1 ;
@@ -273,10 +357,11 @@ function main(options) {
273
357
const config = loadNxConfig ( NX_CONFIG_FILE ) ;
274
358
try {
275
359
if ( isMainBranch ( branch ) ) {
276
- enablePublishing ( config , branch , { npmTag : NPM_TAG_NIGHTLY , prerelease : NPM_TAG_NIGHTLY } ) ;
360
+ const info = { npmTag : NPM_TAG_NIGHTLY , prerelease : NPM_TAG_NIGHTLY } ;
361
+ enablePublishing ( config , branch , info , options ) ;
277
362
} else if ( isStableBranch ( branch ) ) {
278
363
const tag = getTagForStableBranch ( branch , options , logger ) ;
279
- enablePublishing ( config , branch , tag ) ;
364
+ enablePublishing ( config , branch , tag , options ) ;
280
365
}
281
366
} catch ( e ) {
282
367
if ( options . update ) {
@@ -296,6 +381,13 @@ function main(options) {
296
381
const { values } = util . parseArgs ( {
297
382
args : process . argv . slice ( 2 ) ,
298
383
options : {
384
+ "mock-branch" : {
385
+ type : "string" ,
386
+ } ,
387
+ "skip-auth" : {
388
+ type : "boolean" ,
389
+ default : false ,
390
+ } ,
299
391
tag : {
300
392
type : "string" ,
301
393
default : NPM_TAG_NEXT ,
0 commit comments