Skip to content

Commit a5c3f44

Browse files
hoxyqfacebook-github-bot
authored andcommitted
feat(react-native-github): automate publishing bumped packages via circleci (#35621)
Summary: Pull Request resolved: #35621 Changelog: [Internal] 1. Added `for-each-package.js` script. This can be used to iterate through all of the packages inside `/packages` with the access to package manifest. This soon can be used as a replacement for `yarn workspaces --info` 2. Added `find-and-publish-all-bumped-packages.js` script. This script iterates through all the packages and detects if the version was changed via `git log -p` (same as `git diff`). If so, it tries to publish it to npm. 3. Added corresponding job and workflow to CircleCI config, which will use this script Differential Revision: D41972733 fbshipit-source-id: cc7866f01b57603ffd81dc38ca5bba94b36ea6c3
1 parent 65e0f79 commit a5c3f44

File tree

3 files changed

+175
-4
lines changed

3 files changed

+175
-4
lines changed

.circleci/config.yml

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ references:
3434
attach_workspace:
3535
at: *hermes_workspace_root
3636

37+
main_only: &main_only
38+
filters:
39+
branches:
40+
only: main
41+
3742
# -------------------------
3843
# Dependency Anchors
3944
# -------------------------
@@ -1561,6 +1566,17 @@ jobs:
15611566
command: |
15621567
echo "Nightly build run"
15631568
1569+
find_and_publish_bumped_packages:
1570+
executor: reactnativeandroid
1571+
steps:
1572+
- checkout
1573+
- run:
1574+
name: Set NPM auth token
1575+
command: echo "//registry.npmjs.org/:_authToken=${CIRCLE_NPM_TOKEN}" > ~/.npmrc
1576+
- run:
1577+
name: Find and publish all bumped packages
1578+
command: node ./scripts/monorepo/find-and-publish-all-bumped-packages.js
1579+
15641580

15651581
# -------------------------
15661582
# PIPELINE PARAMETERS
@@ -1749,11 +1765,8 @@ workflows:
17491765
unless: << pipeline.parameters.run_package_release_workflow_only >>
17501766
triggers:
17511767
- schedule:
1768+
<<: *main_only
17521769
cron: "0 20 * * *"
1753-
filters:
1754-
branches:
1755-
only:
1756-
- main
17571770
jobs:
17581771
- nightly_job
17591772

@@ -1776,3 +1789,9 @@ workflows:
17761789
- build_hermesc_linux
17771790
- build_hermes_macos
17781791
- build_hermesc_windows
1792+
1793+
publish_bumped_packages:
1794+
unless: << pipeline.parameters.run_package_release_workflow_only >>
1795+
jobs:
1796+
- find_and_publish_bumped_packages:
1797+
<<: *main_only
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 chalk = require('chalk');
12+
const {exec} = require('shelljs');
13+
14+
const forEachPackage = require('./for-each-package');
15+
16+
const ROOT_LOCATION = path.join(__dirname, '..', '..');
17+
const NPM_CONFIG_OTP = process.env.NPM_CONFIG_OTP;
18+
19+
const findAndPublishAllBumpedPackages = () => {
20+
console.log('Traversing all packages inside /packages...');
21+
22+
forEachPackage(
23+
(packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => {
24+
if (packageManifest.private) {
25+
console.log(
26+
`\u23ED Skipping private package ${chalk.dim(packageManifest.name)}`,
27+
);
28+
29+
return;
30+
}
31+
32+
const diff = exec(
33+
`git log -p --format="" HEAD~1..HEAD ${packageRelativePathFromRoot}/package.json`,
34+
{cwd: ROOT_LOCATION, silent: true},
35+
).stdout;
36+
37+
const previousVersionPatternMatches = diff.match(
38+
/- {2}"version": "([0-9]+.[0-9]+.[0-9]+)"/,
39+
);
40+
41+
if (!previousVersionPatternMatches) {
42+
console.log(
43+
`\uD83D\uDD0E No version bump for ${chalk.green(
44+
packageManifest.name,
45+
)}`,
46+
);
47+
48+
return;
49+
}
50+
51+
const nextVersionPatternMatches = diff.match(
52+
/\+ {2}"version": "([0-9]+.[0-9]+.[0-9]+)"/,
53+
);
54+
const [, previousVersion] = previousVersionPatternMatches;
55+
const [, nextVersion] = nextVersionPatternMatches;
56+
57+
console.log(
58+
`\uD83D\uDCA1 ${chalk.yellow(
59+
packageManifest.name,
60+
)} was updated: ${chalk.red(previousVersion)} -> ${chalk.green(
61+
nextVersion,
62+
)}`,
63+
);
64+
65+
const npmOTPFlag = NPM_CONFIG_OTP ? `--otp ${NPM_CONFIG_OTP}` : '';
66+
67+
const {code, stderr} = exec(`npm publish ${npmOTPFlag}`, {
68+
cwd: packageAbsolutePath,
69+
silent: true,
70+
});
71+
if (code) {
72+
console.log(
73+
chalk.red(
74+
`\u274c Failed to publish version ${nextVersion} of ${packageManifest.name}. Stderr:`,
75+
),
76+
);
77+
console.log(stderr);
78+
79+
process.exit(1);
80+
} else {
81+
console.log(
82+
`\u2705 Successfully published new version of ${chalk.green(
83+
packageManifest.name,
84+
)}`,
85+
);
86+
}
87+
},
88+
);
89+
90+
process.exit(0);
91+
};
92+
93+
findAndPublishAllBumpedPackages();
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 {readdirSync, readFileSync} = require('fs');
12+
13+
const ROOT_LOCATION = path.join(__dirname, '..', '..');
14+
const PACKAGES_LOCATION = path.join(ROOT_LOCATION, 'packages');
15+
16+
const PACKAGES_BLOCK_LIST = ['react-native'];
17+
18+
/**
19+
* Function, which returns an array of all directories inside specified location
20+
*
21+
* @param {string} source Path to directory, where this should be executed
22+
* @returns {string[]} List of directories names
23+
*/
24+
const getDirectories = source =>
25+
readdirSync(source, {withFileTypes: true})
26+
.filter(file => file.isDirectory())
27+
.map(directory => directory.name);
28+
29+
/**
30+
* @callback forEachPackageCallback
31+
* @param {string} packageAbsolutePath
32+
* @param {string} packageRelativePathFromRoot
33+
* @param {Object} packageManifest
34+
*/
35+
36+
/**
37+
* Iterate through every package inside /packages (ignoring react-native) and call provided callback for each of them
38+
*
39+
* @param {forEachPackageCallback} callback The callback which will be called for each package
40+
*/
41+
const forEachPackage = callback => {
42+
// We filter react-native package on purpose, so that no CI's script will be executed for this package in future
43+
const packagesDirectories = getDirectories(PACKAGES_LOCATION).filter(
44+
directoryName => !PACKAGES_BLOCK_LIST.includes(directoryName),
45+
);
46+
47+
packagesDirectories.forEach(packageDirectory => {
48+
const packageAbsolutePath = path.join(PACKAGES_LOCATION, packageDirectory);
49+
const packageRelativePathFromRoot = path.join('packages', packageDirectory);
50+
51+
const packageManifest = JSON.parse(
52+
readFileSync(path.join(packageRelativePathFromRoot, 'package.json')),
53+
);
54+
55+
callback(packageAbsolutePath, packageRelativePathFromRoot, packageManifest);
56+
});
57+
};
58+
59+
module.exports = forEachPackage;

0 commit comments

Comments
 (0)