Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 8 additions & 0 deletions .ado/jobs/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jobs:
- script: |
echo Target branch: $(System.PullRequest.TargetBranch)
yarn nx release --dry-run --verbose

# Show what additional tags would be applied
node .ado/scripts/apply-additional-tags.mjs --tags "$(additionalTags)" --dry-run
displayName: Version and publish packages (dry run)
condition: and(succeeded(), ne(variables['publish_react_native_macos'], '1'))

Expand Down Expand Up @@ -83,6 +86,11 @@ jobs:
displayName: Publish packages
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))

- script: |
node .ado/scripts/apply-additional-tags.mjs --tags "$(additionalTags)" --token "$(npmAuthToken)"
displayName: Apply additional dist-tags
condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1'))
Comment thread
Saadnajmi marked this conversation as resolved.

- script: |
if [ "$(USE_YARN_FOR_PUBLISH)" = "true" ]; then
echo "Cleaning up yarn npm configuration"
Expand Down
102 changes: 102 additions & 0 deletions .ado/scripts/apply-additional-tags.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// @ts-check
import { spawnSync } from "node:child_process";
import * as fs from "node:fs";
import * as util from "node:util";

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

const registry = "https://registry.npmjs.org/";
const packages = [
"@react-native-macos/virtualized-lists",
"react-native-macos",
];

/**
* @typedef {{
* tags?: string;
* token?: string;
* "dry-run"?: boolean;
* }} Options;
*/

/**
* @param {Options} options
* @returns {number}
*/
function main({ tags, token, "dry-run": dryRun }) {
if (!tags) {
console.log("No additional tags to apply");
return 0;
}

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

const packageJson = JSON.parse(
fs.readFileSync("./packages/react-native/package.json", "utf-8")
);
const version = packageJson.version;

if (dryRun) {
console.log("");
console.log("=== Additional dist-tags that would be applied ===");
for (const tag of tags.split(",")) {
for (const pkg of packages) {
console.log(` ${pkg}@${version} -> ${tag}`);
}
}
return 0;
}

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}`,
],
{ stdio: "inherit", shell: true }
);

if (result.status !== 0) {
console.error(`Failed to add dist-tag '${tag}' to ${pkg}@${version}`);
return 1;
}
}
}

return 0;
}

const { values } = util.parseArgs({
args: process.argv.slice(2),
options: {
tags: {
type: "string",
},
token: {
type: "string",
},
"dry-run": {
type: "boolean",
default: false,
},
},
strict: true,
});

process.exitCode = main(values);
84 changes: 47 additions & 37 deletions .ado/scripts/prepublish-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ const RNMACOS_NEXT = "react-native-macos@next";
* verbose?: boolean;
* }} Options;
* @typedef {{
* npmTag: string;
* npmTags: string[];
* prerelease?: string;
* isNewTag?: boolean;
* }} TagInfo;
*/

Expand Down Expand Up @@ -264,7 +263,12 @@ function getPublishedVersion(tag) {
}

/**
* Returns the npm tag and prerelease identifier for the specified branch.
* Returns the npm tags and prerelease identifier for the specified branch.
*
* The first tag in the array is used for the initial publish. When promoting
* to `latest`, also includes additional tags to apply:
* - The version-specific stable tag (e.g., `v0.81-stable`)
* - The `next` tag if the current `next` version is lower
*
* @privateRemarks
* Note that the current implementation treats minor versions as major. If
Expand All @@ -276,50 +280,57 @@ function getPublishedVersion(tag) {
* @param {typeof info} log
* @returns {TagInfo}
*/
function getTagForStableBranch(branch, { tag }, log) {
function getTagsForStableBranch(branch, { tag }, log) {
if (!isStableBranch(branch)) {
throw new Error("Expected a stable branch");
}

const latestVersion = getPublishedVersion("latest");
const nextVersion = getPublishedVersion("next");
const currentVersion = versionToNumber(branch);

log(`${RNMACOS_LATEST}: ${latestVersion}`);
log(`${RNMACOS_NEXT}: ${nextVersion}`);
log(`Current version: ${currentVersion}`);

// Patching latest version
if (currentVersion === latestVersion) {
const npmTag = "latest";
log(`Expected npm tag: ${npmTag}`);
return { npmTag };
const versionTag = "v" + branch;
log(`Expected npm tags: latest, ${versionTag}`);
return { npmTags: ["latest", versionTag] };
}

// Demoting or patching an older stable version
if (currentVersion < latestVersion) {
const npmTag = "v" + branch;
log(`Expected npm tag: ${npmTag}`);
// If we're demoting a branch, we will need to create a new tag. This will
// make Nx trip if we don't specify a fallback. In all other scenarios, the
// tags should exist and therefore prefer it to fail.
return { npmTag, isNewTag: true };
log(`Expected npm tags: ${npmTag}`);
return { npmTags: [npmTag] };
}

// Publishing a new latest version
if (tag === "latest") {
log(`Expected npm tag: ${tag}`);
return { npmTag: tag };
// When promoting to latest, also add the version-specific stable tag
const versionTag = "v" + branch;
Comment thread
Saadnajmi marked this conversation as resolved.
Outdated
const npmTags = ["latest", versionTag];

// Also add "next" tag if the current next version is lower
if (currentVersion > nextVersion) {
npmTags.push(NPM_TAG_NEXT);
}

log(`Expected npm tags: ${npmTags.join(", ")}`);
return { npmTags };
}

// Publishing a release candidate
const nextVersion = getPublishedVersion("next");
log(`${RNMACOS_NEXT}: ${nextVersion}`);
log(`Expected npm tag: ${NPM_TAG_NEXT}`);
// currentVersion > latestVersion
log(`Expected npm tags: ${NPM_TAG_NEXT}`);

if (currentVersion < nextVersion) {
throw new Error(`Current version cannot be a release candidate because it is too old: ${currentVersion} < ${nextVersion}`);
}

return { npmTag: NPM_TAG_NEXT, prerelease: "rc" };
return { npmTags: [NPM_TAG_NEXT], prerelease: "rc" };
}

/**
Expand All @@ -330,11 +341,12 @@ function getTagForStableBranch(branch, { tag }, log) {
* @param {Options} options
* @returns {asserts config is NxConfig["release"]}
*/
function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNewTag }, options) {
function enablePublishing(config, currentBranch, { npmTags, prerelease }, options) {
/** @type {string[]} */
const errors = [];

const { defaultBase, release } = config;
const [primaryTag, ...additionalTags] = npmTags;

// `defaultBase` determines what we diff against when looking for tags or
// released version and must therefore be set to either the main branch or one
Expand All @@ -358,23 +370,10 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe

// What the published version should be tagged as e.g., "latest" or "nightly".
const currentVersionResolverMetadata = /** @type {{ tag?: string }} */ (versionActionsOptions.currentVersionResolverMetadata || {});
if (currentVersionResolverMetadata.tag !== tag) {
errors.push(`'release.version.versionActionsOptions.currentVersionResolverMetadata.tag' must be set to '${tag}'`);
if (currentVersionResolverMetadata.tag !== primaryTag) {
errors.push(`'release.version.versionActionsOptions.currentVersionResolverMetadata.tag' must be set to '${primaryTag}'`);
versionActionsOptions.currentVersionResolverMetadata ??= {};
/** @type {any} */ (versionActionsOptions.currentVersionResolverMetadata).tag = tag;
}

// If we're demoting a branch, we will need to create a new tag. This will
// make Nx trip if we don't specify a fallback. In all other scenarios, the
// tags should exist and therefore prefer it to fail.
Comment thread
Saadnajmi marked this conversation as resolved.
if (isNewTag) {
if (versionActionsOptions.fallbackCurrentVersionResolver !== "disk") {
errors.push("'release.version.versionActionsOptions.fallbackCurrentVersionResolver' must be set to 'disk'");
versionActionsOptions.fallbackCurrentVersionResolver = "disk";
}
} else if (typeof versionActionsOptions.fallbackCurrentVersionResolver === "string") {
errors.push("'release.version.versionActionsOptions.fallbackCurrentVersionResolver' must be removed");
versionActionsOptions.fallbackCurrentVersionResolver = undefined;
/** @type {any} */ (versionActionsOptions.currentVersionResolverMetadata).tag = primaryTag;
}

if (errors.length > 0) {
Expand All @@ -388,6 +387,17 @@ function enablePublishing(config, currentBranch, { npmTag: tag, prerelease, isNe
verifyNpmAuth();
}

// Output additional tags as pipeline/workflow variable
if (additionalTags.length > 0) {
const tagsValue = additionalTags.join(",");
// Azure Pipelines
console.log(`##vso[task.setvariable variable=additionalTags]${tagsValue}`);
// GitHub Actions
if (process.env["GITHUB_OUTPUT"]) {
fs.appendFileSync(process.env["GITHUB_OUTPUT"], `additionalTags=${tagsValue}\n`);
}
}

// Don't enable publishing in PRs
if (!getTargetBranch()) {
enablePublishingOnAzurePipelines();
Expand All @@ -410,10 +420,10 @@ function main(options) {
const config = loadNxConfig(NX_CONFIG_FILE);
try {
if (isMainBranch(branch)) {
const info = { npmTag: NPM_TAG_NIGHTLY, prerelease: NPM_TAG_NIGHTLY };
const info = { npmTags: [NPM_TAG_NIGHTLY], prerelease: NPM_TAG_NIGHTLY };
enablePublishing(config, branch, info, options);
} else if (isStableBranch(branch)) {
const tag = getTagForStableBranch(branch, options, logger);
const tag = getTagsForStableBranch(branch, options, logger);
enablePublishing(config, branch, tag, options);
}
} catch (e) {
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/microsoft-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
- name: Read publish tag from nx.json
id: config
run: |
PUBLISH_TAG=$(jq -r '.release.version.generatorOptions.currentVersionResolverMetadata.tag' nx.json)
PUBLISH_TAG=$(jq -r '.release.version.versionActionsOptions.currentVersionResolverMetadata.tag' nx.json)
echo "publishTag=$PUBLISH_TAG" >> $GITHUB_OUTPUT
echo "Using publish tag from nx.json: $PUBLISH_TAG"
- name: Configure git
Expand All @@ -67,13 +67,17 @@ jobs:
- name: Install dependencies
run: yarn
- name: Verify release config
id: prepublish
run: |
node .ado/scripts/prepublish-check.mjs --verbose --skip-auth --tag ${{ steps.config.outputs.publishTag }}

- name: Version and publish packages (dry run)
run: |
echo "Target branch: ${{ github.base_ref }}"
yarn nx release --dry-run --verbose

# Show what additional tags would be applied
node .ado/scripts/apply-additional-tags.mjs --tags "${{ steps.prepublish.outputs.additionalTags }}" --dry-run

yarn-constraints:
name: "Check Yarn Constraints"
Expand Down
Loading