Skip to content

Commit

Permalink
feat(react-native-github): automate publishing bumped packages via ci…
Browse files Browse the repository at this point in the history
…rcleci (#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
  • Loading branch information
hoxyq authored and facebook-github-bot committed Dec 13, 2022
1 parent 65e0f79 commit a5c3f44
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 4 deletions.
27 changes: 23 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ references:
attach_workspace:
at: *hermes_workspace_root

main_only: &main_only
filters:
branches:
only: main

# -------------------------
# Dependency Anchors
# -------------------------
Expand Down Expand Up @@ -1561,6 +1566,17 @@ jobs:
command: |
echo "Nightly build run"
find_and_publish_bumped_packages:
executor: reactnativeandroid
steps:
- checkout
- run:
name: Set NPM auth token
command: echo "//registry.npmjs.org/:_authToken=${CIRCLE_NPM_TOKEN}" > ~/.npmrc
- run:
name: Find and publish all bumped packages
command: node ./scripts/monorepo/find-and-publish-all-bumped-packages.js


# -------------------------
# PIPELINE PARAMETERS
Expand Down Expand Up @@ -1749,11 +1765,8 @@ workflows:
unless: << pipeline.parameters.run_package_release_workflow_only >>
triggers:
- schedule:
<<: *main_only
cron: "0 20 * * *"
filters:
branches:
only:
- main
jobs:
- nightly_job

Expand All @@ -1776,3 +1789,9 @@ workflows:
- build_hermesc_linux
- build_hermes_macos
- build_hermesc_windows

publish_bumped_packages:
unless: << pipeline.parameters.run_package_release_workflow_only >>
jobs:
- find_and_publish_bumped_packages:
<<: *main_only
93 changes: 93 additions & 0 deletions scripts/monorepo/find-and-publish-all-bumped-packages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* 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 chalk = require('chalk');
const {exec} = require('shelljs');

const forEachPackage = require('./for-each-package');

const ROOT_LOCATION = path.join(__dirname, '..', '..');
const NPM_CONFIG_OTP = process.env.NPM_CONFIG_OTP;

const findAndPublishAllBumpedPackages = () => {
console.log('Traversing all packages inside /packages...');

forEachPackage(
(packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => {
if (packageManifest.private) {
console.log(
`\u23ED Skipping private package ${chalk.dim(packageManifest.name)}`,
);

return;
}

const diff = exec(
`git log -p --format="" HEAD~1..HEAD ${packageRelativePathFromRoot}/package.json`,
{cwd: ROOT_LOCATION, silent: true},
).stdout;

const previousVersionPatternMatches = diff.match(
/- {2}"version": "([0-9]+.[0-9]+.[0-9]+)"/,
);

if (!previousVersionPatternMatches) {
console.log(
`\uD83D\uDD0E No version bump for ${chalk.green(
packageManifest.name,
)}`,
);

return;
}

const nextVersionPatternMatches = diff.match(
/\+ {2}"version": "([0-9]+.[0-9]+.[0-9]+)"/,
);
const [, previousVersion] = previousVersionPatternMatches;
const [, nextVersion] = nextVersionPatternMatches;

console.log(
`\uD83D\uDCA1 ${chalk.yellow(
packageManifest.name,
)} was updated: ${chalk.red(previousVersion)} -> ${chalk.green(
nextVersion,
)}`,
);

const npmOTPFlag = NPM_CONFIG_OTP ? `--otp ${NPM_CONFIG_OTP}` : '';

const {code, stderr} = exec(`npm publish ${npmOTPFlag}`, {
cwd: packageAbsolutePath,
silent: true,
});
if (code) {
console.log(
chalk.red(
`\u274c Failed to publish version ${nextVersion} of ${packageManifest.name}. Stderr:`,
),
);
console.log(stderr);

process.exit(1);
} else {
console.log(
`\u2705 Successfully published new version of ${chalk.green(
packageManifest.name,
)}`,
);
}
},
);

process.exit(0);
};

findAndPublishAllBumpedPackages();
59 changes: 59 additions & 0 deletions scripts/monorepo/for-each-package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* 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 {readdirSync, readFileSync} = require('fs');

const ROOT_LOCATION = path.join(__dirname, '..', '..');
const PACKAGES_LOCATION = path.join(ROOT_LOCATION, 'packages');

const PACKAGES_BLOCK_LIST = ['react-native'];

/**
* Function, which returns an array of all directories inside specified location
*
* @param {string} source Path to directory, where this should be executed
* @returns {string[]} List of directories names
*/
const getDirectories = source =>
readdirSync(source, {withFileTypes: true})
.filter(file => file.isDirectory())
.map(directory => directory.name);

/**
* @callback forEachPackageCallback
* @param {string} packageAbsolutePath
* @param {string} packageRelativePathFromRoot
* @param {Object} packageManifest
*/

/**
* Iterate through every package inside /packages (ignoring react-native) and call provided callback for each of them
*
* @param {forEachPackageCallback} callback The callback which will be called for each package
*/
const forEachPackage = callback => {
// We filter react-native package on purpose, so that no CI's script will be executed for this package in future
const packagesDirectories = getDirectories(PACKAGES_LOCATION).filter(
directoryName => !PACKAGES_BLOCK_LIST.includes(directoryName),
);

packagesDirectories.forEach(packageDirectory => {
const packageAbsolutePath = path.join(PACKAGES_LOCATION, packageDirectory);
const packageRelativePathFromRoot = path.join('packages', packageDirectory);

const packageManifest = JSON.parse(
readFileSync(path.join(packageRelativePathFromRoot, 'package.json')),
);

callback(packageAbsolutePath, packageRelativePathFromRoot, packageManifest);
});
};

module.exports = forEachPackage;

0 comments on commit a5c3f44

Please sign in to comment.