Skip to content

Commit 21d1a0e

Browse files
authored
Merge branch 'main' into gha
2 parents e276015 + 9634b2d commit 21d1a0e

19 files changed

Lines changed: 579 additions & 728 deletions

File tree

.ado/jobs/npm-publish.yml

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ jobs:
88
variables:
99
- name: BUILDSECMON_OPT_IN
1010
value: true
11+
- name: USE_YARN_FOR_PUBLISH
12+
value: false
13+
1114
timeoutInMinutes: 90
1215
cancelTimeoutInMinutes: 5
1316
templateContext:
@@ -49,12 +52,6 @@ jobs:
4952
5053
# Disable Nightly publishing on the main branch
5154
- ${{ if endsWith(variables['Build.SourceBranchName'], '-stable') }}:
52-
- script: |
53-
echo "//registry.npmjs.org/:_authToken=$(npmAuthToken)" > ~/.npmrc
54-
node .ado/scripts/prepublish-check.mjs --verbose --tag $(publishTag)
55-
displayName: Set and validate npm auth
56-
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
57-
5855
- script: |
5956
git switch $(Build.SourceBranchName)
6057
yarn nx release --skip-publish --verbose
@@ -64,18 +61,36 @@ jobs:
6461
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
6562
6663
- script: |
64+
set -eox pipefail
6765
if [[ -f .rnm-publish ]]; then
6866
# https://github.com/microsoft/react-native-macos/issues/2580
6967
# `nx release publish` gets confused by the output of RNM's prepack script.
70-
# Let's call `yarn npm publish` directly instead on the packages we want to publish.
71-
# yarn nx release publish --tag ${{ parameters['publishTag'] }} --excludeTaskDependencies
72-
yarn ./packages/virtualized-lists npm publish --tag ${{ parameters['publishTag'] }}
73-
yarn ./packages/react-native npm publish --tag ${{ parameters['publishTag'] }}
68+
# Let's call publish directly instead on the packages we want to publish.
69+
# yarn nx release publish --tag $(publishTag) --excludeTaskDependencies
70+
if [ "$(USE_YARN_FOR_PUBLISH)" = "true" ]; then
71+
echo "Configuring yarn for npm publishing"
72+
yarn config set npmPublishRegistry "https://registry.npmjs.org"
73+
yarn config set npmAuthToken $(npmAuthToken)
74+
echo "Publishing with yarn npm publish"
75+
yarn ./packages/virtualized-lists npm publish --tag $(publishTag)
76+
yarn ./packages/react-native npm publish --tag $(publishTag)
77+
else
78+
echo "Publishing with npm publish"
79+
npm publish ./packages/virtualized-lists --tag $(publishTag) --registry https://registry.npmjs.org/ --//registry.npmjs.org/:_authToken=$(npmAuthToken)
80+
npm publish ./packages/react-native --tag $(publishTag) --registry https://registry.npmjs.org/ --//registry.npmjs.org/:_authToken=$(npmAuthToken)
81+
fi
7482
fi
7583
displayName: Publish packages
7684
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
7785
7886
- script: |
79-
rm -f ~/.npmrc
80-
displayName: Remove npmrc if it exists
87+
if [ "$(USE_YARN_FOR_PUBLISH)" = "true" ]; then
88+
echo "Cleaning up yarn npm configuration"
89+
yarn config unset npmAuthToken || true
90+
yarn config unset npmPublishRegistry || true
91+
else
92+
echo "Cleaning up npm configuration"
93+
rm -f ~/.npmrc
94+
fi
95+
displayName: Remove NPM auth configuration
8196
condition: always()

.ado/scripts/prepublish-check.mjs

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -88,44 +88,90 @@ function loadNxConfig(configFile) {
8888
return JSON.parse(nx);
8989
}
9090

91+
/**
92+
* Detects whether to use npm or yarn to publish based on .npmrc existence
93+
* @returns {boolean} true if npm should be used, false if yarn should be used
94+
* @throws {Error} if neither .npmrc nor .yarnrc.yml exists
95+
*/
96+
function shouldUseNpm() {
97+
const hasNpmrc = fs.existsSync('.npmrc');
98+
const hasYarnrc = fs.existsSync('.yarnrc.yml');
99+
100+
if (!hasNpmrc && !hasYarnrc) {
101+
error('No package manager configuration found. Expected either .npmrc or .yarnrc.yml file.');
102+
throw new Error('No package manager configuration found');
103+
}
104+
105+
if (hasNpmrc && hasYarnrc) {
106+
// If both exist, prefer npm (could be changed based on project preference)
107+
info('Both .npmrc and .yarnrc.yml found, using npm configuration');
108+
return true;
109+
}
110+
111+
return hasNpmrc;
112+
}
113+
91114
function verifyNpmAuth(registry = NPM_DEFEAULT_REGISTRY) {
92-
const npmErrorRegex = /npm error code (\w+)/;
115+
const useNpm = shouldUseNpm();
93116
const spawnOptions = {
94117
stdio: /** @type {const} */ ("pipe"),
95118
shell: true,
96119
windowsVerbatimArguments: true,
97120
};
98121

99-
const whoamiArgs = ["whoami", "--registry", registry];
100-
const whoami = spawnSync("npm", whoamiArgs, spawnOptions);
101-
if (whoami.status !== 0) {
102-
const error = whoami.stderr.toString();
103-
const m = error.match(npmErrorRegex);
104-
const errorCode = m && m[1];
105-
switch (errorCode) {
106-
case "EINVALIDNPMTOKEN":
107-
throw new Error(`Invalid auth token for npm registry: ${registry}`);
108-
case "ENEEDAUTH":
109-
throw new Error(`Missing auth token for npm registry: ${registry}`);
110-
default:
111-
throw new Error(error);
122+
if (useNpm) {
123+
info("Using npm for authentication (found .npmrc)");
124+
const npmErrorRegex = /npm error code (\w+)/;
125+
126+
const whoamiArgs = ["whoami", "--registry", registry];
127+
const whoami = spawnSync("npm", whoamiArgs, spawnOptions);
128+
if (whoami.status !== 0) {
129+
const error = whoami.stderr.toString();
130+
const m = error.match(npmErrorRegex);
131+
const errorCode = m && m[1];
132+
switch (errorCode) {
133+
case "EINVALIDNPMTOKEN":
134+
throw new Error(`Invalid auth token for npm registry: ${registry}`);
135+
case "ENEEDAUTH":
136+
throw new Error(`Missing auth token for npm registry: ${registry}`);
137+
default:
138+
throw new Error(error);
139+
}
112140
}
113-
}
114141

115-
const tokenArgs = ["token", "list", "--registry", registry];
116-
const token = spawnSync("npm", tokenArgs, spawnOptions);
117-
if (token.status !== 0) {
118-
const error = token.stderr.toString();
119-
const m = error.match(npmErrorRegex);
120-
const errorCode = m && m[1];
142+
const tokenArgs = ["token", "list", "--registry", registry];
143+
const token = spawnSync("npm", tokenArgs, spawnOptions);
144+
if (token.status !== 0) {
145+
const error = token.stderr.toString();
146+
const m = error.match(npmErrorRegex);
147+
const errorCode = m && m[1];
148+
149+
// E403 means the token doesn't have permission to list tokens, but that's
150+
// not required for publishing. Only fail for other error codes.
151+
if (errorCode === "E403") {
152+
info(`Token verification skipped: token doesn't have permission to list tokens (${errorCode})`);
153+
} else {
154+
throw new Error(m ? `Auth token for '${registry}' returned error code ${errorCode}` : error);
155+
}
156+
}
157+
} else {
158+
info("Using yarn for authentication (no .npmrc found)");
121159

122-
// E403 means the token doesn't have permission to list tokens, but that's
123-
// not required for publishing. Only fail for other error codes.
124-
if (errorCode === "E403") {
125-
info(`Token verification skipped: token doesn't have permission to list tokens (${errorCode})`);
126-
} else {
127-
throw new Error(m ? `Auth token for '${registry}' returned error code ${errorCode}` : error);
160+
const whoamiArgs = ["npm", "whoami", "--publish"];
161+
const whoami = spawnSync("yarn", whoamiArgs, spawnOptions);
162+
if (whoami.status !== 0) {
163+
const errorOutput =
164+
whoami.stderr.toString().trim() ||
165+
whoami.stdout.toString().trim() ||
166+
'No error message available';
167+
168+
// Provide more context about the yarn authentication failure
169+
throw new Error(`Yarn authentication failed (exit code ${whoami.status}): ${errorOutput}`);
128170
}
171+
172+
// Skip token listing for yarn since it doesn't support npm token commands
173+
// The whoami check above is sufficient to verify authentication
174+
info("Skipping token list check when using yarn (not required for publishing)");
129175
}
130176
}
131177

0 commit comments

Comments
 (0)