From 37b0d91fac9118b336cb207a300c3df8a37331fd Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Thu, 4 Nov 2021 20:33:10 -0700 Subject: [PATCH 1/3] Add some version tests, Create parseVersion and test it Differential Revision: D32196238 fbshipit-source-id: 1c3e9be27555cdce02d9498370cffacfcb6eb59c --- scripts/__tests__/version-utils-test.js | 39 +++++++++++++++++++++++++ scripts/bump-oss-version.js | 18 +++++++----- scripts/version-utils.js | 28 ++++++++++++++++++ 3 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 scripts/__tests__/version-utils-test.js create mode 100644 scripts/version-utils.js diff --git a/scripts/__tests__/version-utils-test.js b/scripts/__tests__/version-utils-test.js new file mode 100644 index 00000000000000..ce7f2372345868 --- /dev/null +++ b/scripts/__tests__/version-utils-test.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +const {parseVersion} = require('../version-utils'); + +describe('version-utils', () => { + describe('parseVersion', () => { + it('should throw error if invalid match', () => { + function testInvalidVersion() { + parseVersion(''); + } + expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot( + `"You must pass a correctly formatted version; couldn't parse "`, + ); + }); + + it('should parse pre-release version with .', () => { + const {major, minor, patch, prerelease} = parseVersion('0.66.0-rc.4'); + expect(major).toBe('0'); + expect(minor).toBe('66'); + expect(patch).toBe('0'); + expect(prerelease).toBe('rc.4'); + }); + + it('should parse stable version', () => { + const {major, minor, patch, prerelease} = parseVersion('0.66.0'); + expect(major).toBe('0'); + expect(minor).toBe('66'); + expect(patch).toBe('0'); + expect(prerelease).toBeUndefined(); + }); + }); +}); diff --git a/scripts/bump-oss-version.js b/scripts/bump-oss-version.js index e7887d9fb240b4..fed991cf73b50e 100755 --- a/scripts/bump-oss-version.js +++ b/scripts/bump-oss-version.js @@ -19,6 +19,7 @@ const fs = require('fs'); const {cat, echo, exec, exit, sed} = require('shelljs'); const yargs = require('yargs'); +const {parseVersion} = require('./version-utils'); let argv = yargs .option('r', { @@ -68,15 +69,16 @@ if (!nightlyBuild) { } } -// Generate version files to detect mismatches between JS and native. -let match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/); -if (!match) { - echo( - `You must pass a correctly formatted version; couldn't parse ${version}`, - ); +let major, + minor, + patch, + prerelease = -1; +try { + ({major, minor, patch, prerelease} = parseVersion(version)); +} catch (e) { + echo(e.message); exit(1); } -let [, major, minor, patch, prerelease] = match; fs.writeFileSync( 'ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java', @@ -228,7 +230,7 @@ if (!nightlyBuild) { exec(`git push ${remote} v${version}`); // Tag latest if doing stable release - if (version.indexOf('rc') === -1) { + if (prerelease == null) { exec('git tag -d latest'); exec(`git push ${remote} :latest`); exec('git tag latest'); diff --git a/scripts/version-utils.js b/scripts/version-utils.js new file mode 100644 index 00000000000000..a2d0d6342de11a --- /dev/null +++ b/scripts/version-utils.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +function parseVersion(version) { + const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/); + if (!match) { + throw new Error( + `You must pass a correctly formatted version; couldn't parse ${version}`, + ); + } + const [, major, minor, patch, prerelease] = match; + return { + major, + minor, + patch, + prerelease, + }; +} + +module.exports = { + parseVersion, +}; From 072c7a8754ecb6ac6ee6c398399f23d663c544c1 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Thu, 4 Nov 2021 20:33:10 -0700 Subject: [PATCH 2/3] Update publish-npm to use same parseVersion Differential Revision: D32196237 fbshipit-source-id: 7ffedaedffe06f6f14659d262bb3b096accc8305 --- scripts/__tests__/version-utils-test.js | 55 ++++++++++- scripts/publish-npm.js | 123 +++++++++--------------- scripts/version-utils.js | 9 +- 3 files changed, 105 insertions(+), 82 deletions(-) diff --git a/scripts/__tests__/version-utils-test.js b/scripts/__tests__/version-utils-test.js index ce7f2372345868..cc5c8d54d323ec 100644 --- a/scripts/__tests__/version-utils-test.js +++ b/scripts/__tests__/version-utils-test.js @@ -21,19 +21,70 @@ describe('version-utils', () => { }); it('should parse pre-release version with .', () => { - const {major, minor, patch, prerelease} = parseVersion('0.66.0-rc.4'); + const {version, major, minor, patch, prerelease} = + parseVersion('0.66.0-rc.4'); + expect(version).toBe('0.66.0-rc.4'); expect(major).toBe('0'); expect(minor).toBe('66'); expect(patch).toBe('0'); expect(prerelease).toBe('rc.4'); }); + it('should parse pre-release version with -', () => { + const {version, major, minor, patch, prerelease} = + parseVersion('0.66.0-rc-4'); + expect(version).toBe('0.66.0-rc-4'); + expect(major).toBe('0'); + expect(minor).toBe('66'); + expect(patch).toBe('0'); + expect(prerelease).toBe('rc-4'); + }); + it('should parse stable version', () => { - const {major, minor, patch, prerelease} = parseVersion('0.66.0'); + const {version, major, minor, patch, prerelease} = parseVersion('0.66.0'); + expect(version).toBe('0.66.0'); + expect(major).toBe('0'); + expect(minor).toBe('66'); + expect(patch).toBe('0'); + expect(prerelease).toBeUndefined(); + }); + it('should parse pre-release version from tag', () => { + const {version, major, minor, patch, prerelease} = + parseVersion('v0.66.1-rc.4'); + expect(version).toBe('0.66.1-rc.4'); + expect(major).toBe('0'); + expect(minor).toBe('66'); + expect(patch).toBe('1'); + expect(prerelease).toBe('rc.4'); + }); + + it('should parse stable version from tag', () => { + const {version, major, minor, patch, prerelease} = + parseVersion('v0.66.0'); + expect(version).toBe('0.66.0'); expect(major).toBe('0'); expect(minor).toBe('66'); expect(patch).toBe('0'); expect(prerelease).toBeUndefined(); }); + + it('should parse nightly fake version', () => { + const {version, major, minor, patch, prerelease} = parseVersion('0.0.0'); + expect(version).toBe('0.0.0'); + expect(major).toBe('0'); + expect(minor).toBe('0'); + expect(patch).toBe('0'); + expect(prerelease).toBeUndefined(); + }); + + it('should parse dryrun fake version', () => { + const {version, major, minor, patch, prerelease} = + parseVersion('1000.0.0'); + expect(version).toBe('1000.0.0'); + expect(major).toBe('1000'); + expect(minor).toBe('0'); + expect(patch).toBe('0'); + expect(prerelease).toBeUndefined(); + }); }); }); diff --git a/scripts/publish-npm.js b/scripts/publish-npm.js index 86e83fd59e71c0..825e08928217ea 100644 --- a/scripts/publish-npm.js +++ b/scripts/publish-npm.js @@ -10,7 +10,7 @@ 'use strict'; /** - * This script publishes a new version of react-native to NPM. + * This script prepares a release version of react-native and may publish to NPM. * It is supposed to run in CI environment, not on a developer's machine. * * To make it easier for developers it uses some logic to identify with which @@ -49,11 +49,14 @@ * If tag v0.XY.Z is present on the commit then publish to npm with version 0.XY.Z and no tag (npm will consider it latest) */ -/*eslint-disable no-undef */ -require('shelljs/global'); +const {exec, echo, exit, test} = require('shelljs'); const yargs = require('yargs'); +const {parseVersion} = require('./version-utils'); -let argv = yargs +const buildTag = process.env.CIRCLE_TAG; +const otp = process.env.NPM_CONFIG_OTP; + +const argv = yargs .option('n', { alias: 'nightly', type: 'boolean', @@ -64,75 +67,52 @@ let argv = yargs type: 'boolean', default: false, }).argv; - const nightlyBuild = argv.nightly; const dryRunBuild = argv.dryRun; -const buildFromMain = nightlyBuild || dryRunBuild; -const buildTag = process.env.CIRCLE_TAG; -const otp = process.env.NPM_CONFIG_OTP; - -let branchVersion = 0; -if (buildFromMain) { - branchVersion = 0; -} else { - if (!buildTag) { - echo('Error: We publish only from git tags'); - exit(1); - } - - let match = buildTag.match(/^v(\d+\.\d+)\.\d+(?:-.+)?$/); - if (!match) { - echo('Error: We publish only from release version git tags'); - exit(1); - } - [, branchVersion] = match; -} -// 0.33 // 34c034298dc9cad5a4553964a5a324450fda0385 -const currentCommit = exec('git rev-parse HEAD', {silent: true}).stdout.trim(); - -// Note: We rely on tagsWithVersion to be alphabetically sorted -// [34c034298dc9cad5a4553964a5a324450fda0385, refs/heads/0.33-stable, refs/tags/latest, refs/tags/v0.33.1, refs/tags/v0.34.1-rc] -const tagsWithVersion = exec(`git ls-remote origin | grep ${currentCommit}`, { +const currentCommit = exec('git rev-parse HEAD', { silent: true, -}) - .stdout.split(/\s/) - // ['refs/tags/v0.33.0', 'refs/tags/v0.33.0-rc', 'refs/tags/v0.33.0-rc1', 'refs/tags/v0.33.0-rc2', 'refs/tags/v0.34.0'] - .filter( - version => - !!version && version.indexOf(`refs/tags/v${branchVersion}`) === 0, - ) - // ['refs/tags/v0.33.0', 'refs/tags/v0.33.0-rc', 'refs/tags/v0.33.0-rc1', 'refs/tags/v0.33.0-rc2'] - .filter(version => version.indexOf(branchVersion) !== -1) - // ['0.33.0', '0.33.0-rc', '0.33.0-rc1', '0.33.0-rc2'] - .map(version => version.slice('refs/tags/v'.length)); - -if (!buildFromMain && tagsWithVersion.length === 0) { - echo( - 'Error: Cannot find version tag in current commit. To deploy to NPM you must add tag v0.XY.Z[-rc] to your commit', - ); +}).stdout.trim(); +const shortCommit = currentCommit.slice(0, 9); + +const rawVersion = + // 0.0.0 triggers issues with cocoapods for codegen when building template project. + dryRunBuild + ? '1000.0.0' + : // For nightly we continue to use 0.0.0 for clarity for npm + nightlyBuild + ? '0.0.0' + : // For pre-release and stable releases, we use the git tag of the version we're releasing (set in bump-oss-version) + buildTag; + +let version, + prerelease = null; +try { + ({version, prerelease} = parseVersion(rawVersion)); +} catch (e) { + echo(e.message); exit(1); } let releaseVersion; +if (dryRunBuild) { + releaseVersion = `${version}-${shortCommit}`; +} else if (nightlyBuild) { + // 2021-09-28T05:38:40.669Z -> 20210928-0538 + const dateIdentifier = new Date() + .toISOString() + .slice(0, -8) + .replace(/[-:]/g, '') + .replace(/[T]/g, '-'); + releaseVersion = `${version}-${dateIdentifier}-${shortCommit}`; +} else { + releaseVersion = version; +} -if (buildFromMain) { - if (nightlyBuild) { - releaseVersion = '0.0.0-'; - // 2021-09-28T05:38:40.669Z -> 20210928-0538 - releaseVersion += new Date() - .toISOString() - .slice(0, -8) - .replace(/[-:]/g, '') - .replace(/[T]/g, '-'); - } else { - // 0.0.0 triggers issues with cocoapods for codegen for building template project. - releaseVersion = '1000.0.0'; - } - - releaseVersion += `-${currentCommit.slice(0, 9)}`; - // Bump version number in various files (package.json, gradle.properties etc) +// Bump version number in various files (package.json, gradle.properties etc) +// For stable, pre-release releases, we manually call bump-oss-version on release branch +if (nightlyBuild || dryRunBuild) { if ( exec( `node scripts/bump-oss-version.js --nightly --to-version ${releaseVersion}`, @@ -141,14 +121,6 @@ if (buildFromMain) { echo('Failed to bump version number'); exit(1); } -} else if (tagsWithVersion[0].indexOf('-rc') === -1) { - // if first tag on this commit is non -rc then we are making a stable release - // '0.33.0' - releaseVersion = tagsWithVersion[0]; -} else { - // otherwise pick last -rc tag, indicates latest rc version due to alpha-sort - // '0.33.0-rc2' - releaseVersion = tagsWithVersion[tagsWithVersion.length - 1]; } // -------- Generating Android Artifacts with JavaDoc @@ -183,12 +155,12 @@ if (dryRunBuild) { exit(0); } -// if version contains -rc, tag as prerelease +// Set the right tag for nightly and prerelease builds const tagFlag = nightlyBuild ? '--tag nightly' - : releaseVersion.indexOf('-rc') === -1 - ? '' - : '--tag next'; + : prerelease != null + ? '--tag next' + : ''; // use otp from envvars if available const otpFlag = otp ? `--otp ${otp}` : ''; @@ -200,4 +172,3 @@ if (exec(`npm publish ${tagFlag} ${otpFlag}`).code) { echo(`Published to npm ${releaseVersion}`); exit(0); } -/*eslint-enable no-undef */ diff --git a/scripts/version-utils.js b/scripts/version-utils.js index a2d0d6342de11a..2be7394398a9e7 100644 --- a/scripts/version-utils.js +++ b/scripts/version-utils.js @@ -7,15 +7,16 @@ * @format */ -function parseVersion(version) { - const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/); +function parseVersion(versionStr) { + const match = versionStr.match(/^v?((\d+)\.(\d+)\.(\d+)(?:-(.+))?)$/); if (!match) { throw new Error( - `You must pass a correctly formatted version; couldn't parse ${version}`, + `You must pass a correctly formatted version; couldn't parse ${versionStr}`, ); } - const [, major, minor, patch, prerelease] = match; + const [, version, major, minor, patch, prerelease] = match; return { + version, major, minor, patch, From 95ebb0d724fc6cb0485b673b517e899bced8afb6 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Thu, 4 Nov 2021 20:33:23 -0700 Subject: [PATCH 3/3] Fix npm latest tag issue when releasing patches Summary: Changelog: [Internal] Fix npm `latest` tag issue that occurs when we release a patch on an older minor version Context: * There are two types of tags, git and npm, they are unrelated. * When we publish a stable release, we set the git tag `latest`. This logic is faulty when we release a patch to an older version. * When publishing a package to npm, if you don't provide an explicit tag, the `latest` tag will be applied -- at least that's how I've understood the [docs here](https://docs.npmjs.com/cli/v7/commands/npm-dist-tag#description). This again is faulty logic when we release a patch to an older version. * npm and git's `latest` tag should always point to our most recent stable version This change: * Introduces a `--latest` flag for `bump-oss-script` that will indicate that the release we're running (either a stable or pre-release) should really be considered "latest" * If the version is not a pre-release and the `--latest` flag is set, we will set the git `latest` tag * Later, in the circleCI job that we use to publish the npm package, we will see if the current commit is git-tagged as `latest`. If it is, then we'll explicitly tell npm to use `latest` tag but most importantly, if it's not, we'll set a tag of the form `{major}.{minor}-stable`. * This type of tag (ex. `0.66-stable`) is new and the intention is that it will always point to latest of that minor version. Differential Revision: D32196239 fbshipit-source-id: e162f4d56d93f584f6f891bc451985a61690825b --- scripts/bump-oss-version.js | 10 ++++++++-- scripts/publish-npm.js | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/scripts/bump-oss-version.js b/scripts/bump-oss-version.js index fed991cf73b50e..553109e7a3c382 100755 --- a/scripts/bump-oss-version.js +++ b/scripts/bump-oss-version.js @@ -34,6 +34,11 @@ let argv = yargs .option('v', { alias: 'to-version', type: 'string', + }) + .option('l', { + alias: 'latest', + type: 'boolean', + default: false, }).argv; const nightlyBuild = argv.nightly; @@ -229,8 +234,9 @@ if (!nightlyBuild) { let remote = argv.remote; exec(`git push ${remote} v${version}`); - // Tag latest if doing stable release - if (prerelease == null) { + // Tag latest if doing stable release. + // This will also tag npm release as `latest` + if (prerelease == null && argv.latest) { exec('git tag -d latest'); exec(`git push ${remote} :latest`); exec('git tag latest'); diff --git a/scripts/publish-npm.js b/scripts/publish-npm.js index 825e08928217ea..d965724c7b1689 100644 --- a/scripts/publish-npm.js +++ b/scripts/publish-npm.js @@ -87,9 +87,11 @@ const rawVersion = buildTag; let version, + major, + minor, prerelease = null; try { - ({version, prerelease} = parseVersion(rawVersion)); + ({version, major, minor, prerelease} = parseVersion(rawVersion)); } catch (e) { echo(e.message); exit(1); @@ -155,12 +157,24 @@ if (dryRunBuild) { exit(0); } +// Running to see if this commit has been git tagged as `latest` +const latestCommit = exec("git rev-list -n 1 'latest'", { + silent: true, +}).stdout.replace('\n', ''); +const isLatest = currentCommit === latestCommit; + +const releaseBranch = `${major}.${minor}-stable`; + // Set the right tag for nightly and prerelease builds +// If a release is not git-tagged as `latest` we use `releaseBranch` to prevent +// npm from overriding the current `latest` version tag, which it will do if no tag is set. const tagFlag = nightlyBuild ? '--tag nightly' : prerelease != null ? '--tag next' - : ''; + : isLatest + ? '--tag latest' + : `--tag ${releaseBranch}`; // use otp from envvars if available const otpFlag = otp ? `--otp ${otp}` : '';