Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .ado/jobs/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
displayName: Install npm dependencies

- script: |
node .ado/scripts/configure-publish.mts --verbose --skip-auth
node .ado/scripts/configure-publish.mts --verbose
displayName: Verify release config

# Disable Nightly publishing on the main branch
Expand All @@ -52,9 +52,11 @@ jobs:
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))

- script: |
node .ado/scripts/apply-additional-tags.mjs --tags "$(additionalTags)" --token "$(npmAuthToken)"
node .ado/scripts/apply-additional-tags.mjs --tags "$(additionalTags)"
displayName: Apply additional dist-tags
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
env:
NODE_AUTH_TOKEN: $(npmAuthToken)

- script: |
yarn config unset npmPublishAccess || true
Expand Down
27 changes: 11 additions & 16 deletions .ado/scripts/apply-additional-tags.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import * as util from "node:util";

/**
* Apply additional dist-tags to published packages
* Usage: node apply-additional-tags.mjs --tags <tags> --token <token>
* Usage: node apply-additional-tags.mjs --tags <tags>
* node apply-additional-tags.mjs --tags <tags> --dry-run
* Where tags is a comma-separated list of tags (e.g., "next,v0.79-stable")
*
* Auth token is read from the NODE_AUTH_TOKEN environment variable.
*/

const registry = "https://registry.npmjs.org/";
Expand All @@ -19,7 +21,6 @@ const packages = [
/**
* @typedef {{
* tags?: string;
* token?: string;
* "dry-run"?: boolean;
* }} Options;
*/
Expand All @@ -28,14 +29,18 @@ const packages = [
* @param {Options} options
* @returns {number}
*/
function main({ tags, token, "dry-run": dryRun }) {
function main({ tags, "dry-run": dryRun }) {
if (!tags) {
console.log("No additional tags to apply");
return 0;
}

const token = process.env.NODE_AUTH_TOKEN;

if (!dryRun && !token) {
console.error("Error: npm auth token is required (use --dry-run to preview)");
console.error(
"Error: NODE_AUTH_TOKEN is required (use --dry-run to preview)"
);
return 1;
}

Expand All @@ -58,17 +63,10 @@ function main({ tags, token, "dry-run": dryRun }) {
for (const tag of tags.split(",")) {
for (const pkg of packages) {
console.log(`Adding dist-tag '${tag}' to ${pkg}@${version}`);

const result = spawnSync(
"npm",
[
"dist-tag",
"add",
`${pkg}@${version}`,
tag,
"--registry",
registry,
`--//registry.npmjs.org/:_authToken=${token}`,
],
["dist-tag", "add", `${pkg}@${version}`, tag, "--registry", registry],
{ stdio: "inherit", shell: true }
);

Expand All @@ -88,9 +86,6 @@ const { values } = util.parseArgs({
tags: {
type: "string",
},
token: {
type: "string",
},
"dry-run": {
type: "boolean",
default: false,
Expand Down
46 changes: 15 additions & 31 deletions .ado/scripts/configure-publish.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import { $, argv, echo, fs } from 'zx';
import { resolve } from 'node:path';

const NPM_DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
const isGitHubActions = process.env['GITHUB_ACTIONS'] === 'true';

const NPM_TAG_NEXT = 'next';

export type ReleaseState = 'STABLE_IS_LATEST' | 'STABLE_IS_NEW' | 'STABLE_IS_OLD';
Expand All @@ -21,19 +22,20 @@ export interface TagInfo {

interface Options {
'mock-branch'?: string;
'skip-auth'?: boolean;
tag?: string;
verbose?: boolean;
}

/**
* Exports a variable, `publish_react_native_macos`, to signal that we want to
* enable publishing on Azure Pipelines.
*/
function enablePublishingOnAzurePipelines() {
echo(`##vso[task.setvariable variable=publish_react_native_macos]1`);
}

function enablePublishingOnGitHubActions() {
if (process.env['GITHUB_OUTPUT']) {
fs.appendFileSync(process.env['GITHUB_OUTPUT'], `publish_react_native_macos=1\n`);
}
}

export function isMainBranch(branch: string): boolean {
return branch === 'main';
}
Expand Down Expand Up @@ -160,23 +162,6 @@ export function getPublishTags(
}
}

async function verifyNpmAuth(registry = NPM_DEFAULT_REGISTRY) {
const whoami = await $`npm whoami --registry ${registry}`.nothrow();
if (whoami.exitCode !== 0) {
const errText = whoami.stderr;
const m = errText.match(/npm error code (\w+)/);
const errorCode = m && m[1];
switch (errorCode) {
case 'EINVALIDNPMTOKEN':
throw new Error(`Invalid auth token for npm registry: ${registry}`);
case 'ENEEDAUTH':
throw new Error(`Missing auth token for npm registry: ${registry}`);
default:
throw new Error(errText);
}
}
}

async function enablePublishing(tagInfo: TagInfo, options: Options) {
const [primaryTag, ...additionalTags] = tagInfo.npmTags;

Expand All @@ -195,15 +180,15 @@ async function enablePublishing(tagInfo: TagInfo, options: Options) {
}
}

if (options['skip-auth']) {
echo('ℹ️ Skipped npm auth validation');
} else {
await verifyNpmAuth();
}

// Don't enable publishing in PRs
if (!getTargetBranch()) {
enablePublishingOnAzurePipelines();
if (isGitHubActions) {
enablePublishingOnGitHubActions();
} else if (process.env['TF_BUILD'] === 'True') {
enablePublishingOnAzurePipelines();
} else {
echo('ℹ️ Local run — publishing not enabled');
}
}
}

Expand All @@ -215,7 +200,6 @@ if (isDirectRun) {
// Parse CLI args using zx's argv (minimist)
const options: Options = {
'mock-branch': argv['mock-branch'] as string | undefined,
'skip-auth': Boolean(argv['skip-auth']),
tag: typeof argv['tag'] === 'string' ? argv['tag'] : NPM_TAG_NEXT,
verbose: Boolean(argv['verbose']),
};
Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/microsoft-npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Publish to npm

on:
push:
branches:
- "*-stable"

jobs:
publish:
name: Publish to npm
runs-on: ubuntu-latest

if: github.repository == 'microsoft/react-native-macos'

# Matches the environment name registered as a Trusted Publisher on npmjs.com.
environment: npm-publish

permissions:
contents: read
id-token: write

steps:
- name: Checkout
uses: actions/checkout@v4
with:
filter: blob:none
fetch-depth: 0

- name: Setup toolchain
uses: ./.github/actions/microsoft-setup-toolchain
with:
node-version: "22"

- name: Install dependencies
run: yarn install --immutable

- name: Verify release config
id: configure-publish
run: node .ado/scripts/configure-publish.mts --verbose

- name: Configure yarn for npm publishing
if: steps.configure-publish.outputs.publish_react_native_macos == '1'
run: |
yarn config set npmPublishAccess public
yarn config set npmPublishRegistry "https://registry.npmjs.org"
Comment on lines +41 to +45

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also unset unconditionally after


- name: Publish packages
if: steps.configure-publish.outputs.publish_react_native_macos == '1'
run: |
yarn workspaces foreach -vv --all --topological --no-private npm publish \
--provenance \
--tag "${{ steps.configure-publish.outputs.publishTag }}" \
--tolerate-republish

- name: Remove npm auth configuration
if: always()
run: |
yarn config unset npmPublishAccess || true
yarn config unset npmPublishRegistry || true
Loading