diff --git a/scripts/__tests__/version-utils-test.js b/scripts/__tests__/version-utils-test.js new file mode 100644 index 00000000000000..cc5c8d54d323ec --- /dev/null +++ b/scripts/__tests__/version-utils-test.js @@ -0,0 +1,90 @@ +/** + * 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 {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 {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/bump-oss-version.js b/scripts/bump-oss-version.js index e7887d9fb240b4..553109e7a3c382 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', { @@ -33,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; @@ -68,15 +74,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', @@ -227,8 +234,9 @@ if (!nightlyBuild) { let remote = argv.remote; exec(`git push ${remote} v${version}`); - // Tag latest if doing stable release - if (version.indexOf('rc') === -1) { + // 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 86e83fd59e71c0..d965724c7b1689 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,54 @@ 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, + major, + minor, + prerelease = null; +try { + ({version, major, minor, 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 +123,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 +157,24 @@ if (dryRunBuild) { exit(0); } -// if version contains -rc, tag as prerelease +// 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' - : releaseVersion.indexOf('-rc') === -1 - ? '' - : '--tag next'; + : prerelease != null + ? '--tag next' + : isLatest + ? '--tag latest' + : `--tag ${releaseBranch}`; // use otp from envvars if available const otpFlag = otp ? `--otp ${otp}` : ''; @@ -200,4 +186,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 new file mode 100644 index 00000000000000..2be7394398a9e7 --- /dev/null +++ b/scripts/version-utils.js @@ -0,0 +1,29 @@ +/** + * 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(versionStr) { + const match = versionStr.match(/^v?((\d+)\.(\d+)\.(\d+)(?:-(.+))?)$/); + if (!match) { + throw new Error( + `You must pass a correctly formatted version; couldn't parse ${versionStr}`, + ); + } + const [, version, major, minor, patch, prerelease] = match; + return { + version, + major, + minor, + patch, + prerelease, + }; +} + +module.exports = { + parseVersion, +};