Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: set up proper publishing pipeline #2266

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
4 changes: 1 addition & 3 deletions .ado/jobs/npm-publish-dry-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,4 @@ jobs:
submodules: recursive # set to 'true' for a single level of submodules or 'recursive' to get submodules of submodules
persistCredentials: true # set to 'true' to leave the OAuth token in the Git config after the initial fetch

- template: /.ado/templates/apple-steps-publish.yml@self
parameters:
build_type: 'dry-run'
- template: /.ado/templates/npm-publish.yml@self
44 changes: 1 addition & 43 deletions .ado/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ variables:
- group: InfoSec-SecurityResults
- name: tags
value: production,externalfacing
# Remember to update this in previous stable branches when creating a new stable branch
- name : latestStableBranch
value: '0.76-stable'

resources:
repositories:
Expand Down Expand Up @@ -76,46 +73,7 @@ extends:
submodules: recursive # set to 'true' for a single level of submodules or 'recursive' to get submodules of submodules
persistCredentials: true # set to 'true' to leave the OAuth token in the Git config after the initial fetch

# Setup the repo to be ready for release. This includes:
# - Autogenerating the next version number
# - Calling the approprate scripts that upstream React Native uses to prepare a release
# - Skipping the actual `git tag`, `git push`, and `npm publish steps as we do that here instead

- ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
- template: .ado/templates/apple-steps-publish.yml@self
parameters:
build_type: nightly
- ${{ elseif endsWith(variables['Build.SourceBranchName'], '-stable') }}:
- template: .ado/templates/apple-steps-publish.yml@self
parameters:
build_type: release
- ${{ else }}:
- task: CmdLine@2
displayName: Unknown branch, skipping publish
inputs:
script: |
echo "Skipping publish for branch $(Build.SourceBranchName)"
exit 1

# Set the NPM dist-tag and do the actual NPM publish

- bash: echo "##vso[task.setvariable variable=npmDistTag]latest"
displayName: Set dist-tag to latest
condition: eq(variables['Build.SourceBranchName'], variables.latestStableBranch)

- bash: echo "##vso[task.setvariable variable=npmDistTag]canary"
displayName: Set dist-tag to canary
condition: eq(variables['Build.SourceBranchName'], 'main')

- bash: echo "##vso[task.setvariable variable=npmDistTag]v${{variables['Build.SourceBranchName']}}"
displayName: Set dist-tag to v0.x-stable
condition: and(ne(variables['Build.SourceBranchName'], 'main'), ne(variables['Build.SourceBranchName'], variables.latestStableBranch))

- task: CmdLine@2
displayName: Actual NPM Publish
inputs:
script: |
npm publish ./packages/react-native --tag $(npmDistTag) --registry https://registry.npmjs.org/ --//registry.npmjs.org/:_authToken=$(npmAuthToken)
- template: /.ado/templates/npm-publish.yml@self

# Set the git tag and push the version update back to Github

Expand Down
183 changes: 183 additions & 0 deletions .ado/scripts/prepublish-check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// @ts-check
import { spawnSync } from "node:child_process";
import * as fs from "node:fs";
import * as util from "node:util";

/**
* @typedef {typeof import("../../nx.json")} NxConfig
* @typedef {{ tag?: string }} Options
*/

/**
* Exports a variable, `publish_react_native_macos`, to signal that we want to
* enable publishing on Azure Pipelines.
*
* Note that pipelines need to read this variable separately and do the actual
* work to publish bits.
*/
function enablePublishingOnAzurePipelines() {
console.log(`##vso[task.setvariable variable=publish_react_native_macos]1`);
}

/**
* Returns whether the given branch is considered main branch.
* @param {string} branch
*/
function isMainBranch(branch) {
// There is currently no good way to consistently get the main branch. We
// hardcode the value for now.
return branch === "main";
}

/**
* Returns whether the given branch is considered a stable branch.
* @param {string} branch
*/
function isStableBranch(branch) {
return /^\d+\.\d+-stable$/.test(branch);
}

/**
* Loads Nx configuration.
* @returns {NxConfig}
*/
function loadNxConfig(configFile = "nx.json") {
const nx = fs.readFileSync(configFile, { encoding: "utf-8" });
return JSON.parse(nx);
}

/**
* @param {string} version
* @returns {number}
*/
function versionToNumber(version) {
const [major, minor] = version.split("-")[0].split(".");
return Number(major) * 1000 + Number(minor);
}

/**
* Returns the currently checked out branch. Note that this function prefers
* predefined CI environment variables over local clone.
* @returns {string}
*/
function getCurrentBranch() {
// https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
const adoSourceBranchName = process.env["BUILD_SOURCEBRANCHNAME"];
if (adoSourceBranchName) {
return adoSourceBranchName.replace(/^refs\/heads\//, "");
}

// Depending on how the repo was cloned, HEAD may not exist. We only use this
// method as fallback.
const { stdout } = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
return stdout.toString().trim();
}

function getLatestVersion() {
const { stdout } = spawnSync("npm", ["view", "react-native-macos@latest", "version"]);
return versionToNumber(stdout.toString().trim());
}

/**
* @param {string} branch
* @param {Options} options
* @returns {{ npmTag: string; prerelease?: string; }}
*/
function getStableBranchTag(branch, { tag }) {
if (!isStableBranch(branch)) {
throw new Error("Expected a stable branch");
}

const latestVersion = getLatestVersion();
const currentVersion = versionToNumber(branch);

// Patching latest version
if (currentVersion === latestVersion) {
return { npmTag: "latest" };
}

// Patching an older stable version
if (currentVersion < latestVersion) {
return { npmTag: "v" + branch };
}

// Publishing a new latest version
if (tag === "latest") {
return { npmTag: tag };
}

// Publishing a release candidate
return { npmTag: "next", prerelease: "rc" };
}

/**
* Verifies the configuration and enables publishing on CI.
* @param {NxConfig} config
* @param {string} currentBranch
* @param {string} tag
* @param {string} [prerelease]
* @returns {asserts config is NxConfig["release"]}
*/
function enablePublishing({ defaultBase, release: config }, currentBranch, tag, prerelease) {
/** @type {string[]} */
const errors = [];

// `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
// of the stable branches.
if (currentBranch !== defaultBase) {
errors.push(`'defaultBase' must be set to '${currentBranch}'`);
}

// Determines whether we need to add "nightly" or "rc" to the version string.
const { currentVersionResolverMetadata, preid } = config.version.generatorOptions;
if (preid !== prerelease) {
errors.push(`'release.version.generatorOptions.preid' must be set to '${prerelease || ""}'`);
}

// What the published version should be tagged as e.g., "latest" or "nightly".
if (currentVersionResolverMetadata.tag !== tag) {
errors.push(`'release.version.generatorOptions.currentVersionResolverMetadata.tag' must be set to '${tag}'`);
}

if (errors.length > 0) {
for (const e of errors) {
console.error("❌", e);
}
throw new Error("Nx Release is not correctly configured for the current branch");
}

enablePublishingOnAzurePipelines();
}

/**
* @param {Options} options
*/
function main(options) {
const branch = getCurrentBranch();
if (!branch) {
throw new Error("Could not get current branch");
}

const config = loadNxConfig();
if (isMainBranch(branch)) {
enablePublishing(config, branch, "nightly", "nightly");
} else if (isStableBranch(branch)) {
const { npmTag, prerelease } = getStableBranchTag(branch, options);
enablePublishing(config, branch, npmTag, prerelease);
}
}

const { values } = util.parseArgs({
args: process.argv.slice(2),
options: {
tag: {
type: "string",
default: "next",
},
},
strict: true,
allowNegative: true,
});

main(values);
11 changes: 3 additions & 8 deletions .ado/scripts/verdaccio.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,9 @@ case ${1-} in

"publish")
checkpoint=$(git rev-parse HEAD)
yarn set-version 1000.0.0-pr
git commit --all --message 'bump' --no-verify
packages=()
for json in $(yarn workspaces list --no-private --json); do
packages+=(--package $(node --print "JSON.parse('$json').name"))
done
npx beachball change --no-fetch --type patch --message 'bump for testing purposes' ${packages[@]}
npx beachball $* --no-push --registry $NPM_REGISTRY --yes --access public --no-generate-changelog
cp nx.test.json nx.json
yarn nx release version 1000.0.0
yarn nx release publish --registry $NPM_REGISTRY
git reset --hard $checkpoint
;;
esac
43 changes: 0 additions & 43 deletions .ado/templates/apple-steps-publish.yml

This file was deleted.

25 changes: 25 additions & 0 deletions .ado/templates/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
parameters:
# If this is a new stable branch, change `publishTag` to `latest` when going stable
publishTag: 'next'

steps:
- script: |
yarn install
displayName: Install npm dependencies

- script: |
node .ado/scripts/prepublish-check.mjs --tag ${{ parameters['publishTag'] }}
displayName: Verify release config

- script: |
yarn nx release --dry-run
displayName: Version and publish packages (dry run)
condition: ${{ ne(variables['publish_react_native_macos'], '1') }}

- script: |
#yarn nx release --yes
yarn nx release --dry-run
env:
NODE_AUTH_TOKEN: $(npmAuthToken)
displayName: Version and publish packages
condition: ${{ eq(variables['publish_react_native_macos'], '1') }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,8 @@ vendor/
.ado/Brewfile.lock.json
.ado/verdaccio/htpasswd
.ado/verdaccio/storage/.verdaccio-db.json

# Nx
.nx/cache
.nx/workspace-data
# macOS]
29 changes: 29 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"defaultBase": "main",
tido64 marked this conversation as resolved.
Show resolved Hide resolved
"targetDefaults": {
"build": {
"dependsOn": ["^build"]
}
},
"release": {
"changelog": {
"projectChangelogs": false,
"workspaceChangelog": false
},
"projects": ["packages/react-native", "packages/virtualized-lists"],
"projectsRelationship": "independent",
"versionPlans": true,
"version": {
"generatorOptions": {
"currentVersionResolver": "registry",
"currentVersionResolverMetadata": {
"tag": "nightly"
},
"fallbackCurrentVersionResolver": "disk",
"preid": "nightly",
"skipLockFileUpdate": true
}
}
}
}
Loading
Loading