Skip to content

Commit ec28c5b

Browse files
hoxyqfacebook-github-bot
authored andcommitted
feat(react-native-github): a script to automate patch version bumping of packages (#35767)
Summary: Pull Request resolved: #35767 Changelog: [Internal] Introducing a script, which can be used to identify all packages inside `/packages`, which contain any changes after the last time its version was changed How it works step by step: ``` check that no git changes are present for each package: if package is private -> skip grep id of the last commit that changed package grep id of the last commit that changed version of the package if these ids are different: bump package patch version commit changes if required ``` Can be executed only in git environment and by running: `node ./scripts/bump-all-updated-packages` --- Also adding a separate script `align-package-versions.js`, which can be used to update versions of packages inside consumer packages ``` check that no git changes are present for each package x: for each package y: if y has x as dependency: validate that y uses the latest version of x if some changes were made: run yarn ``` --- Q: Why `run_yarn` step was removed from CircleCI flow? A: For *-stable branches, there are no yarn workspaces and all packages are specified as direct dependencies, so if we update `react-native/assets-registry` to the next version, we won't be able to run `yarn` for react-native root package, because updated version is not yet published to npm To avoid this, we first need publish new versions and then update them in consumer packages --- The final flow: 1. Developer uses `node ./scripts/bump-all-updated-packages` to bump versions of all updated packages. 2. Commit created from step 1 being merged or directly pushed to `main` or `*-stable` branches 3. A workflow from CircleCI publishes all updated versions to npm 4. Developer can use `align-package-versions.js` script to create required changes to align all packages versions Reviewed By: cortinico Differential Revision: D42295344 fbshipit-source-id: 54b667adb3ee5f28d19ee9c7991570451549aac2
1 parent a80cf96 commit ec28c5b

File tree

10 files changed

+516
-32
lines changed

10 files changed

+516
-32
lines changed

.circleci/config.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1576,7 +1576,6 @@ jobs:
15761576
executor: reactnativeandroid
15771577
steps:
15781578
- checkout
1579-
- run_yarn
15801579
- run:
15811580
name: Set NPM auth token
15821581
command: echo "//registry.npmjs.org/:_authToken=${CIRCLE_NPM_TOKEN}" > ~/.npmrc

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@
9898
"test-e2e-local-clean": "node ./scripts/test-e2e-local-clean.js",
9999
"test-ios": "./scripts/objc-test.sh test",
100100
"test-typescript": "dtslint types",
101-
"test-typescript-offline": "dtslint --localTs node_modules/typescript/lib types"
101+
"test-typescript-offline": "dtslint --localTs node_modules/typescript/lib types",
102+
"bump-all-updated-packages": "node ./scripts/monorepo/bump-all-updated-packages",
103+
"align-package-versions": "node ./scripts/monorepo/align-package-versions.js"
102104
},
103105
"workspaces": [
104106
"packages/*",

scripts/__tests__/find-and-publish-all-bumped-packages-test.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
* @format
88
*/
99

10-
const {exec} = require('shelljs');
10+
const {spawnSync} = require('child_process');
1111

12+
const {BUMP_COMMIT_MESSAGE} = require('../monorepo/constants');
1213
const forEachPackage = require('../monorepo/for-each-package');
1314
const findAndPublishAllBumpedPackages = require('../monorepo/find-and-publish-all-bumped-packages');
1415

15-
jest.mock('shelljs', () => ({exec: jest.fn()}));
16+
jest.mock('child_process', () => ({spawnSync: jest.fn()}));
1617
jest.mock('../monorepo/for-each-package', () => jest.fn());
1718

1819
describe('findAndPublishAllBumpedPackages', () => {
@@ -24,10 +25,15 @@ describe('findAndPublishAllBumpedPackages', () => {
2425
version: mockedPackageNewVersion,
2526
});
2627
});
27-
exec.mockImplementationOnce(() => ({
28+
29+
spawnSync.mockImplementationOnce(() => ({
2830
stdout: `- "version": "0.72.0"\n+ "version": "${mockedPackageNewVersion}"\n`,
2931
}));
3032

33+
spawnSync.mockImplementationOnce(() => ({
34+
stdout: BUMP_COMMIT_MESSAGE,
35+
}));
36+
3137
expect(() => findAndPublishAllBumpedPackages()).toThrow(
3238
`Package version expected to be 0.x.y, but received ${mockedPackageNewVersion}`,
3339
);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
*/
9+
10+
const path = require('path');
11+
const {writeFileSync} = require('fs');
12+
13+
const bumpPackageVersion = require('../bump-all-updated-packages/bump-package-version');
14+
15+
jest.mock('fs', () => ({
16+
writeFileSync: jest.fn(),
17+
readFileSync: jest.fn(() => '{}'),
18+
}));
19+
20+
jest.mock('../for-each-package', () => callback => {});
21+
22+
describe('bumpPackageVersionTest', () => {
23+
it('updates patch version of the package', () => {
24+
const mockedPackageLocation = '~/packages/assets';
25+
const mockedPackageManifest = {
26+
name: '@react-native/test',
27+
version: '1.2.3',
28+
};
29+
30+
bumpPackageVersion(mockedPackageLocation, mockedPackageManifest);
31+
32+
expect(writeFileSync).toHaveBeenCalledWith(
33+
path.join(mockedPackageLocation, 'package.json'),
34+
JSON.stringify({...mockedPackageManifest, version: '1.2.4'}, null, 2) +
35+
'\n',
36+
'utf-8',
37+
);
38+
});
39+
});
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
*/
9+
10+
const {spawnSync} = require('child_process');
11+
const {writeFileSync, readFileSync} = require('fs');
12+
const path = require('path');
13+
14+
const checkForGitChanges = require('./check-for-git-changes');
15+
const forEachPackage = require('./for-each-package');
16+
17+
const ROOT_LOCATION = path.join(__dirname, '..', '..');
18+
const TEMPLATE_LOCATION = path.join(ROOT_LOCATION, 'template');
19+
const REPO_CONFIG_LOCATION = path.join(ROOT_LOCATION, 'repo-config');
20+
21+
const readJSONFile = pathToFile => JSON.parse(readFileSync(pathToFile));
22+
23+
const checkIfShouldUpdateDependencyPackageVersion = (
24+
consumerPackageAbsolutePath,
25+
updatedPackageName,
26+
updatedPackageVersion,
27+
) => {
28+
const consumerPackageManifestPath = path.join(
29+
consumerPackageAbsolutePath,
30+
'package.json',
31+
);
32+
const consumerPackageManifest = readJSONFile(consumerPackageManifestPath);
33+
34+
const dependencyVersion =
35+
consumerPackageManifest.dependencies?.[updatedPackageName];
36+
37+
if (dependencyVersion && dependencyVersion !== '*') {
38+
const updatedDependencyVersion = dependencyVersion.startsWith('^')
39+
? `^${updatedPackageVersion}`
40+
: updatedPackageVersion;
41+
42+
if (updatedDependencyVersion !== dependencyVersion) {
43+
console.log(
44+
`\uD83D\uDCA1 ${consumerPackageManifest.name} was updated: now using version ${updatedPackageVersion} of ${updatedPackageName}`,
45+
);
46+
47+
const updatedPackageManifest = {
48+
...consumerPackageManifest,
49+
dependencies: {
50+
...consumerPackageManifest.dependencies,
51+
[updatedPackageName]: updatedDependencyVersion,
52+
},
53+
};
54+
55+
writeFileSync(
56+
consumerPackageManifestPath,
57+
JSON.stringify(updatedPackageManifest, null, 2) + '\n',
58+
'utf-8',
59+
);
60+
}
61+
}
62+
63+
const devDependencyVersion =
64+
consumerPackageManifest.devDependencies?.[updatedPackageName];
65+
66+
if (devDependencyVersion && devDependencyVersion !== '*') {
67+
const updatedDependencyVersion = devDependencyVersion.startsWith('^')
68+
? `^${updatedPackageVersion}`
69+
: updatedPackageVersion;
70+
71+
if (updatedDependencyVersion !== devDependencyVersion) {
72+
console.log(
73+
`\uD83D\uDCA1 ${consumerPackageManifest.name} was updated: now using version ${updatedPackageVersion} of ${updatedPackageName}`,
74+
);
75+
76+
const updatedPackageManifest = {
77+
...consumerPackageManifest,
78+
devDependencies: {
79+
...consumerPackageManifest.devDependencies,
80+
[updatedPackageName]: updatedDependencyVersion,
81+
},
82+
};
83+
84+
writeFileSync(
85+
consumerPackageManifestPath,
86+
JSON.stringify(updatedPackageManifest, null, 2) + '\n',
87+
'utf-8',
88+
);
89+
}
90+
}
91+
};
92+
93+
const alignPackageVersions = () => {
94+
if (checkForGitChanges()) {
95+
console.log(
96+
'\u274c Found uncommitted changes. Please commit or stash them before running this script',
97+
);
98+
99+
process.exit(1);
100+
}
101+
102+
forEachPackage((packageAbsolutePath, _, packageManifest) => {
103+
checkIfShouldUpdateDependencyPackageVersion(
104+
ROOT_LOCATION,
105+
packageManifest.name,
106+
packageManifest.version,
107+
);
108+
109+
checkIfShouldUpdateDependencyPackageVersion(
110+
TEMPLATE_LOCATION,
111+
packageManifest.name,
112+
packageManifest.version,
113+
);
114+
115+
checkIfShouldUpdateDependencyPackageVersion(
116+
REPO_CONFIG_LOCATION,
117+
packageManifest.name,
118+
packageManifest.version,
119+
);
120+
121+
forEachPackage(pathToPackage =>
122+
checkIfShouldUpdateDependencyPackageVersion(
123+
pathToPackage,
124+
packageManifest.name,
125+
packageManifest.version,
126+
),
127+
);
128+
});
129+
130+
if (!checkForGitChanges()) {
131+
console.log(
132+
'\u2705 There were no changes. Every consumer package uses the actual version of dependency package.',
133+
);
134+
return;
135+
}
136+
137+
console.log('Running yarn to update lock file...');
138+
spawnSync('yarn', ['install'], {
139+
cwd: ROOT_LOCATION,
140+
shell: true,
141+
stdio: 'inherit',
142+
encoding: 'utf-8',
143+
});
144+
};
145+
146+
alignPackageVersions();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
*/
9+
10+
const {writeFileSync} = require('fs');
11+
const path = require('path');
12+
13+
const getIncrementedVersion = (version, increment) =>
14+
version
15+
.split('.')
16+
.map((token, index) => {
17+
const indexOfVersionToIncrement = increment === 'minor' ? 1 : 2;
18+
19+
if (index === indexOfVersionToIncrement) {
20+
return parseInt(token, 10) + 1;
21+
}
22+
23+
if (index > indexOfVersionToIncrement) {
24+
return 0;
25+
}
26+
27+
return token;
28+
})
29+
.join('.');
30+
31+
const bumpPackageVersion = (
32+
packageAbsolutePath,
33+
packageManifest,
34+
increment = 'patch',
35+
) => {
36+
const updatedVersion = getIncrementedVersion(
37+
packageManifest.version,
38+
increment,
39+
);
40+
41+
// Not using simple `npm version patch` because it updates dependencies and yarn.lock file
42+
writeFileSync(
43+
path.join(packageAbsolutePath, 'package.json'),
44+
JSON.stringify({...packageManifest, version: updatedVersion}, null, 2) +
45+
'\n',
46+
'utf-8',
47+
);
48+
49+
return updatedVersion;
50+
};
51+
52+
module.exports = bumpPackageVersion;

0 commit comments

Comments
 (0)