@@ -6,6 +6,7 @@ import * as util from "node:util";
66const ADO_PUBLISH_PIPELINE = ".ado/templates/npm-publish-steps.yml" ;
77const NX_CONFIG_FILE = "nx.json" ;
88
9+ const NPM_DEFEAULT_REGISTRY = "https://registry.npmjs.org/"
910const NPM_TAG_NEXT = "next" ;
1011const NPM_TAG_NIGHTLY = "nightly" ;
1112const RNMACOS_LATEST = "react-native-macos@latest" ;
@@ -21,8 +22,18 @@ 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+ * "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;
2637 */
2738
2839/**
@@ -80,6 +91,38 @@ function loadNxConfig(configFile) {
8091 return JSON . parse ( nx ) ;
8192}
8293
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+
83126/**
84127 * Returns a numerical value for a given version string.
85128 * @param {string } version
@@ -91,17 +134,39 @@ function versionToNumber(version) {
91134}
92135
93136/**
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
96151 * @returns {string }
97152 */
98- function getCurrentBranch ( ) {
153+ function getCurrentBranch ( options ) {
154+ const adoTargetBranchName = getTargetBranch ( ) ;
155+ if ( adoTargetBranchName ) {
156+ return adoTargetBranchName ;
157+ }
158+
99159 // https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
100160 const adoSourceBranchName = process . env [ "BUILD_SOURCEBRANCHNAME" ] ;
101161 if ( adoSourceBranchName ) {
102162 return adoSourceBranchName . replace ( / ^ r e f s \/ h e a d s \/ / , "" ) ;
103163 }
104164
165+ const { "mock-branch" : mockBranch } = options ;
166+ if ( mockBranch ) {
167+ return mockBranch ;
168+ }
169+
105170 // Depending on how the repo was cloned, HEAD may not exist. We only use this
106171 // method as fallback.
107172 const { stdout } = spawnSync ( "git" , [ "rev-parse" , "--abbrev-ref" , "HEAD" ] ) ;
@@ -199,9 +264,10 @@ function verifyPublishPipeline(file, tag) {
199264 * @param {NxConfig } config
200265 * @param {string } currentBranch
201266 * @param {TagInfo } tag
267+ * @param {Options } options
202268 * @returns {asserts config is NxConfig["release"] }
203269 */
204- function enablePublishing ( config , currentBranch , { npmTag : tag , prerelease, isNewTag } ) {
270+ function enablePublishing ( config , currentBranch , { npmTag : tag , prerelease, isNewTag } , options ) {
205271 /** @type {string[] } */
206272 const errors = [ ] ;
207273
@@ -244,7 +310,7 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
244310 generatorOptions . fallbackCurrentVersionResolver = "disk" ;
245311 }
246312 } 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 " ) ;
248314 generatorOptions . fallbackCurrentVersionResolver = undefined ;
249315 }
250316
@@ -253,16 +319,26 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
253319 throw new Error ( "Nx Release is not correctly configured for the current branch" ) ;
254320 }
255321
322+ if ( options [ "skip-auth" ] ) {
323+ info ( "Skipped npm auth validation" ) ;
324+ } else {
325+ verifyNpmAuth ( ) ;
326+ }
327+
256328 verifyPublishPipeline ( ADO_PUBLISH_PIPELINE , tag ) ;
257- enablePublishingOnAzurePipelines ( ) ;
329+
330+ // Don't enable publishing in PRs
331+ if ( ! getTargetBranch ( ) ) {
332+ enablePublishingOnAzurePipelines ( ) ;
333+ }
258334}
259335
260336/**
261337 * @param {Options } options
262338 * @returns {number }
263339 */
264340function main ( options ) {
265- const branch = getCurrentBranch ( ) ;
341+ const branch = getCurrentBranch ( options ) ;
266342 if ( ! branch ) {
267343 error ( "Could not get current branch" ) ;
268344 return 1 ;
@@ -273,10 +349,11 @@ function main(options) {
273349 const config = loadNxConfig ( NX_CONFIG_FILE ) ;
274350 try {
275351 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 ) ;
277354 } else if ( isStableBranch ( branch ) ) {
278355 const tag = getTagForStableBranch ( branch , options , logger ) ;
279- enablePublishing ( config , branch , tag ) ;
356+ enablePublishing ( config , branch , tag , options ) ;
280357 }
281358 } catch ( e ) {
282359 if ( options . update ) {
@@ -296,6 +373,13 @@ function main(options) {
296373const { values } = util . parseArgs ( {
297374 args : process . argv . slice ( 2 ) ,
298375 options : {
376+ "mock-branch" : {
377+ type : "string" ,
378+ } ,
379+ "skip-auth" : {
380+ type : "boolean" ,
381+ default : false ,
382+ } ,
299383 tag : {
300384 type : "string" ,
301385 default : NPM_TAG_NEXT ,
0 commit comments