From bdd901eaa74ef58da83a90d3286f45161e1a9d01 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 24 Nov 2022 07:46:39 -0800 Subject: [PATCH 1/8] refactor(circleci/template): publish all packages to Verdaccio before template initialization Summary: Changelog: [Internal] [Changed] - now bootstrapping Verdaccio before template app initialization, this is required because react-native migh depend on some package which version is not yet published to npm Differential Revision: D41521496 fbshipit-source-id: 7a32d4de80ff1de0840760380cda410bed626808 --- .circleci/config.yml | 7 +-- scripts/template/initialize.js | 105 +++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 scripts/template/initialize.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 405d173d37bf05..c851fe9a78bab0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -817,10 +817,7 @@ jobs: command: | REPO_ROOT=$(pwd) node ./scripts/set-rn-template-version.js "file:$REPO_ROOT/build/$(cat build/react-native-package-version)" - node cli.js init $PROJECT_NAME --directory "/tmp/$PROJECT_NAME" --template $REPO_ROOT --verbose --skip-install - cd /tmp/$PROJECT_NAME - yarn - + node ./scripts/template/initialize.js --reactNativeRootPath $REPO_ROOT --templateName $PROJECT_NAME --templateConfigPath $REPO_ROOT --directory "/tmp/$PROJECT_NAME" - run: name: Build the template application for << parameters.flavor >> with Architecture set to << parameters.architecture >>, and using the << parameters.jsengine>> JS engine. command: | @@ -923,7 +920,7 @@ jobs: PACKAGE=$(cat build/react-native-package-version) PATH_TO_PACKAGE="$REPO_ROOT/build/$PACKAGE" node ./scripts/set-rn-template-version.js "file:$PATH_TO_PACKAGE" - node cli.js init $PROJECT_NAME --directory "/tmp/$PROJECT_NAME" --template $REPO_ROOT --verbose --skip-install + node ./scripts/template/initialize.js --reactNativeRootPath $REPO_ROOT --templateName $PROJECT_NAME --templateConfigPath $REPO_ROOT --directory "/tmp/$PROJECT_NAME" - run: name: Install iOS dependencies - Configuration << parameters.flavor >>; New Architecture << parameters.architecture >>; JS Engine << parameters.jsengine>>; Flipper << parameters.flipper >> command: | diff --git a/scripts/template/initialize.js b/scripts/template/initialize.js new file mode 100644 index 00000000000000..b0cfaf9365af2b --- /dev/null +++ b/scripts/template/initialize.js @@ -0,0 +1,105 @@ +/** + * 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 + */ + +'use strict'; + +const yargs = require('yargs'); +const {execSync, spawnSync} = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const setupVerdaccio = require('../setup-verdaccio'); + +const {argv} = yargs + .option('r', { + alias: 'reactNativeRootPath', + describe: 'Path to root folder of react-native', + required: true, + }) + .option('n', { + alias: 'templateName', + describe: 'Template App name', + required: true, + }) + .option('tcp', { + alias: 'templateConfigPath', + describe: 'Path to folder containing template config', + required: true, + }) + .option('d', { + alias: 'directory', + describe: 'Path to template application folder', + required: true, + }) + .strict(); + +const {reactNativeRootPath, templateName, templateConfigPath, directory} = argv; + +const VERDACCIO_CONFIG_PATH = `${reactNativeRootPath}/.circleci/verdaccio.yml`; + +function readPackageJSON(pathToPackage) { + return JSON.parse(fs.readFileSync(path.join(pathToPackage, 'package.json'))); +} + +function install() { + const yarnWorkspacesStdout = execSync('yarn --json workspaces info', { + cwd: reactNativeRootPath, + encoding: 'utf8', + }); + const packages = JSON.parse(JSON.parse(yarnWorkspacesStdout).data); + + const VERDACCIO_PID = setupVerdaccio( + reactNativeRootPath, + VERDACCIO_CONFIG_PATH, + ); + process.stdout.write('Bootstrapped Verdaccio \u2705\n'); + + process.stdout.write('Starting to publish all the packages...\n'); + Object.entries(packages).forEach(([packageName, packageEntity]) => { + const packageRelativePath = packageEntity.location; + const packageAbsolutePath = `${reactNativeRootPath}/${packageRelativePath}`; + + const packageManifest = readPackageJSON(packageAbsolutePath); + if (packageManifest.private) { + return; + } + + execSync('npm publish --registry http://localhost:4873 --access public', { + cwd: `${reactNativeRootPath}/${packageEntity.location}`, + stdio: [process.stdin, process.stdout, process.stderr], + }); + + process.stdout.write(`Published ${packageName} to proxy \u2705\n`); + }); + process.stdout.write('Published all packages \u2705\n'); + + execSync( + `node cli.js init ${templateName} --directory ${directory} --template ${templateConfigPath} --verbose --skip-install`, + { + cwd: reactNativeRootPath, + stdio: [process.stdin, process.stdout, process.stderr], + }, + ); + process.stdout.write('Completed initialization of template app \u2705\n'); + + process.stdout.write('Installing dependencies in template app folder...\n'); + spawnSync('yarn', ['install'], { + cwd: directory, + stdio: [process.stdin, process.stdout, process.stderr], + }); + process.stdout.write('Installed dependencies via Yarn \u2705\n'); + + process.stdout.write(`Killing verdaccio. PID — ${VERDACCIO_PID}...\n`); + execSync(`kill -9 ${VERDACCIO_PID}`); + process.stdout.write('Killed Verdaccio process \u2705\n'); + + process.exit(); +} + +install(); From 7e3d13b3ad32ba44e37682ab1c42a08d0ecd2319 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Tue, 13 Dec 2022 13:27:10 -0800 Subject: [PATCH 2/8] feat(react-native-github): automate publishing bumped packages via circleci (#35621) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/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 Reviewed By: cortinico Differential Revision: D41972733 fbshipit-source-id: d9de31365d5a68381d3326674ca12608472a6f96 --- .circleci/config.yml | 33 ++++++- ...nd-and-publish-all-bumped-packages-test.js | 35 +++++++ scripts/__tests__/for-each-package-test.js | 51 ++++++++++ .../find-and-publish-all-bumped-packages.js | 96 +++++++++++++++++++ scripts/monorepo/for-each-package.js | 59 ++++++++++++ 5 files changed, 270 insertions(+), 4 deletions(-) create mode 100644 scripts/__tests__/find-and-publish-all-bumped-packages-test.js create mode 100644 scripts/__tests__/for-each-package-test.js create mode 100644 scripts/monorepo/find-and-publish-all-bumped-packages.js create mode 100644 scripts/monorepo/for-each-package.js diff --git a/.circleci/config.yml b/.circleci/config.yml index c851fe9a78bab0..0fe6c07f777c33 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,6 +34,18 @@ references: attach_workspace: at: *hermes_workspace_root + main_only: &main_only + filters: + branches: + only: main + main_or_stable_only: &main_or_stable_only + filters: + branches: + only: + - main + - /0\.[0-9]+[\.[0-9]+]?-stable/ + + # ------------------------- # Dependency Anchors # ------------------------- @@ -1567,6 +1579,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 @@ -1755,11 +1778,8 @@ workflows: unless: << pipeline.parameters.run_package_release_workflow_only >> triggers: - schedule: + <<: *main_only cron: "0 20 * * *" - filters: - branches: - only: - - main jobs: - nightly_job @@ -1782,3 +1802,8 @@ workflows: - build_hermesc_linux - build_hermes_macos - build_hermesc_windows + + publish_bumped_packages: + jobs: + - find_and_publish_bumped_packages: + <<: *main_or_stable_only diff --git a/scripts/__tests__/find-and-publish-all-bumped-packages-test.js b/scripts/__tests__/find-and-publish-all-bumped-packages-test.js new file mode 100644 index 00000000000000..ee44b354b1e4c9 --- /dev/null +++ b/scripts/__tests__/find-and-publish-all-bumped-packages-test.js @@ -0,0 +1,35 @@ +/** + * 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 {exec} = require('shelljs'); + +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('../monorepo/for-each-package', () => jest.fn()); + +describe('findAndPublishAllBumpedPackages', () => { + it('throws an error if updated version is not 0.x.y', () => { + const mockedPackageNewVersion = '1.0.0'; + + forEachPackage.mockImplementationOnce(callback => { + callback('absolute/path/to/package', 'to/package', { + version: mockedPackageNewVersion, + }); + }); + exec.mockImplementationOnce(() => ({ + stdout: `- "version": "0.72.0"\n+ "version": "${mockedPackageNewVersion}"\n`, + })); + + expect(() => findAndPublishAllBumpedPackages()).toThrow( + `Package version expected to be 0.x.y, but received ${mockedPackageNewVersion}`, + ); + }); +}); diff --git a/scripts/__tests__/for-each-package-test.js b/scripts/__tests__/for-each-package-test.js new file mode 100644 index 00000000000000..23bbb0925772bb --- /dev/null +++ b/scripts/__tests__/for-each-package-test.js @@ -0,0 +1,51 @@ +/** + * 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 forEachPackage = require('../monorepo/for-each-package'); + +jest.mock('fs', () => ({ + readdirSync: jest.fn(), + readFileSync: jest.fn(), +})); + +describe('forEachPackage', () => { + it('executes callback call with parameters', () => { + const callback = jest.fn(); + const mockedPackageManifest = '{"name": "my-new-package"}'; + const mockedParsedPackageManifest = JSON.parse(mockedPackageManifest); + const mockedPackageName = 'my-new-package'; + + readdirSync.mockImplementationOnce(() => [ + {name: mockedPackageName, isDirectory: () => true}, + ]); + readFileSync.mockImplementationOnce(() => mockedPackageManifest); + + forEachPackage(callback); + + expect(callback).toHaveBeenCalledWith( + path.join(__dirname, '..', '..', 'packages', mockedPackageName), + path.join('packages', mockedPackageName), + mockedParsedPackageManifest, + ); + }); + + it('filters react-native folder', () => { + const callback = jest.fn(); + readdirSync.mockImplementationOnce(() => [ + {name: 'react-native', isDirectory: () => true}, + ]); + + forEachPackage(callback); + + expect(callback).not.toHaveBeenCalled(); + }); +}); diff --git a/scripts/monorepo/find-and-publish-all-bumped-packages.js b/scripts/monorepo/find-and-publish-all-bumped-packages.js new file mode 100644 index 00000000000000..b2a75ab0225be5 --- /dev/null +++ b/scripts/monorepo/find-and-publish-all-bumped-packages.js @@ -0,0 +1,96 @@ +/** + * 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 [, previousVersion] = previousVersionPatternMatches; + const nextVersion = packageManifest.version; + + console.log( + `\uD83D\uDCA1 ${chalk.yellow( + packageManifest.name, + )} was updated: ${chalk.red(previousVersion)} -> ${chalk.green( + nextVersion, + )}`, + ); + + if (!nextVersion.startsWith('0.')) { + throw new Error( + `Package version expected to be 0.x.y, but received ${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(); diff --git a/scripts/monorepo/for-each-package.js b/scripts/monorepo/for-each-package.js new file mode 100644 index 00000000000000..10991b5936da1d --- /dev/null +++ b/scripts/monorepo/for-each-package.js @@ -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(packageAbsolutePath, 'package.json')), + ); + + callback(packageAbsolutePath, packageRelativePathFromRoot, packageManifest); + }); +}; + +module.exports = forEachPackage; From a7f2b3b5025217247e7f2611d04a27b5e89113c5 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Tue, 13 Dec 2022 14:05:00 -0800 Subject: [PATCH 3/8] refactor(scripts): use forEachPackage instead of `yarn workspaces info` Summary: Changelog: [Internal] These changes add usage of `forEachPackage` as a replacement for `yarn --json workspaces info`. This is because at some point in release cycle there is a script which removed `workspaces` block from react-native's `package.json`, so `yarn --info workspaces info` produces an error Reviewed By: cortinico Differential Revision: D41996732 fbshipit-source-id: f1f89c6766aed213df6f1fb18305925ff2e4928c --- scripts/run-ci-e2e-tests.js | 22 ++++++++++------ scripts/template/initialize.js | 48 ++++++++++++++-------------------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/scripts/run-ci-e2e-tests.js b/scripts/run-ci-e2e-tests.js index ee4ae4fea95371..44e5c96ae286e3 100644 --- a/scripts/run-ci-e2e-tests.js +++ b/scripts/run-ci-e2e-tests.js @@ -23,7 +23,9 @@ const {cd, cp, echo, exec, exit, mv} = require('shelljs'); const spawn = require('child_process').spawn; const argv = require('yargs').argv; const path = require('path'); -const {setupVerdaccio} = require('./setup-verdaccio'); + +const forEachPackage = require('./monorepo/for-each-package'); +const setupVerdaccio = require('./setup-verdaccio'); const SCRIPTS = __dirname; const ROOT = path.normalize(path.join(__dirname, '..')); @@ -76,14 +78,18 @@ try { VERDACCIO_PID = setupVerdaccio(); describe('Publish packages'); - const packages = JSON.parse( - JSON.parse(exec('yarn --json workspaces info').stdout).data, + forEachPackage( + (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { + if (packageManifest.private) { + return; + } + + exec( + 'npm publish --registry http://localhost:4873 --yes --access public', + {cwd: packageAbsolutePath}, + ); + }, ); - Object.keys(packages).forEach(packageName => { - exec( - `cd ${packages[packageName].location} && npm publish --registry http://localhost:4873 --yes --access public`, - ); - }); describe('Scaffold a basic React Native app from template'); exec(`rsync -a ${ROOT}/template ${REACT_NATIVE_TEMP_DIR}`); diff --git a/scripts/template/initialize.js b/scripts/template/initialize.js index b0cfaf9365af2b..eaba0e4e900373 100644 --- a/scripts/template/initialize.js +++ b/scripts/template/initialize.js @@ -11,9 +11,8 @@ const yargs = require('yargs'); const {execSync, spawnSync} = require('child_process'); -const fs = require('fs'); -const path = require('path'); +const forEachPackage = require('../monorepo/for-each-package'); const setupVerdaccio = require('../setup-verdaccio'); const {argv} = yargs @@ -43,41 +42,32 @@ const {reactNativeRootPath, templateName, templateConfigPath, directory} = argv; const VERDACCIO_CONFIG_PATH = `${reactNativeRootPath}/.circleci/verdaccio.yml`; -function readPackageJSON(pathToPackage) { - return JSON.parse(fs.readFileSync(path.join(pathToPackage, 'package.json'))); -} - function install() { - const yarnWorkspacesStdout = execSync('yarn --json workspaces info', { - cwd: reactNativeRootPath, - encoding: 'utf8', - }); - const packages = JSON.parse(JSON.parse(yarnWorkspacesStdout).data); - const VERDACCIO_PID = setupVerdaccio( reactNativeRootPath, VERDACCIO_CONFIG_PATH, ); process.stdout.write('Bootstrapped Verdaccio \u2705\n'); - process.stdout.write('Starting to publish all the packages...\n'); - Object.entries(packages).forEach(([packageName, packageEntity]) => { - const packageRelativePath = packageEntity.location; - const packageAbsolutePath = `${reactNativeRootPath}/${packageRelativePath}`; - - const packageManifest = readPackageJSON(packageAbsolutePath); - if (packageManifest.private) { - return; - } - - execSync('npm publish --registry http://localhost:4873 --access public', { - cwd: `${reactNativeRootPath}/${packageEntity.location}`, - stdio: [process.stdin, process.stdout, process.stderr], - }); + process.stdout.write('Starting to publish every package...\n'); + forEachPackage( + (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { + if (packageManifest.private) { + return; + } + + execSync('npm publish --registry http://localhost:4873 --access public', { + cwd: packageAbsolutePath, + stdio: [process.stdin, process.stdout, process.stderr], + }); + + process.stdout.write( + `Published ${packageManifest.name} to proxy \u2705\n`, + ); + }, + ); - process.stdout.write(`Published ${packageName} to proxy \u2705\n`); - }); - process.stdout.write('Published all packages \u2705\n'); + process.stdout.write('Published every package \u2705\n'); execSync( `node cli.js init ${templateName} --directory ${directory} --template ${templateConfigPath} --verbose --skip-install`, From 058defb472b7bb889ff48137b5c60234afbcf502 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Tue, 13 Dec 2022 14:04:33 -0800 Subject: [PATCH 4/8] feat(react-native-github): automate publishing bumped packages via circleci Summary: 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: 6a8b8c2995943d6c8421cbe19cd291069655183b From ce9a79e4385a79fdbff877e6868ceaf1f8ee8f95 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Tue, 10 Jan 2023 13:23:06 -0800 Subject: [PATCH 5/8] feat(react-native-github): a script to automate patch version bumping of packages (#35767) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/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 --- package.json | 4 +- ...nd-and-publish-all-bumped-packages-test.js | 12 +- .../__tests__/bump-package-version-test.js | 39 +++++ scripts/monorepo/align-package-versions.js | 146 +++++++++++++++++ .../bump-package-version.js | 52 ++++++ .../bump-all-updated-packages/index.js | 154 ++++++++++++++++++ scripts/monorepo/check-for-git-changes.js | 39 +++++ scripts/monorepo/constants.js | 13 ++ .../find-and-publish-all-bumped-packages.js | 88 +++++++--- 9 files changed, 516 insertions(+), 31 deletions(-) create mode 100644 scripts/monorepo/__tests__/bump-package-version-test.js create mode 100644 scripts/monorepo/align-package-versions.js create mode 100644 scripts/monorepo/bump-all-updated-packages/bump-package-version.js create mode 100644 scripts/monorepo/bump-all-updated-packages/index.js create mode 100644 scripts/monorepo/check-for-git-changes.js create mode 100644 scripts/monorepo/constants.js diff --git a/package.json b/package.json index 95ef29c8e743e4..63212c8af6e42e 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/scripts/__tests__/find-and-publish-all-bumped-packages-test.js b/scripts/__tests__/find-and-publish-all-bumped-packages-test.js index ee44b354b1e4c9..5bf488ea31c56e 100644 --- a/scripts/__tests__/find-and-publish-all-bumped-packages-test.js +++ b/scripts/__tests__/find-and-publish-all-bumped-packages-test.js @@ -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', () => { @@ -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}`, ); diff --git a/scripts/monorepo/__tests__/bump-package-version-test.js b/scripts/monorepo/__tests__/bump-package-version-test.js new file mode 100644 index 00000000000000..dd67ab2b6b5c0f --- /dev/null +++ b/scripts/monorepo/__tests__/bump-package-version-test.js @@ -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', + ); + }); +}); diff --git a/scripts/monorepo/align-package-versions.js b/scripts/monorepo/align-package-versions.js new file mode 100644 index 00000000000000..e29272f324bbd0 --- /dev/null +++ b/scripts/monorepo/align-package-versions.js @@ -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(); diff --git a/scripts/monorepo/bump-all-updated-packages/bump-package-version.js b/scripts/monorepo/bump-all-updated-packages/bump-package-version.js new file mode 100644 index 00000000000000..d403eb9f94e712 --- /dev/null +++ b/scripts/monorepo/bump-all-updated-packages/bump-package-version.js @@ -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; diff --git a/scripts/monorepo/bump-all-updated-packages/index.js b/scripts/monorepo/bump-all-updated-packages/index.js new file mode 100644 index 00000000000000..bec5791460a2ba --- /dev/null +++ b/scripts/monorepo/bump-all-updated-packages/index.js @@ -0,0 +1,154 @@ +/** + * 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 chalk = require('chalk'); +const inquirer = require('inquirer'); +const path = require('path'); +const {echo, exec, exit} = require('shelljs'); + +const {BUMP_COMMIT_MESSAGE} = require('../constants'); +const forEachPackage = require('../for-each-package'); +const checkForGitChanges = require('../check-for-git-changes'); +const bumpPackageVersion = require('./bump-package-version'); + +const ROOT_LOCATION = path.join(__dirname, '..', '..', '..'); + +const buildExecutor = + (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => + async () => { + const {name: packageName} = packageManifest; + if (packageManifest.private) { + echo(`\u23ED Skipping private package ${chalk.dim(packageName)}`); + + return; + } + + const hashOfLastCommitInsidePackage = exec( + `git log -n 1 --format=format:%H -- ${packageRelativePathFromRoot}`, + {cwd: ROOT_LOCATION, silent: true}, + ).stdout.trim(); + + const hashOfLastCommitThatChangedVersion = exec( + `git log -G\\"version\\": --format=format:%H -n 1 -- ${packageRelativePathFromRoot}/package.json`, + {cwd: ROOT_LOCATION, silent: true}, + ).stdout.trim(); + + if (hashOfLastCommitInsidePackage === hashOfLastCommitThatChangedVersion) { + echo( + `\uD83D\uDD0E No changes for package ${chalk.green( + packageName, + )} since last version bump`, + ); + + return; + } + + echo(`\uD83D\uDCA1 Found changes for ${chalk.yellow(packageName)}:`); + exec( + `git log --pretty=oneline ${hashOfLastCommitThatChangedVersion}..${hashOfLastCommitInsidePackage} ${packageRelativePathFromRoot}`, + { + cwd: ROOT_LOCATION, + }, + ); + echo(); + + await inquirer + .prompt([ + { + type: 'list', + name: 'shouldBumpPackage', + message: `Do you want to bump ${packageName}?`, + choices: ['Yes', 'No'], + filter: val => val === 'Yes', + }, + ]) + .then(({shouldBumpPackage}) => { + if (!shouldBumpPackage) { + echo(`Skipping bump for ${packageName}`); + return; + } + + return inquirer + .prompt([ + { + type: 'list', + name: 'increment', + message: 'Which version you want to increment?', + choices: ['patch', 'minor'], + }, + ]) + .then(({increment}) => { + const updatedVersion = bumpPackageVersion( + packageAbsolutePath, + packageManifest, + increment, + ); + echo( + `\u2705 Successfully bumped ${chalk.green( + packageName, + )} to ${chalk.green(updatedVersion)}`, + ); + }); + }); + }; + +const buildAllExecutors = () => { + const executors = []; + + forEachPackage((...params) => { + executors.push(buildExecutor(...params)); + }); + + return executors; +}; + +const main = async () => { + if (checkForGitChanges()) { + echo( + chalk.red( + 'Found uncommitted changes. Please commit or stash them before running this script', + ), + ); + exit(1); + } + + const executors = buildAllExecutors(); + for (const executor of executors) { + await executor() + .catch(() => exit(1)) + .then(() => echo()); + } + + if (checkForGitChanges()) { + await inquirer + .prompt([ + { + type: 'list', + name: 'shouldSubmitCommit', + message: 'Do you want to submit a commit with these changes?', + choices: ['Yes', 'No'], + filter: val => val === 'Yes', + }, + ]) + .then(({shouldSubmitCommit}) => { + if (!shouldSubmitCommit) { + echo('Not submitting a commit, but keeping all changes'); + return; + } + + exec(`git commit -a -m "${BUMP_COMMIT_MESSAGE}"`, {cwd: ROOT_LOCATION}); + }) + .then(() => echo()); + } + + echo(chalk.green('Successfully finished the process of bumping packages')); + exit(0); +}; + +main(); diff --git a/scripts/monorepo/check-for-git-changes.js b/scripts/monorepo/check-for-git-changes.js new file mode 100644 index 00000000000000..77e2d5d0190df0 --- /dev/null +++ b/scripts/monorepo/check-for-git-changes.js @@ -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 {spawnSync} = require('child_process'); +const path = require('path'); + +const ROOT_LOCATION = path.join(__dirname, '..', '..'); + +const checkForGitChanges = () => { + const {stdout: thereIsSomethingToCommit, stderr} = spawnSync( + 'git', + ['status', '--porcelain'], + { + cwd: ROOT_LOCATION, + shell: true, + stdio: 'pipe', + encoding: 'utf-8', + }, + ); + + if (stderr) { + console.log( + '\u274c An error occured while running `git status --porcelain`:', + ); + console.log(stderr); + + process.exit(1); + } + + return Boolean(thereIsSomethingToCommit); +}; + +module.exports = checkForGitChanges; diff --git a/scripts/monorepo/constants.js b/scripts/monorepo/constants.js new file mode 100644 index 00000000000000..fcbc5c51e3c16d --- /dev/null +++ b/scripts/monorepo/constants.js @@ -0,0 +1,13 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +const BUMP_COMMIT_MESSAGE = '[ci][monorepo] bump package versions'; + +module.exports = {BUMP_COMMIT_MESSAGE}; diff --git a/scripts/monorepo/find-and-publish-all-bumped-packages.js b/scripts/monorepo/find-and-publish-all-bumped-packages.js index b2a75ab0225be5..17e06596c3af2b 100644 --- a/scripts/monorepo/find-and-publish-all-bumped-packages.js +++ b/scripts/monorepo/find-and-publish-all-bumped-packages.js @@ -8,9 +8,9 @@ */ const path = require('path'); -const chalk = require('chalk'); -const {exec} = require('shelljs'); +const {spawnSync} = require('child_process'); +const {BUMP_COMMIT_MESSAGE} = require('./constants'); const forEachPackage = require('./for-each-package'); const ROOT_LOCATION = path.join(__dirname, '..', '..'); @@ -22,41 +22,77 @@ const findAndPublishAllBumpedPackages = () => { forEachPackage( (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { if (packageManifest.private) { - console.log( - `\u23ED Skipping private package ${chalk.dim(packageManifest.name)}`, - ); + console.log(`\u23ED Skipping private package ${packageManifest.name}`); return; } - const diff = exec( - `git log -p --format="" HEAD~1..HEAD ${packageRelativePathFromRoot}/package.json`, - {cwd: ROOT_LOCATION, silent: true}, - ).stdout; + const {stdout: diff, stderr: commitDiffStderr} = spawnSync( + 'git', + [ + 'log', + '-p', + '--format=""', + 'HEAD~1..HEAD', + `${packageRelativePathFromRoot}/package.json`, + ], + {cwd: ROOT_LOCATION, shell: true, stdio: 'pipe', encoding: 'utf-8'}, + ); + + if (commitDiffStderr) { + console.log( + `\u274c Failed to get latest committed changes for ${packageManifest.name}:`, + ); + console.log(commitDiffStderr); + + process.exit(1); + } const previousVersionPatternMatches = diff.match( /- {2}"version": "([0-9]+.[0-9]+.[0-9]+)"/, ); if (!previousVersionPatternMatches) { + console.log(`\uD83D\uDD0E No version bump for ${packageManifest.name}`); + + return; + } + + const {stdout: commitMessage, stderr: commitMessageStderr} = spawnSync( + 'git', + [ + 'log', + '-n', + '1', + '--format=format:%B', + `${packageRelativePathFromRoot}/package.json`, + ], + {cwd: ROOT_LOCATION, shell: true, stdio: 'pipe', encoding: 'utf-8'}, + ); + + if (commitMessageStderr) { console.log( - `\uD83D\uDD0E No version bump for ${chalk.green( - packageManifest.name, - )}`, + `\u274c Failed to get latest commit message for ${packageManifest.name}:`, ); + console.log(commitMessageStderr); - return; + process.exit(1); + } + + const hasSpecificCommitMessage = + commitMessage.startsWith(BUMP_COMMIT_MESSAGE); + + if (!hasSpecificCommitMessage) { + throw new Error( + `Package ${packageManifest.name} was updated, but not through CI script`, + ); } const [, previousVersion] = previousVersionPatternMatches; const nextVersion = packageManifest.version; console.log( - `\uD83D\uDCA1 ${chalk.yellow( - packageManifest.name, - )} was updated: ${chalk.red(previousVersion)} -> ${chalk.green( - nextVersion, - )}`, + `\uD83D\uDCA1 ${packageManifest.name} was updated: ${previousVersion} -> ${nextVersion}`, ); if (!nextVersion.startsWith('0.')) { @@ -67,24 +103,22 @@ const findAndPublishAllBumpedPackages = () => { const npmOTPFlag = NPM_CONFIG_OTP ? `--otp ${NPM_CONFIG_OTP}` : ''; - const {code, stderr} = exec(`npm publish ${npmOTPFlag}`, { + const {stderr} = spawnSync('npm', ['publish', `${npmOTPFlag}`], { cwd: packageAbsolutePath, - silent: true, + shell: true, + stdio: 'pipe', + encoding: 'utf-8', }); - if (code) { + if (stderr) { console.log( - chalk.red( - `\u274c Failed to publish version ${nextVersion} of ${packageManifest.name}. Stderr:`, - ), + `\u274c Failed to publish version ${nextVersion} of ${packageManifest.name}:`, ); console.log(stderr); process.exit(1); } else { console.log( - `\u2705 Successfully published new version of ${chalk.green( - packageManifest.name, - )}`, + `\u2705 Successfully published new version of ${packageManifest.name}`, ); } }, From 6a3752eecec9e6dc17ad9539e75e369cf7e73848 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Tue, 10 Jan 2023 13:23:06 -0800 Subject: [PATCH 6/8] refactor(scripts/monorepo/tests): moved related tests to scripts/monorepo/__tests__ Summary: Changelog: [Internal] just a small cleanup, moved all tests related to monorepo scripts under `/scripts/monorepo` Reviewed By: cortinico Differential Revision: D42308455 fbshipit-source-id: 7743a2af1381ff748556c15b89cf980f8f907674 --- .../find-and-publish-all-bumped-packages-test.js | 8 ++++---- scripts/{ => monorepo}/__tests__/for-each-package-test.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename scripts/{ => monorepo}/__tests__/find-and-publish-all-bumped-packages-test.js (78%) rename scripts/{ => monorepo}/__tests__/for-each-package-test.js (90%) diff --git a/scripts/__tests__/find-and-publish-all-bumped-packages-test.js b/scripts/monorepo/__tests__/find-and-publish-all-bumped-packages-test.js similarity index 78% rename from scripts/__tests__/find-and-publish-all-bumped-packages-test.js rename to scripts/monorepo/__tests__/find-and-publish-all-bumped-packages-test.js index 5bf488ea31c56e..0a50307646d6b7 100644 --- a/scripts/__tests__/find-and-publish-all-bumped-packages-test.js +++ b/scripts/monorepo/__tests__/find-and-publish-all-bumped-packages-test.js @@ -9,12 +9,12 @@ 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'); +const {BUMP_COMMIT_MESSAGE} = require('../constants'); +const forEachPackage = require('../for-each-package'); +const findAndPublishAllBumpedPackages = require('../find-and-publish-all-bumped-packages'); jest.mock('child_process', () => ({spawnSync: jest.fn()})); -jest.mock('../monorepo/for-each-package', () => jest.fn()); +jest.mock('../for-each-package', () => jest.fn()); describe('findAndPublishAllBumpedPackages', () => { it('throws an error if updated version is not 0.x.y', () => { diff --git a/scripts/__tests__/for-each-package-test.js b/scripts/monorepo/__tests__/for-each-package-test.js similarity index 90% rename from scripts/__tests__/for-each-package-test.js rename to scripts/monorepo/__tests__/for-each-package-test.js index 23bbb0925772bb..34ba8ecdb1c8a6 100644 --- a/scripts/__tests__/for-each-package-test.js +++ b/scripts/monorepo/__tests__/for-each-package-test.js @@ -10,7 +10,7 @@ const path = require('path'); const {readdirSync, readFileSync} = require('fs'); -const forEachPackage = require('../monorepo/for-each-package'); +const forEachPackage = require('../for-each-package'); jest.mock('fs', () => ({ readdirSync: jest.fn(), @@ -32,7 +32,7 @@ describe('forEachPackage', () => { forEachPackage(callback); expect(callback).toHaveBeenCalledWith( - path.join(__dirname, '..', '..', 'packages', mockedPackageName), + path.join(__dirname, '..', '..', '..', 'packages', mockedPackageName), path.join('packages', mockedPackageName), mockedParsedPackageManifest, ); From 1ca223f8b56026223d26368447a8e18dc140c44e Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 12 Jan 2023 03:35:41 -0800 Subject: [PATCH 7/8] refactor(bump-all-updated-packages): support force-bump to next minor version of the package (#35810) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/35810 Changelog: [Internal] `bump-all-updated-packages` script now supports `release-branch-cutoff` argument If this argument is provided, the script will bump each public package to next minor version Updated [wiki](https://github.com/facebook/react-native/wiki/Release-and-its-automated-processes) with relevant information about how to use it Reviewed By: cortinico Differential Revision: D42455329 fbshipit-source-id: a40d2f5dc356f22d3182da3a118c6383b634817d --- .../bump-all-updated-packages/index.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/scripts/monorepo/bump-all-updated-packages/index.js b/scripts/monorepo/bump-all-updated-packages/index.js index bec5791460a2ba..953851d7db1d5f 100644 --- a/scripts/monorepo/bump-all-updated-packages/index.js +++ b/scripts/monorepo/bump-all-updated-packages/index.js @@ -11,6 +11,7 @@ const chalk = require('chalk'); const inquirer = require('inquirer'); const path = require('path'); const {echo, exec, exit} = require('shelljs'); +const yargs = require('yargs'); const {BUMP_COMMIT_MESSAGE} = require('../constants'); const forEachPackage = require('../for-each-package'); @@ -19,6 +20,15 @@ const bumpPackageVersion = require('./bump-package-version'); const ROOT_LOCATION = path.join(__dirname, '..', '..', '..'); +const { + argv: {releaseBranchCutoff}, +} = yargs + .option('release-branch-cutoff', { + type: 'boolean', + describe: 'Should force bump minor version for each public package', + }) + .strict(); + const buildExecutor = (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => async () => { @@ -29,6 +39,21 @@ const buildExecutor = return; } + if (releaseBranchCutoff) { + const updatedVersion = bumpPackageVersion( + packageAbsolutePath, + packageManifest, + 'minor', + ); + echo( + `\u2705 Successfully bumped ${chalk.green( + packageName, + )} to ${chalk.green(updatedVersion)}`, + ); + + return; + } + const hashOfLastCommitInsidePackage = exec( `git log -n 1 --format=format:%H -- ${packageRelativePathFromRoot}`, {cwd: ROOT_LOCATION, silent: true}, From 526c1941df298550b578501a05614f8e48fdcb6b Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Wed, 23 Nov 2022 13:20:00 -0800 Subject: [PATCH 8/8] feat(react-native/template): use verdaccio to publish local packages before testing template app (#35444) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/35444 Changelog: [Internal][Changed] - now using Verdaccio to publish necessary packages for template app - Adds script `/scripts/template/install-dependencies.js`, which incapsulates the logic of installing dependencies of template app - The idea of the script is to run verdaccio and publish all necessary packages to node_modules, since these packages might not yet be present on npm - This should also potentially resolve some template app test failures on CircleCI related to package-ifying Animated, VirtualizedList, FlatList modules Reviewed By: cortinico Differential Revision: D41498086 fbshipit-source-id: 48fbbb1c9334e7a9e7657e6275b7b04f9ce290b5 --- .circleci/config.yml | 4 +-- scripts/run-ci-e2e-tests.js | 5 +++- scripts/setup-verdaccio.js | 49 +++++++++++++++++++++++++------------ 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0fe6c07f777c33..7345fe18dc74e7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -936,9 +936,7 @@ jobs: - run: name: Install iOS dependencies - Configuration << parameters.flavor >>; New Architecture << parameters.architecture >>; JS Engine << parameters.jsengine>>; Flipper << parameters.flipper >> command: | - cd /tmp/$PROJECT_NAME - yarn install - cd ios + cd /tmp/$PROJECT_NAME/ios bundle install diff --git a/scripts/run-ci-e2e-tests.js b/scripts/run-ci-e2e-tests.js index 44e5c96ae286e3..a94e1be4468860 100644 --- a/scripts/run-ci-e2e-tests.js +++ b/scripts/run-ci-e2e-tests.js @@ -36,6 +36,9 @@ const REACT_NATIVE_TEMP_DIR = exec( ).stdout.trim(); const REACT_NATIVE_APP_DIR = `${REACT_NATIVE_TEMP_DIR}/template`; const numberOfRetries = argv.retries || 1; + +const VERDACCIO_CONFIG_PATH = path.join(ROOT, '.circleci/verdaccio.yml'); + let SERVER_PID; let APPIUM_PID; let VERDACCIO_PID; @@ -75,7 +78,7 @@ try { const REACT_NATIVE_PACKAGE = path.join(ROOT, 'react-native-*.tgz'); describe('Set up Verdaccio'); - VERDACCIO_PID = setupVerdaccio(); + VERDACCIO_PID = setupVerdaccio(ROOT, VERDACCIO_CONFIG_PATH); describe('Publish packages'); forEachPackage( diff --git a/scripts/setup-verdaccio.js b/scripts/setup-verdaccio.js index e62e7918540d2a..3dc32a261dbe60 100644 --- a/scripts/setup-verdaccio.js +++ b/scripts/setup-verdaccio.js @@ -9,22 +9,41 @@ 'use strict'; -const {exec} = require('shelljs'); -const spawn = require('child_process').spawn; - -function setupVerdaccio() { - const verdaccioProcess = spawn('npx', [ - 'verdaccio@5.15.3', - '--config', - '.circleci/verdaccio.yml', - ]); +const {execSync, spawn} = require('child_process'); + +function setupVerdaccio( + reactNativeRootPath, // Path to React Native root folder + verdaccioConfigPath, // Path to Verdaccio config file, which you want to use for bootstrapping Verdaccio + verdaccioStoragePath, // Path to Verdaccio storage, where it should keep packages. Optional. Default value will be decided by your Verdaccio config +) { + if (!reactNativeRootPath) { + throw new Error( + 'Path to React Native repo root is not specified. You should provide it as a first argument', + ); + } + + if (!verdaccioConfigPath) { + throw new Error( + 'Path to Verdaccio config is not specified. You should provide it as a second argument', + ); + } + + const verdaccioProcess = spawn( + 'npx', + ['verdaccio@5.16.3', '--config', verdaccioConfigPath], + {env: {...process.env, VERDACCIO_STORAGE_PATH: verdaccioStoragePath}}, + ); + const VERDACCIO_PID = verdaccioProcess.pid; - exec('npx wait-on@6.0.1 http://localhost:4873'); - exec('npm set registry http://localhost:4873'); - exec('echo "//localhost:4873/:_authToken=secretToken" > .npmrc'); + + execSync('npx wait-on@6.0.1 http://localhost:4873'); + + execSync('npm set registry http://localhost:4873'); + execSync('echo "//localhost:4873/:_authToken=secretToken" > .npmrc', { + cwd: reactNativeRootPath, + }); + return VERDACCIO_PID; } -module.exports = { - setupVerdaccio: setupVerdaccio, -}; +module.exports = setupVerdaccio;