Refactor set-rn-version and add tests (#37291)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37291 Changelog: [Internal] Add tests for `rn-set-version` script and refactor for clarity Reviewed By: NickGerleman Differential Revision: D45248915 fbshipit-source-id: f6b8566d553d0954647ba81c55767fab6a2caf1c
This commit is contained in:
Родитель
2c4e809beb
Коммит
b23cf101f7
|
@ -0,0 +1,182 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and 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 execMock = jest.fn();
|
||||
const echoMock = jest.fn();
|
||||
const exitMock = jest.fn();
|
||||
const catMock = jest.fn();
|
||||
const sedMock = jest.fn();
|
||||
const writeFileSyncMock = jest.fn();
|
||||
|
||||
jest
|
||||
.mock('shelljs', () => ({
|
||||
exec: execMock,
|
||||
echo: echoMock,
|
||||
exit: exitMock,
|
||||
cat: catMock,
|
||||
sed: sedMock,
|
||||
}))
|
||||
.mock('./../scm-utils', () => ({
|
||||
saveFiles: jest.fn(),
|
||||
}))
|
||||
.mock('path', () => ({
|
||||
join: () => '../packages/react-native',
|
||||
}))
|
||||
.mock('fs', () => ({
|
||||
writeFileSync: writeFileSyncMock,
|
||||
mkdtempSync: () => './rn-set-version/',
|
||||
}))
|
||||
.mock('os');
|
||||
|
||||
const setReactNativeVersion = require('../set-rn-version');
|
||||
|
||||
describe('set-rn-version', () => {
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should set nightly version', () => {
|
||||
catMock.mockImplementation(path => {
|
||||
if (path === 'packages/react-native/package.json') {
|
||||
return '{"name": "myPackage", "version": 2}';
|
||||
} else if (
|
||||
path === 'scripts/versiontemplates/ReactNativeVersion.java.template' ||
|
||||
path === 'scripts/versiontemplates/RCTVersion.m.template' ||
|
||||
path === 'scripts/versiontemplates/ReactNativeVersion.h.template' ||
|
||||
path === 'scripts/versiontemplates/ReactNativeVersion.js.template'
|
||||
) {
|
||||
return '{major: ${major}, minor: ${minor}, patch: ${patch}, prerelease: ${prerelease}}';
|
||||
} else {
|
||||
throw new Error(`Invalid path passed for package dir. Path: ${path}`);
|
||||
}
|
||||
});
|
||||
|
||||
execMock
|
||||
.mockReturnValueOnce({code: 0})
|
||||
.mockReturnValueOnce({stdout: 'line1\nline2\nline3\n'});
|
||||
sedMock.mockReturnValueOnce({code: 0});
|
||||
|
||||
const version = '0.81.0-nightly-29282302-abcd1234';
|
||||
setReactNativeVersion(version, 'nightly');
|
||||
|
||||
expect(sedMock).toHaveBeenCalledWith(
|
||||
'-i',
|
||||
/^VERSION_NAME=.*/,
|
||||
`VERSION_NAME=${version}`,
|
||||
'packages/react-native/ReactAndroid/gradle.properties',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls.length).toBe(5);
|
||||
expect(writeFileSyncMock.mock.calls[0][0]).toBe(
|
||||
'packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[0][1]).toBe(
|
||||
'{major: 0, minor: 81, patch: 0, prerelease: "nightly-29282302-abcd1234"}',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[1][0]).toBe(
|
||||
'packages/react-native/React/Base/RCTVersion.m',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[2][0]).toBe(
|
||||
'packages/react-native/ReactCommon/cxxreact/ReactNativeVersion.h',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[3][0]).toBe(
|
||||
'packages/react-native/Libraries/Core/ReactNativeVersion.js',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[4][0]).toBe(
|
||||
'packages/react-native/package.json',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[4][1]).toBe(
|
||||
`{\n "name": "myPackage",\n "version": "${version}"\n}`,
|
||||
);
|
||||
|
||||
expect(exitMock.mock.calls[0][0]).toBe(0);
|
||||
expect(execMock.mock.calls[0][0]).toBe(
|
||||
`node scripts/set-rn-template-version.js ${version}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should set release version', () => {
|
||||
catMock.mockImplementation(path => {
|
||||
if (path === 'packages/react-native/package.json') {
|
||||
return '{"name": "myPackage", "version": 2}';
|
||||
}
|
||||
return 'exports.version = {major: ${major}, minor: ${minor}, patch: ${patch}, prerelease: ${prerelease}}';
|
||||
});
|
||||
|
||||
execMock
|
||||
.mockReturnValueOnce({code: 0})
|
||||
.mockReturnValueOnce({stdout: 'line1\nline2\nline3\n'});
|
||||
sedMock.mockReturnValueOnce({code: 0});
|
||||
|
||||
const version = '0.81.0';
|
||||
setReactNativeVersion(version, 'release');
|
||||
|
||||
expect(sedMock).toHaveBeenCalledWith(
|
||||
'-i',
|
||||
/^VERSION_NAME=.*/,
|
||||
`VERSION_NAME=${version}`,
|
||||
'packages/react-native/ReactAndroid/gradle.properties',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls.length).toBe(5);
|
||||
expect(writeFileSyncMock.mock.calls[0][0]).toBe(
|
||||
'packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[0][1]).toBe(
|
||||
'exports.version = {major: 0, minor: 81, patch: 0, prerelease: null}',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[4][0]).toBe(
|
||||
'packages/react-native/package.json',
|
||||
);
|
||||
expect(writeFileSyncMock.mock.calls[4][1]).toBe(
|
||||
`{\n "name": "myPackage",\n "version": "${version}"\n}`,
|
||||
);
|
||||
|
||||
expect(exitMock.mock.calls[0][0]).toBe(0);
|
||||
expect(execMock.mock.calls[0][0]).toBe(
|
||||
`node scripts/set-rn-template-version.js ${version}`,
|
||||
);
|
||||
expect(execMock.mock.calls[1][0]).toBe(
|
||||
`diff -r ./rn-set-version/ . | grep '^[>]' | grep -c ${version} `,
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail validation', () => {
|
||||
catMock.mockReturnValue('{}');
|
||||
|
||||
execMock
|
||||
.mockReturnValueOnce({code: 0})
|
||||
.mockReturnValueOnce({stdout: 'line1\nline2\n'});
|
||||
sedMock.mockReturnValueOnce({code: 0});
|
||||
const filesToValidate = [
|
||||
'packages/react-native/package.json',
|
||||
'packages/react-native/ReactAndroid/gradle.properties',
|
||||
'packages/react-native/template/package.json',
|
||||
];
|
||||
|
||||
const version = '0.81.0';
|
||||
setReactNativeVersion(version, 'release');
|
||||
|
||||
expect(exitMock).toHaveBeenCalledWith(0);
|
||||
expect(echoMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'The tmp versioning folder is ./rn-set-version/',
|
||||
);
|
||||
|
||||
expect(echoMock).toHaveBeenNthCalledWith(2, 'WARNING:');
|
||||
|
||||
expect(echoMock.mock.calls[2][0]).toBe(
|
||||
`Failed to update all the files: [${filesToValidate.join(
|
||||
', ',
|
||||
)}] must have versions in them`,
|
||||
);
|
||||
expect(echoMock.mock.calls[3][0]).toBe(
|
||||
`These files already had version ${version} set.`,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -9,13 +9,6 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This script updates relevant React Native files with supplied version:
|
||||
* * Prepares a package.json suitable for package consumption
|
||||
* * Updates package.json for template project
|
||||
* * Updates the version in gradle files and makes sure they are consistent between each other
|
||||
* * Creates a gemfile
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
@ -24,155 +17,160 @@ const yargs = require('yargs');
|
|||
const {parseVersion, validateBuildType} = require('./version-utils');
|
||||
const {saveFiles} = require('./scm-utils');
|
||||
|
||||
let argv = yargs
|
||||
.option('v', {
|
||||
alias: 'to-version',
|
||||
type: 'string',
|
||||
required: true,
|
||||
})
|
||||
.option('b', {
|
||||
alias: 'build-type',
|
||||
type: 'string',
|
||||
required: true,
|
||||
}).argv;
|
||||
/**
|
||||
* This script updates relevant React Native files with supplied version:
|
||||
* * Prepares a package.json suitable for package consumption
|
||||
* * Updates package.json for template project
|
||||
* * Updates the version in gradle files and makes sure they are consistent between each other
|
||||
* * Creates a gemfile
|
||||
*/
|
||||
if (require.main === module) {
|
||||
let argv = yargs
|
||||
.option('v', {
|
||||
alias: 'to-version',
|
||||
type: 'string',
|
||||
required: true,
|
||||
})
|
||||
.option('b', {
|
||||
alias: 'build-type',
|
||||
type: 'string',
|
||||
required: true,
|
||||
}).argv;
|
||||
|
||||
const buildType = argv.buildType;
|
||||
const version = argv.toVersion;
|
||||
|
||||
try {
|
||||
validateBuildType(buildType);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
setReactNativeVersion(argv.toVersion, argv.buildType);
|
||||
}
|
||||
|
||||
let major,
|
||||
minor,
|
||||
patch,
|
||||
prerelease = -1;
|
||||
try {
|
||||
({major, minor, patch, prerelease} = parseVersion(version, buildType));
|
||||
} catch (e) {
|
||||
throw e;
|
||||
function setSource({major, minor, patch, prerelease}) {
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java',
|
||||
cat('scripts/versiontemplates/ReactNativeVersion.java.template')
|
||||
.replace('${major}', major)
|
||||
.replace('${minor}', minor)
|
||||
.replace('${patch}', patch)
|
||||
.replace(
|
||||
'${prerelease}',
|
||||
prerelease !== undefined ? `"${prerelease}"` : 'null',
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/React/Base/RCTVersion.m',
|
||||
cat('scripts/versiontemplates/RCTVersion.m.template')
|
||||
.replace('${major}', `@(${major})`)
|
||||
.replace('${minor}', `@(${minor})`)
|
||||
.replace('${patch}', `@(${patch})`)
|
||||
.replace(
|
||||
'${prerelease}',
|
||||
prerelease !== undefined ? `@"${prerelease}"` : '[NSNull null]',
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/ReactCommon/cxxreact/ReactNativeVersion.h',
|
||||
cat('scripts/versiontemplates/ReactNativeVersion.h.template')
|
||||
.replace('${major}', major)
|
||||
.replace('${minor}', minor)
|
||||
.replace('${patch}', patch)
|
||||
.replace(
|
||||
'${prerelease}',
|
||||
prerelease !== undefined ? `"${prerelease}"` : '""',
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/Libraries/Core/ReactNativeVersion.js',
|
||||
cat('scripts/versiontemplates/ReactNativeVersion.js.template')
|
||||
.replace('${major}', major)
|
||||
.replace('${minor}', minor)
|
||||
.replace('${patch}', patch)
|
||||
.replace(
|
||||
'${prerelease}',
|
||||
prerelease !== undefined ? `'${prerelease}'` : 'null',
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
}
|
||||
|
||||
const tmpVersioningFolder = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'rn-set-version'),
|
||||
);
|
||||
echo(`The temp versioning folder is ${tmpVersioningFolder}`);
|
||||
|
||||
saveFiles(
|
||||
[
|
||||
'packages/react-native/package.json',
|
||||
'packages/react-native/template/package.json',
|
||||
],
|
||||
tmpVersioningFolder,
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java',
|
||||
cat('scripts/versiontemplates/ReactNativeVersion.java.template')
|
||||
.replace('${major}', major)
|
||||
.replace('${minor}', minor)
|
||||
.replace('${patch}', patch)
|
||||
.replace(
|
||||
'${prerelease}',
|
||||
prerelease !== undefined ? `"${prerelease}"` : 'null',
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/React/Base/RCTVersion.m',
|
||||
cat('scripts/versiontemplates/RCTVersion.m.template')
|
||||
.replace('${major}', `@(${major})`)
|
||||
.replace('${minor}', `@(${minor})`)
|
||||
.replace('${patch}', `@(${patch})`)
|
||||
.replace(
|
||||
'${prerelease}',
|
||||
prerelease !== undefined ? `@"${prerelease}"` : '[NSNull null]',
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/ReactCommon/cxxreact/ReactNativeVersion.h',
|
||||
cat('scripts/versiontemplates/ReactNativeVersion.h.template')
|
||||
.replace('${major}', major)
|
||||
.replace('${minor}', minor)
|
||||
.replace('${patch}', patch)
|
||||
.replace(
|
||||
'${prerelease}',
|
||||
prerelease !== undefined ? `"${prerelease}"` : '""',
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/Libraries/Core/ReactNativeVersion.js',
|
||||
cat('scripts/versiontemplates/ReactNativeVersion.js.template')
|
||||
.replace('${major}', major)
|
||||
.replace('${minor}', minor)
|
||||
.replace('${patch}', patch)
|
||||
.replace(
|
||||
'${prerelease}',
|
||||
prerelease !== undefined ? `'${prerelease}'` : 'null',
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const packageJson = JSON.parse(cat('packages/react-native/package.json'));
|
||||
packageJson.version = version;
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/package.json',
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
// Change ReactAndroid/gradle.properties
|
||||
saveFiles(
|
||||
['packages/react-native/ReactAndroid/gradle.properties'],
|
||||
tmpVersioningFolder,
|
||||
);
|
||||
if (
|
||||
sed(
|
||||
function setGradle({version}) {
|
||||
const result = sed(
|
||||
'-i',
|
||||
/^VERSION_NAME=.*/,
|
||||
`VERSION_NAME=${version}`,
|
||||
'packages/react-native/ReactAndroid/gradle.properties',
|
||||
).code
|
||||
) {
|
||||
echo("Couldn't update version for Gradle");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Change react-native version in the template's package.json
|
||||
exec(`node scripts/set-rn-template-version.js ${version}`);
|
||||
|
||||
// Verify that files changed, we just do a git diff and check how many times version is added across files
|
||||
const filesToValidate = [
|
||||
'packages/react-native/package.json',
|
||||
'packages/react-native/ReactAndroid/gradle.properties',
|
||||
'packages/react-native/template/package.json',
|
||||
];
|
||||
|
||||
const numberOfChangedLinesWithNewVersion = exec(
|
||||
`diff -r ${tmpVersioningFolder} . | grep '^[>]' | grep -c ${version} `,
|
||||
{silent: true},
|
||||
).stdout.trim();
|
||||
|
||||
if (+numberOfChangedLinesWithNewVersion !== filesToValidate.length) {
|
||||
// TODO: the logic that checks whether all the changes have been applied
|
||||
// is missing several files. For example, it is not checking Ruby version nor that
|
||||
// the Objecive-C files, the codegen and other files are properly updated.
|
||||
// We are going to work on this in another PR.
|
||||
echo('WARNING:');
|
||||
echo(
|
||||
`Failed to update all the files: [${filesToValidate.join(
|
||||
', ',
|
||||
)}] must have versions in them`,
|
||||
);
|
||||
echo(`These files already had version ${version} set.`);
|
||||
if (result.code) {
|
||||
echo("Couldn't update version for Gradle");
|
||||
throw result.stderr;
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
function setPackage({version}) {
|
||||
const packageJson = JSON.parse(cat('packages/react-native/package.json'));
|
||||
packageJson.version = version;
|
||||
|
||||
fs.writeFileSync(
|
||||
'packages/react-native/package.json',
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
'utf-8',
|
||||
);
|
||||
}
|
||||
|
||||
function setTemplatePackage({version}) {
|
||||
const result = exec(`node scripts/set-rn-template-version.js ${version}`);
|
||||
if (result.code) {
|
||||
echo("Failed to update React Native template's version of React Native");
|
||||
throw result.stderr;
|
||||
}
|
||||
}
|
||||
|
||||
function setReactNativeVersion(argVersion, buildType) {
|
||||
validateBuildType(buildType);
|
||||
|
||||
const version = parseVersion(argVersion, buildType);
|
||||
|
||||
// Create tmp folder for copies of files to verify files have changed
|
||||
const filesToValidate = [
|
||||
'packages/react-native/package.json',
|
||||
'packages/react-native/ReactAndroid/gradle.properties',
|
||||
'packages/react-native/template/package.json',
|
||||
];
|
||||
const tmpVersioningFolder = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'rn-set-version'),
|
||||
);
|
||||
echo(`The tmp versioning folder is ${tmpVersioningFolder}`);
|
||||
saveFiles(tmpVersioningFolder);
|
||||
|
||||
setSource(version);
|
||||
setPackage(version);
|
||||
setTemplatePackage(version);
|
||||
setGradle(version);
|
||||
|
||||
// Validate changes
|
||||
// We just do a git diff and check how many times version is added across files
|
||||
const numberOfChangedLinesWithNewVersion = exec(
|
||||
`diff -r ${tmpVersioningFolder} . | grep '^[>]' | grep -c ${version.version} `,
|
||||
{silent: true},
|
||||
).stdout.trim();
|
||||
|
||||
if (+numberOfChangedLinesWithNewVersion !== filesToValidate.length) {
|
||||
// TODO: the logic that checks whether all the changes have been applied
|
||||
// is missing several files. For example, it is not checking Ruby version nor that
|
||||
// the Objecive-C files, the codegen and other files are properly updated.
|
||||
// We are going to work on this in another PR.
|
||||
echo('WARNING:');
|
||||
echo(
|
||||
`Failed to update all the files: [${filesToValidate.join(
|
||||
', ',
|
||||
)}] must have versions in them`,
|
||||
);
|
||||
echo(`These files already had version ${version.version} set.`);
|
||||
}
|
||||
|
||||
return exit(0);
|
||||
}
|
||||
|
||||
module.exports = setReactNativeVersion;
|
||||
|
|
Загрузка…
Ссылка в новой задаче