Skip to content

Commit

Permalink
feat(react-native-github): a script to automate patch version bumping…
Browse files Browse the repository at this point in the history
… of packages (facebook#35767)

Summary:
Pull Request resolved: facebook#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
  • Loading branch information
hoxyq authored and Ruslan Lesiutin committed Jan 24, 2023
1 parent 058defb commit ce9a79e
Show file tree
Hide file tree
Showing 9 changed files with 516 additions and 31 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@
"test-e2e-local-clean": "node ./scripts/test-e2e-local-clean.js",
"test-ios": "./scripts/objc-test.sh test",
"test-typescript": "dtslint types",
"test-typescript-offline": "dtslint --localTs node_modules/typescript/lib types"
"test-typescript-offline": "dtslint --localTs node_modules/typescript/lib types",
"bump-all-updated-packages": "node ./scripts/monorepo/bump-all-updated-packages",
"align-package-versions": "node ./scripts/monorepo/align-package-versions.js"
},
"peerDependencies": {
"react": "18.2.0"
Expand Down
12 changes: 9 additions & 3 deletions scripts/__tests__/find-and-publish-all-bumped-packages-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
* @format
*/

const {exec} = require('shelljs');
const {spawnSync} = require('child_process');

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

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

describe('findAndPublishAllBumpedPackages', () => {
Expand All @@ -24,10 +25,15 @@ describe('findAndPublishAllBumpedPackages', () => {
version: mockedPackageNewVersion,
});
});
exec.mockImplementationOnce(() => ({

spawnSync.mockImplementationOnce(() => ({
stdout: `- "version": "0.72.0"\n+ "version": "${mockedPackageNewVersion}"\n`,
}));

spawnSync.mockImplementationOnce(() => ({
stdout: BUMP_COMMIT_MESSAGE,
}));

expect(() => findAndPublishAllBumpedPackages()).toThrow(
`Package version expected to be 0.x.y, but received ${mockedPackageNewVersion}`,
);
Expand Down
39 changes: 39 additions & 0 deletions scripts/monorepo/__tests__/bump-package-version-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* 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 path = require('path');
const {writeFileSync} = require('fs');

const bumpPackageVersion = require('../bump-all-updated-packages/bump-package-version');

jest.mock('fs', () => ({
writeFileSync: jest.fn(),
readFileSync: jest.fn(() => '{}'),
}));

jest.mock('../for-each-package', () => callback => {});

describe('bumpPackageVersionTest', () => {
it('updates patch version of the package', () => {
const mockedPackageLocation = '~/packages/assets';
const mockedPackageManifest = {
name: '@react-native/test',
version: '1.2.3',
};

bumpPackageVersion(mockedPackageLocation, mockedPackageManifest);

expect(writeFileSync).toHaveBeenCalledWith(
path.join(mockedPackageLocation, 'package.json'),
JSON.stringify({...mockedPackageManifest, version: '1.2.4'}, null, 2) +
'\n',
'utf-8',
);
});
});
146 changes: 146 additions & 0 deletions scripts/monorepo/align-package-versions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* 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 {spawnSync} = require('child_process');
const {writeFileSync, readFileSync} = require('fs');
const path = require('path');

const checkForGitChanges = require('./check-for-git-changes');
const forEachPackage = require('./for-each-package');

const ROOT_LOCATION = path.join(__dirname, '..', '..');
const TEMPLATE_LOCATION = path.join(ROOT_LOCATION, 'template');
const REPO_CONFIG_LOCATION = path.join(ROOT_LOCATION, 'repo-config');

const readJSONFile = pathToFile => JSON.parse(readFileSync(pathToFile));

const checkIfShouldUpdateDependencyPackageVersion = (
consumerPackageAbsolutePath,
updatedPackageName,
updatedPackageVersion,
) => {
const consumerPackageManifestPath = path.join(
consumerPackageAbsolutePath,
'package.json',
);
const consumerPackageManifest = readJSONFile(consumerPackageManifestPath);

const dependencyVersion =
consumerPackageManifest.dependencies?.[updatedPackageName];

if (dependencyVersion && dependencyVersion !== '*') {
const updatedDependencyVersion = dependencyVersion.startsWith('^')
? `^${updatedPackageVersion}`
: updatedPackageVersion;

if (updatedDependencyVersion !== dependencyVersion) {
console.log(
`\uD83D\uDCA1 ${consumerPackageManifest.name} was updated: now using version ${updatedPackageVersion} of ${updatedPackageName}`,
);

const updatedPackageManifest = {
...consumerPackageManifest,
dependencies: {
...consumerPackageManifest.dependencies,
[updatedPackageName]: updatedDependencyVersion,
},
};

writeFileSync(
consumerPackageManifestPath,
JSON.stringify(updatedPackageManifest, null, 2) + '\n',
'utf-8',
);
}
}

const devDependencyVersion =
consumerPackageManifest.devDependencies?.[updatedPackageName];

if (devDependencyVersion && devDependencyVersion !== '*') {
const updatedDependencyVersion = devDependencyVersion.startsWith('^')
? `^${updatedPackageVersion}`
: updatedPackageVersion;

if (updatedDependencyVersion !== devDependencyVersion) {
console.log(
`\uD83D\uDCA1 ${consumerPackageManifest.name} was updated: now using version ${updatedPackageVersion} of ${updatedPackageName}`,
);

const updatedPackageManifest = {
...consumerPackageManifest,
devDependencies: {
...consumerPackageManifest.devDependencies,
[updatedPackageName]: updatedDependencyVersion,
},
};

writeFileSync(
consumerPackageManifestPath,
JSON.stringify(updatedPackageManifest, null, 2) + '\n',
'utf-8',
);
}
}
};

const alignPackageVersions = () => {
if (checkForGitChanges()) {
console.log(
'\u274c Found uncommitted changes. Please commit or stash them before running this script',
);

process.exit(1);
}

forEachPackage((packageAbsolutePath, _, packageManifest) => {
checkIfShouldUpdateDependencyPackageVersion(
ROOT_LOCATION,
packageManifest.name,
packageManifest.version,
);

checkIfShouldUpdateDependencyPackageVersion(
TEMPLATE_LOCATION,
packageManifest.name,
packageManifest.version,
);

checkIfShouldUpdateDependencyPackageVersion(
REPO_CONFIG_LOCATION,
packageManifest.name,
packageManifest.version,
);

forEachPackage(pathToPackage =>
checkIfShouldUpdateDependencyPackageVersion(
pathToPackage,
packageManifest.name,
packageManifest.version,
),
);
});

if (!checkForGitChanges()) {
console.log(
'\u2705 There were no changes. Every consumer package uses the actual version of dependency package.',
);
return;
}

console.log('Running yarn to update lock file...');
spawnSync('yarn', ['install'], {
cwd: ROOT_LOCATION,
shell: true,
stdio: 'inherit',
encoding: 'utf-8',
});
};

alignPackageVersions();
52 changes: 52 additions & 0 deletions scripts/monorepo/bump-all-updated-packages/bump-package-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* 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 {writeFileSync} = require('fs');
const path = require('path');

const getIncrementedVersion = (version, increment) =>
version
.split('.')
.map((token, index) => {
const indexOfVersionToIncrement = increment === 'minor' ? 1 : 2;

if (index === indexOfVersionToIncrement) {
return parseInt(token, 10) + 1;
}

if (index > indexOfVersionToIncrement) {
return 0;
}

return token;
})
.join('.');

const bumpPackageVersion = (
packageAbsolutePath,
packageManifest,
increment = 'patch',
) => {
const updatedVersion = getIncrementedVersion(
packageManifest.version,
increment,
);

// Not using simple `npm version patch` because it updates dependencies and yarn.lock file
writeFileSync(
path.join(packageAbsolutePath, 'package.json'),
JSON.stringify({...packageManifest, version: updatedVersion}, null, 2) +
'\n',
'utf-8',
);

return updatedVersion;
};

module.exports = bumpPackageVersion;
Loading

0 comments on commit ce9a79e

Please sign in to comment.