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
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'))

- 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;
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.
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
5 changes: 5 additions & 0 deletions .nx/version-plans/version-plan-1768362735076.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
__default__: patch
---

Release 0.81.0
5 changes: 2 additions & 3 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@
"versionActionsOptions": {
"currentVersionResolver": "registry",
"currentVersionResolverMetadata": {
"tag": "next"
},
"preid": "rc"
"tag": "latest"
}
},
"useLegacyVersioning": false
}
Expand Down
Loading