@@ -6,6 +6,7 @@ import * as util from "node:util";
6
6
const ADO_PUBLISH_PIPELINE = ".ado/templates/npm-publish-steps.yml" ;
7
7
const NX_CONFIG_FILE = "nx.json" ;
8
8
9
+ const NPM_DEFEAULT_REGISTRY = "https://registry.npmjs.org/"
9
10
const NPM_TAG_NEXT = "next" ;
10
11
const NPM_TAG_NIGHTLY = "nightly" ;
11
12
const RNMACOS_LATEST = "react-native-macos@latest" ;
@@ -21,8 +22,18 @@ const RNMACOS_NEXT = "react-native-macos@next";
21
22
* };
22
23
* };
23
24
* }} NxConfig;
24
- * @typedef {{ tag?: string; update?: boolean; verbose?: boolean; } } Options;
25
- * @typedef {{ npmTag: string; prerelease?: string; isNewTag?: boolean; } } TagInfo;
25
+ * @typedef {{
26
+ * "mock-branch"?: string;
27
+ * "skip-auth"?: boolean;
28
+ * tag?: string;
29
+ * update?: boolean;
30
+ * verbose?: boolean;
31
+ * }} Options;
32
+ * @typedef {{
33
+ * npmTag: string;
34
+ * prerelease?: string;
35
+ * isNewTag?: boolean;
36
+ * }} TagInfo;
26
37
*/
27
38
28
39
/**
@@ -80,6 +91,38 @@ function loadNxConfig(configFile) {
80
91
return JSON . parse ( nx ) ;
81
92
}
82
93
94
+ function verifyNpmAuth ( registry = NPM_DEFEAULT_REGISTRY ) {
95
+ const npmErrorRegex = / n p m e r r o r c o d e ( \w + ) / ;
96
+ const spawnOptions = {
97
+ stdio : /** @type {const } */ ( "pipe" ) ,
98
+ shell : true ,
99
+ windowsVerbatimArguments : true ,
100
+ } ;
101
+
102
+ const whoamiArgs = [ "whoami" , "--registry" , registry ] ;
103
+ const whoami = spawnSync ( "npm" , whoamiArgs , spawnOptions ) ;
104
+ if ( whoami . status !== 0 ) {
105
+ const error = whoami . stderr . toString ( ) ;
106
+ const m = error . match ( npmErrorRegex ) ;
107
+ switch ( m && m [ 1 ] ) {
108
+ case "EINVALIDNPMTOKEN" :
109
+ throw new Error ( `Invalid auth token for npm registry: ${ registry } ` ) ;
110
+ case "ENEEDAUTH" :
111
+ throw new Error ( `Missing auth token for npm registry: ${ registry } ` ) ;
112
+ default :
113
+ throw new Error ( error ) ;
114
+ }
115
+ }
116
+
117
+ const tokenArgs = [ "token" , "list" , "--registry" , registry ] ;
118
+ const token = spawnSync ( "npm" , tokenArgs , spawnOptions ) ;
119
+ if ( token . status !== 0 ) {
120
+ const error = token . stderr . toString ( ) ;
121
+ const m = error . match ( npmErrorRegex ) ;
122
+ throw new Error ( m ? `Auth token for '${ registry } ' returned error code ${ m [ 1 ] } ` : error ) ;
123
+ }
124
+ }
125
+
83
126
/**
84
127
* Returns a numerical value for a given version string.
85
128
* @param {string } version
@@ -91,17 +134,39 @@ function versionToNumber(version) {
91
134
}
92
135
93
136
/**
94
- * Returns the currently checked out branch. Note that this function prefers
95
- * predefined CI environment variables over local clone.
137
+ * Returns the target branch name. If not targetting any branches (e.g., when
138
+ * executing this script locally), `undefined` is returned.
139
+ * @returns {string | undefined }
140
+ */
141
+ function getTargetBranch ( ) {
142
+ // https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
143
+ const adoTargetBranchName = process . env [ "SYSTEM_PULLREQUEST_TARGETBRANCH" ] ;
144
+ return adoTargetBranchName ?. replace ( / ^ r e f s \/ h e a d s \/ / , "" ) ;
145
+ }
146
+
147
+ /**
148
+ * Returns the current branch name. In a pull request, the target branch name is
149
+ * returned.
150
+ * @param {Options } options
96
151
* @returns {string }
97
152
*/
98
- function getCurrentBranch ( ) {
153
+ function getCurrentBranch ( options ) {
154
+ const adoTargetBranchName = getTargetBranch ( ) ;
155
+ if ( adoTargetBranchName ) {
156
+ return adoTargetBranchName ;
157
+ }
158
+
99
159
// https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
100
160
const adoSourceBranchName = process . env [ "BUILD_SOURCEBRANCHNAME" ] ;
101
161
if ( adoSourceBranchName ) {
102
162
return adoSourceBranchName . replace ( / ^ r e f s \/ h e a d s \/ / , "" ) ;
103
163
}
104
164
165
+ const { "mock-branch" : mockBranch } = options ;
166
+ if ( mockBranch ) {
167
+ return mockBranch ;
168
+ }
169
+
105
170
// Depending on how the repo was cloned, HEAD may not exist. We only use this
106
171
// method as fallback.
107
172
const { stdout } = spawnSync ( "git" , [ "rev-parse" , "--abbrev-ref" , "HEAD" ] ) ;
@@ -199,9 +264,10 @@ function verifyPublishPipeline(file, tag) {
199
264
* @param {NxConfig } config
200
265
* @param {string } currentBranch
201
266
* @param {TagInfo } tag
267
+ * @param {Options } options
202
268
* @returns {asserts config is NxConfig["release"] }
203
269
*/
204
- function enablePublishing ( config , currentBranch , { npmTag : tag , prerelease, isNewTag } ) {
270
+ function enablePublishing ( config , currentBranch , { npmTag : tag , prerelease, isNewTag } , options ) {
205
271
/** @type {string[] } */
206
272
const errors = [ ] ;
207
273
@@ -244,7 +310,7 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
244
310
generatorOptions . fallbackCurrentVersionResolver = "disk" ;
245
311
}
246
312
} else if ( typeof generatorOptions . fallbackCurrentVersionResolver === "string" ) {
247
- errors . push ( "'release.version.generatorOptions.fallbackCurrentVersionResolver' must be unset " ) ;
313
+ errors . push ( "'release.version.generatorOptions.fallbackCurrentVersionResolver' must be removed " ) ;
248
314
generatorOptions . fallbackCurrentVersionResolver = undefined ;
249
315
}
250
316
@@ -253,16 +319,26 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
253
319
throw new Error ( "Nx Release is not correctly configured for the current branch" ) ;
254
320
}
255
321
322
+ if ( options [ "skip-auth" ] ) {
323
+ info ( "Skipped npm auth validation" ) ;
324
+ } else {
325
+ verifyNpmAuth ( ) ;
326
+ }
327
+
256
328
verifyPublishPipeline ( ADO_PUBLISH_PIPELINE , tag ) ;
257
- enablePublishingOnAzurePipelines ( ) ;
329
+
330
+ // Don't enable publishing in PRs
331
+ if ( ! getTargetBranch ( ) ) {
332
+ enablePublishingOnAzurePipelines ( ) ;
333
+ }
258
334
}
259
335
260
336
/**
261
337
* @param {Options } options
262
338
* @returns {number }
263
339
*/
264
340
function main ( options ) {
265
- const branch = getCurrentBranch ( ) ;
341
+ const branch = getCurrentBranch ( options ) ;
266
342
if ( ! branch ) {
267
343
error ( "Could not get current branch" ) ;
268
344
return 1 ;
@@ -273,10 +349,11 @@ function main(options) {
273
349
const config = loadNxConfig ( NX_CONFIG_FILE ) ;
274
350
try {
275
351
if ( isMainBranch ( branch ) ) {
276
- enablePublishing ( config , branch , { npmTag : NPM_TAG_NIGHTLY , prerelease : NPM_TAG_NIGHTLY } ) ;
352
+ const info = { npmTag : NPM_TAG_NIGHTLY , prerelease : NPM_TAG_NIGHTLY } ;
353
+ enablePublishing ( config , branch , info , options ) ;
277
354
} else if ( isStableBranch ( branch ) ) {
278
355
const tag = getTagForStableBranch ( branch , options , logger ) ;
279
- enablePublishing ( config , branch , tag ) ;
356
+ enablePublishing ( config , branch , tag , options ) ;
280
357
}
281
358
} catch ( e ) {
282
359
if ( options . update ) {
@@ -296,6 +373,13 @@ function main(options) {
296
373
const { values } = util . parseArgs ( {
297
374
args : process . argv . slice ( 2 ) ,
298
375
options : {
376
+ "mock-branch" : {
377
+ type : "string" ,
378
+ } ,
379
+ "skip-auth" : {
380
+ type : "boolean" ,
381
+ default : false ,
382
+ } ,
299
383
tag : {
300
384
type : "string" ,
301
385
default : NPM_TAG_NEXT ,
0 commit comments