Skip to content

Commit

Permalink
Improve version checks to avoid mistakes in the versioning (#35296)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #35296

This change adds some version checks and enforces that every version matches some specific format based on the build type we are trying to run.

## Changelog
[General][Changed] - Improve version checks

Reviewed By: cortinico

Differential Revision: D41161756

fbshipit-source-id: fcae8101e57dbef79203881961b61d8663285fdf
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Nov 21, 2022
1 parent 25132c8 commit 7eb2c0a
Show file tree
Hide file tree
Showing 8 changed files with 444 additions and 56 deletions.
6 changes: 4 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1363,7 +1363,9 @@ jobs:
- run:
name: "Set new react-native version and commit changes"
command: |
node ./scripts/prepare-package-for-release.js -v << parameters.version >> -l << parameters.latest >> --dry-run << parameters.dryrun >>
PACKAGE_JSON_VERSION=$(npm pgk get version | tr -d '"')
VERSION=${<< parameters.version >>:-"$PACKAGE_JSON_VERSION"}
node ./scripts/prepare-package-for-release.js -v "$VERSION" -l << parameters.latest >> --dry-run << parameters.dryrun >>
build_npm_package:
parameters:
Expand Down Expand Up @@ -1643,7 +1645,7 @@ workflows:
jobs:
- prepare_package_for_release:
name: prepare_package_for_release
version: 'v1000.0.1'
version: ''
latest : false
dryrun: true
- prepare_hermes_workspace:
Expand Down
275 changes: 253 additions & 22 deletions scripts/__tests__/version-utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
* @format
*/

const {parseVersion, isReleaseBranch} = require('../version-utils');
const {
parseVersion,
isReleaseBranch,
validateBuildType,
} = require('../version-utils');

let execResult = null;
jest.mock('shelljs', () => ({
Expand Down Expand Up @@ -38,37 +42,86 @@ describe('version-utils', () => {
});

describe('parseVersion', () => {
it('should throw error if invalid match', () => {
it('should throw error if buildType is undefined', () => {
function testInvalidVersion() {
parseVersion('<invalid version>');
parseVersion('v0.10.5');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"Unsupported build type: undefined"`,
);
});

it('should throw error if buildType is not `release`, `dry-run` or `nightly`', () => {
function testInvalidVersion() {
parseVersion('v0.10.5', 'invalid_build_type');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"Unsupported build type: invalid_build_type"`,
);
});
it('should throw error if invalid match with release', () => {
function testInvalidVersion() {
parseVersion('<invalid version>', 'release');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"You must pass a correctly formatted version; couldn't parse <invalid version>"`,
);
});
it('should throw error if invalid match with dry-run', () => {
function testInvalidVersion() {
parseVersion('<invalid version>', 'dry-run');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"You must pass a correctly formatted version; couldn't parse <invalid version>"`,
);
});
it('should throw error if invalid match with nightly', () => {
function testInvalidVersion() {
parseVersion('<invalid version>', 'nightly');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"You must pass a correctly formatted version; couldn't parse <invalid version>"`,
);
});

it('should parse pre-release version with .', () => {
const {version, major, minor, patch, prerelease} =
parseVersion('0.66.0-rc.4');
it('should parse pre-release version with release and `.`', () => {
const {version, major, minor, patch, prerelease} = parseVersion(
'0.66.0-rc.4',
'release',
);
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');
it('should parse pre-release version with release and `-`', () => {
const {version, major, minor, patch, prerelease} = parseVersion(
'0.66.0-rc-4',
'release',
);
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 reject pre-release version with random prerelease pattern', () => {
function testInvalidVersion() {
parseVersion('0.66.0-something_invalid', 'release');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"Version 0.66.0-something_invalid is not valid for Release"`,
);
});

it('should parse stable version', () => {
const {version, major, minor, patch, prerelease} = parseVersion('0.66.0');
const {version, major, minor, patch, prerelease} = parseVersion(
'0.66.0',
'release',
);
expect(version).toBe('0.66.0');
expect(major).toBe('0');
expect(minor).toBe('66');
Expand All @@ -77,42 +130,220 @@ describe('version-utils', () => {
});

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');
const {version, major, minor, patch, prerelease} = parseVersion(
'v0.66.0-rc.4',
'release',
);
expect(version).toBe('0.66.0-rc.4');
expect(major).toBe('0');
expect(minor).toBe('66');
expect(patch).toBe('1');
expect(patch).toBe('0');
expect(prerelease).toBe('rc.4');
});

it('should reject pre-release version with patch != 0', () => {
function testInvalidVersion() {
parseVersion('0.66.3-rc.4', 'release');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"Version 0.66.3-rc.4 is not valid for Release"`,
);
});

it('should reject pre-release version from tag with random prerelease pattern', () => {
function testInvalidVersion() {
parseVersion('v0.66.0-something_invalid', 'release');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"Version 0.66.0-something_invalid is not valid for Release"`,
);
});

it('should parse stable version from tag', () => {
const {version, major, minor, patch, prerelease} =
parseVersion('v0.66.0');
const {version, major, minor, patch, prerelease} = parseVersion(
'v0.66.0',
'release',
);
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');
it('should reject nightly with no prerelease', () => {
// this should fail
function testInvalidFunction() {
parseVersion('0.0.0', 'nightly');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Version 0.0.0 is not valid for nightlies"`,
);
});

it('should reject nightly with prerelease but wrong version numbers', () => {
// this should fail
function testInvalidFunction() {
parseVersion('1.2.3-pre-release', 'nightly');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Version 1.2.3-pre-release is not valid for nightlies"`,
);
});

it('should parse nightly with 0.0.0 and a prerelease part', () => {
// this should fail
const {version, major, minor, patch, prerelease} = parseVersion(
'0.0.0-pre-release',
'nightly',
);

expect(version).toBe('0.0.0-pre-release');
expect(major).toBe('0');
expect(minor).toBe('0');
expect(patch).toBe('0');
expect(prerelease).toBe('pre-release');
});
it('should parse dryrun with release version', () => {
const {version, major, minor, patch, prerelease} = parseVersion(
'0.7.3',
'dry-run',
);
expect(version).toBe('0.7.3');
expect(major).toBe('0');
expect(minor).toBe('7');
expect(patch).toBe('3');
expect(prerelease).toBeUndefined();
});

it('should parse dryrun fake version', () => {
const {version, major, minor, patch, prerelease} =
parseVersion('1000.0.0');
it('should parse dryrun with prerelease . version', () => {
const {version, major, minor, patch, prerelease} = parseVersion(
'0.20.0-rc.0',
'dry-run',
);
expect(version).toBe('0.20.0-rc.0');
expect(major).toBe('0');
expect(minor).toBe('20');
expect(patch).toBe('0');
expect(prerelease).toBe('rc.0');
});

it('should reject dryrun with prerelease . version with patch different from 0', () => {
function testInvalidFunction() {
parseVersion('0.20.3-rc.0', 'dry-run');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Version 0.20.3-rc.0 is not valid for dry-runs"`,
);
});

it('should parse dryrun with prerelease - version', () => {
const {version, major, minor, patch, prerelease} = parseVersion(
'0.20.0-rc-0',
'dry-run',
);
expect(version).toBe('0.20.0-rc-0');
expect(major).toBe('0');
expect(minor).toBe('20');
expect(patch).toBe('0');
expect(prerelease).toBe('rc-0');
});

it('should reject dryrun with prerelease - version with patch different from 0', () => {
function testInvalidFunction() {
parseVersion('0.20.3-rc-0', 'dry-run');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Version 0.20.3-rc-0 is not valid for dry-runs"`,
);
});

it('should parse dryrun with main version', () => {
const {version, major, minor, patch, prerelease} = parseVersion(
'1000.0.0',
'dry-run',
);
expect(version).toBe('1000.0.0');
expect(major).toBe('1000');
expect(minor).toBe('0');
expect(patch).toBe('0');
expect(prerelease).toBeUndefined();
});

it('should fail for dryrun with v1000.0.1 version', () => {
function testInvalidFunction() {
parseVersion('v1000.0.1', 'dry-run');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Version 1000.0.1 is not valid for dry-runs"`,
);
});
it('should parse dryrun with nightly version', () => {
const {version, major, minor, patch, prerelease} = parseVersion(
'0.0.0-something-else',
'dry-run',
);
expect(version).toBe('0.0.0-something-else');
expect(major).toBe('0');
expect(minor).toBe('0');
expect(patch).toBe('0');
expect(prerelease).toBe('something-else');
});

it('should reject dryrun invalid values', () => {
function testInvalidFunction() {
parseVersion('1000.0.4', 'dry-run');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Version 1000.0.4 is not valid for dry-runs"`,
);
});

it('should reject dryrun for invalid prerelease', () => {
function testInvalidFunction() {
parseVersion('0.6.4-something-else', 'dry-run');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Version 0.6.4-something-else is not valid for dry-runs"`,
);
});

it('should reject dryrun for nightlies with invalid prerelease', () => {
function testInvalidFunction() {
parseVersion('0.0.0', 'dry-run');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Version 0.0.0 is not valid for dry-runs"`,
);
});
});

describe('Validate version', () => {
it('Throw error if the buildType is unknown', () => {
function testInvalidFunction() {
validateBuildType('wrong_build');
}
expect(testInvalidFunction).toThrowErrorMatchingInlineSnapshot(
`"Unsupported build type: wrong_build"`,
);
});
it('Does not throw if the buildType is release', () => {
function testValidCall() {
validateBuildType('release');
}
expect(testValidCall).not.toThrowError();
});
it('Does not throw if the buildType is nightly', () => {
function testValidCall() {
validateBuildType('nightly');
}
expect(testValidCall).not.toThrowError();
});
it('Does not throw if the buildType is dry-run', () => {
function testValidCall() {
validateBuildType('dry-run');
}
expect(testValidCall).not.toThrowError();
});
});
});
2 changes: 1 addition & 1 deletion scripts/bump-oss-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async function main() {
}

let latest = false;
const {version, prerelease} = parseVersion(releaseVersion);
const {version, prerelease} = parseVersion(releaseVersion, 'release');
if (!prerelease) {
const {setLatest} = await inquirer.prompt({
type: 'confirm',
Expand Down
14 changes: 12 additions & 2 deletions scripts/prepare-package-for-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,23 @@ if (branch && !isReleaseBranch(branch) && !isDryRun) {
exit(1);
}

const {version} = parseVersion(releaseVersion);
const buildType = isDryRun
? 'dry-run'
: isReleaseBranch(branch)
? 'release'
: 'nightly';

const {version} = parseVersion(releaseVersion, buildType);
if (version == null) {
console.error(`Invalid version provided: ${releaseVersion}`);
exit(1);
}

if (exec(`node scripts/set-rn-version.js --to-version ${version}`).code) {
if (
exec(
`node scripts/set-rn-version.js --to-version ${version} --build-type ${buildType}`,
).code
) {
echo(`Failed to set React Native version to ${version}`);
exit(1);
}
Expand Down
Loading

0 comments on commit 7eb2c0a

Please sign in to comment.