Skip to content

Commit

Permalink
chore: set up proper publishing pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 committed Nov 26, 2024
1 parent 6270064 commit 2a1da0e
Show file tree
Hide file tree
Showing 17 changed files with 1,230 additions and 1,429 deletions.
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
195 changes: 195 additions & 0 deletions .ado/scripts/prepublish-check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// @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);
}

/**
* Returns a numerical value for a given version string.
* @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();
}

/**
* Returns the latest published version of `react-native-macos` from npm.
* @returns {number}
*/
function getLatestVersion() {
const { stdout } = spawnSync("npm", ["view", "react-native-macos@latest", "version"]);
return versionToNumber(stdout.toString().trim());
}

/**
* Returns the npm tag and prerelease identifier for the specified branch.
*
* @privateRemarks
* Note that the current implementation treats minor versions as major. If
* upstream ever decides to change the versioning scheme, we will need to make
* changes accordingly.
*
* @param {string} branch
* @param {Options} options
* @returns {{ npmTag: string; prerelease?: string; }}
*/
function getTagForStableBranch(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 } = getTagForStableBranch(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.

26 changes: 26 additions & 0 deletions .ado/templates/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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:
GITHUB_TOKEN: $(githubAuthToken)
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]
32 changes: 32 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"defaultBase": "main",
"targetDefaults": {
"build": {
"dependsOn": ["^build"]
}
},
"release": {
"changelog": {
"projectChangelogs": false,
"workspaceChangelog": {
"file": false,
"createRelease": "github"
}
},
"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

0 comments on commit 2a1da0e

Please sign in to comment.