From d6bf51cad98765a715ac703653fbce5b9d40bb50 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Tue, 20 Feb 2024 09:55:46 -0800 Subject: [PATCH] Refactor remaining forEachPackage call sites (#43112) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43112 Changelog: [Internal] Reviewed By: cipolleschi Differential Revision: D53942028 fbshipit-source-id: 335bff3c3a31026bae7140fac1d1a6aae23a0f1e --- scripts/e2e/init-template-e2e.js | 43 +- scripts/e2e/run-ci-e2e-tests.js | 457 +++++++++--------- .../__tests__/bump-package-version-test.js | 2 - .../__tests__/for-each-package-test.js | 70 --- scripts/monorepo/align-package-versions.js | 51 +- .../bump-all-updated-packages/index.js | 41 +- scripts/monorepo/for-each-package.js | 69 --- scripts/monorepo/print/index.js | 68 +-- .../releases-ci/__tests__/publish-npm-test.js | 6 +- .../publish-updated-packages-test.js | 2 +- scripts/releases-ci/publish-npm.js | 2 +- .../releases-ci/publish-updated-packages.js | 2 +- .../trigger-react-native-release.js | 27 +- scripts/releases/set-version/index.js | 4 +- scripts/{releases => }/utils/monorepo.js | 6 +- 15 files changed, 370 insertions(+), 480 deletions(-) delete mode 100644 scripts/monorepo/__tests__/for-each-package-test.js delete mode 100644 scripts/monorepo/for-each-package.js rename scripts/{releases => }/utils/monorepo.js (91%) diff --git a/scripts/e2e/init-template-e2e.js b/scripts/e2e/init-template-e2e.js index 49542fcb730..44c96ea0387 100644 --- a/scripts/e2e/init-template-e2e.js +++ b/scripts/e2e/init-template-e2e.js @@ -13,7 +13,7 @@ const {retry} = require('../circleci/retry'); const {REPO_ROOT} = require('../consts'); -const forEachPackage = require('../monorepo/for-each-package'); +const {getPackages} = require('../utils/monorepo'); const { VERDACCIO_SERVER_URL, VERDACCIO_STORAGE_PATH, @@ -22,6 +22,7 @@ const { const {parseArgs} = require('@pkgjs/parseargs'); const chalk = require('chalk'); const {execSync} = require('child_process'); +const path = require('path'); const config = { options: { @@ -86,26 +87,28 @@ async function initNewProjectFromSource( console.log('\nDone ✅'); console.log('Publishing packages to local npm proxy\n'); - forEachPackage( - (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { - if (packageManifest.private) { - return; - } + const packages = await getPackages({ + includeReactNative: false, + includePrivate: false, + }); - const desc = `${packageManifest.name} (${packageRelativePathFromRoot})`; - process.stdout.write( - `${desc} ${chalk.dim('.').repeat(Math.max(0, 72 - desc.length))} `, - ); - execSync( - `npm publish --registry ${VERDACCIO_SERVER_URL} --access public`, - { - cwd: packageAbsolutePath, - stdio: verbose ? 'inherit' : [process.stderr], - }, - ); - process.stdout.write(chalk.reset.inverse.bold.green(' DONE ') + '\n'); - }, - ); + for (const {path: packagePath, packageJson} of Object.values(packages)) { + const desc = `${packageJson.name} (${path.relative( + REPO_ROOT, + packagePath, + )})`; + process.stdout.write( + `${desc} ${chalk.dim('.').repeat(Math.max(0, 72 - desc.length))} `, + ); + execSync( + `npm publish --registry ${VERDACCIO_SERVER_URL} --access public`, + { + cwd: packagePath, + stdio: verbose ? 'inherit' : [process.stderr], + }, + ); + process.stdout.write(chalk.reset.inverse.bold.green(' DONE ') + '\n'); + } console.log('\nDone ✅'); console.log('Running react-native init without install'); diff --git a/scripts/e2e/run-ci-e2e-tests.js b/scripts/e2e/run-ci-e2e-tests.js index d76d7a1b667..ad3f6d16612 100644 --- a/scripts/e2e/run-ci-e2e-tests.js +++ b/scripts/e2e/run-ci-e2e-tests.js @@ -22,7 +22,7 @@ */ const {REACT_NATIVE_PACKAGE_DIR, REPO_ROOT, SCRIPTS_DIR} = require('../consts'); -const forEachPackage = require('../monorepo/for-each-package'); +const {getPackages} = require('../utils/monorepo'); const tryExecNTimes = require('./utils/try-n-times'); const {setupVerdaccio} = require('./utils/verdaccio'); const {execFileSync, spawn} = require('child_process'); @@ -47,275 +47,282 @@ function describe(message /*: string */) { echo(`\n\n>>>>> ${message}\n\n\n`); } -try { - if (argv.android) { - describe('Compile Android binaries'); +async function main() { + try { + if (argv.android) { + describe('Compile Android binaries'); + if ( + exec( + './gradlew publishAllToMavenTempLocal -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"', + ).code + ) { + throw new Error('Failed to compile Android binaries'); + } + } + + describe('Create react-native package'); if ( exec( - './gradlew publishAllToMavenTempLocal -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"', + 'node ./scripts/releases/set-rn-version.js --to-version 1000.0.0 --build-type dry-run', ).code ) { - throw new Error('Failed to compile Android binaries'); + throw new Error( + 'Failed to set version and update package.json ready for release', + ); } - } - describe('Create react-native package'); - if ( - exec( - 'node ./scripts/releases/set-rn-version.js --to-version 1000.0.0 --build-type dry-run', - ).code - ) { - throw new Error( - 'Failed to set version and update package.json ready for release', + if (exec('npm pack', {cwd: REACT_NATIVE_PACKAGE_DIR}).code) { + throw new Error('Failed to pack react-native'); + } + + const REACT_NATIVE_PACKAGE = path.join( + REACT_NATIVE_PACKAGE_DIR, + 'react-native-*.tgz', ); - } - if (exec('npm pack', {cwd: REACT_NATIVE_PACKAGE_DIR}).code) { - throw new Error('Failed to pack react-native'); - } + describe('Set up Verdaccio'); + VERDACCIO_PID = setupVerdaccio(); - const REACT_NATIVE_PACKAGE = path.join( - REACT_NATIVE_PACKAGE_DIR, - 'react-native-*.tgz', - ); + describe('Build and publish packages'); + exec('node ./scripts/build/build.js', {cwd: REPO_ROOT}); - describe('Set up Verdaccio'); - VERDACCIO_PID = setupVerdaccio(); - - describe('Build and publish packages'); - exec('node ./scripts/build/build.js', {cwd: REPO_ROOT}); - - forEachPackage( - (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { - if (packageManifest.private) { - return; - } + const packages = await getPackages({ + includeReactNative: false, + includePrivate: false, + }); + for (const {path: packageAbsolutePath} of Object.values(packages)) { exec( 'npm publish --registry http://localhost:4873 --yes --access public', {cwd: packageAbsolutePath}, ); - }, - ); - - describe('Scaffold a basic React Native app from template'); - execFileSync('rsync', [ - '-a', - `${REPO_ROOT}/packages/react-native/template`, - REACT_NATIVE_TEMP_DIR, - ]); - cd(REACT_NATIVE_APP_DIR); - - mv('_bundle', '.bundle'); - mv('_eslintrc.js', '.eslintrc.js'); - mv('_prettierrc.js', '.prettierrc.js'); - mv('_watchmanconfig', '.watchmanconfig'); - fs.writeFileSync('.npmrc', 'registry=http://localhost:4873'); - - describe('Install React Native package'); - exec(`npm install ${REACT_NATIVE_PACKAGE}`); - - describe('Install node_modules'); - if ( - tryExecNTimes( - () => { - return exec('npm install').code; - }, - numberOfRetries, - () => exec('sleep 10s'), - ) - ) { - throw new Error( - 'Failed to execute npm install. Most common reason is npm registry connectivity, try again', - ); - } - exec('rm -rf ./node_modules/react-native/template'); - - if (argv.android) { - describe('Install end-to-end framework'); - if ( - tryExecNTimes( - () => - exec( - 'yarn add --dev appium@1.11.1 mocha@2.4.5 wd@1.11.1 colors@1.0.3 pretty-data2@0.40.1', - {silent: true}, - ).code, - numberOfRetries, - ) - ) { - throw new Error( - 'Failed to install appium. Most common reason is npm registry connectivity, try again.', - ); - } - cp(`${SCRIPTS_DIR}/android-e2e-test.js`, 'android-e2e-test.js'); - cd('android'); - describe('Download Maven deps'); - exec('./gradlew :app:copyDownloadableDepsToLibs'); - cd('..'); - - describe('Generate key'); - exec('rm android/app/debug.keystore'); - if ( - exec( - 'keytool -genkey -v -keystore android/app/debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US"', - ).code - ) { - throw new Error('Key could not be generated'); } - // $FlowFixMe[incompatible-type] - describe(`Start appium server, ${APPIUM_PID}`); - const appiumProcess = spawn('node', ['./node_modules/.bin/appium']); - APPIUM_PID = appiumProcess.pid; + describe('Scaffold a basic React Native app from template'); + execFileSync('rsync', [ + '-a', + `${REPO_ROOT}/packages/react-native/template`, + REACT_NATIVE_TEMP_DIR, + ]); + cd(REACT_NATIVE_APP_DIR); - describe('Build the app'); - if (exec('react-native run-android').code) { - throw new Error('Could not execute react-native run-android'); - } + mv('_bundle', '.bundle'); + mv('_eslintrc.js', '.eslintrc.js'); + mv('_prettierrc.js', '.prettierrc.js'); + mv('_watchmanconfig', '.watchmanconfig'); + fs.writeFileSync('.npmrc', 'registry=http://localhost:4873'); - // $FlowFixMe[incompatible-type] - describe(`Start Metro, ${SERVER_PID}`); - // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn - const packagerProcess = spawn('yarn', ['start', '--max-workers 1']); - SERVER_PID = packagerProcess.pid; - // wait a bit to allow packager to startup - exec('sleep 15s'); - describe('Test: Android end-to-end test'); + describe('Install React Native package'); + exec(`npm install ${REACT_NATIVE_PACKAGE}`); + + describe('Install node_modules'); if ( tryExecNTimes( () => { - return exec('node node_modules/.bin/_mocha android-e2e-test.js').code; + return exec('npm install').code; }, numberOfRetries, () => exec('sleep 10s'), ) ) { throw new Error( - 'Failed to run Android end-to-end tests. Most likely the code is broken.', + 'Failed to execute npm install. Most common reason is npm registry connectivity, try again', ); } - } + exec('rm -rf ./node_modules/react-native/template'); - if (argv.ios) { - cd('ios'); - // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn - const packagerEnv = Object.create(process.env); - // $FlowFixMe[prop-missing] - packagerEnv.REACT_NATIVE_MAX_WORKERS = 1; - describe('Start Metro'); - const packagerProcess = spawn('yarn', ['start'], { - stdio: 'inherit', - env: packagerEnv, - }); - SERVER_PID = packagerProcess.pid; - exec('sleep 15s'); - // prepare cache to reduce chances of possible red screen "Can't find variable __fbBatchedBridge..." - exec( - 'response=$(curl --write-out %{http_code} --silent --output /dev/null localhost:8081/index.bundle?platform=ios&dev=true)', - ); - echo(`Metro is running, ${SERVER_PID}`); + if (argv.android) { + describe('Install end-to-end framework'); + if ( + tryExecNTimes( + () => + exec( + 'yarn add --dev appium@1.11.1 mocha@2.4.5 wd@1.11.1 colors@1.0.3 pretty-data2@0.40.1', + {silent: true}, + ).code, + numberOfRetries, + ) + ) { + throw new Error( + 'Failed to install appium. Most common reason is npm registry connectivity, try again.', + ); + } + cp(`${SCRIPTS_DIR}/android-e2e-test.js`, 'android-e2e-test.js'); + cd('android'); + describe('Download Maven deps'); + exec('./gradlew :app:copyDownloadableDepsToLibs'); + cd('..'); - describe('Install CocoaPod dependencies'); - exec('bundle exec pod install'); + describe('Generate key'); + exec('rm android/app/debug.keystore'); + if ( + exec( + 'keytool -genkey -v -keystore android/app/debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US"', + ).code + ) { + throw new Error('Key could not be generated'); + } - describe('Test: iOS end-to-end test'); - if ( - // TODO: Get target OS and simulator from .tests.env - tryExecNTimes( - () => { - return exec( - [ - 'xcodebuild', - '-workspace', - '"HelloWorld.xcworkspace"', - '-destination', - '"platform=iOS Simulator,name=iPhone 8,OS=13.3"', - '-scheme', - '"HelloWorld"', - '-sdk', - 'iphonesimulator', - '-UseModernBuildSystem=NO', - 'test', - ].join(' ') + - ' | ' + + // $FlowFixMe[incompatible-type] + describe(`Start appium server, ${APPIUM_PID}`); + const appiumProcess = spawn('node', ['./node_modules/.bin/appium']); + APPIUM_PID = appiumProcess.pid; + + describe('Build the app'); + if (exec('react-native run-android').code) { + throw new Error('Could not execute react-native run-android'); + } + + // $FlowFixMe[incompatible-type] + describe(`Start Metro, ${SERVER_PID}`); + // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn + const packagerProcess = spawn('yarn', ['start', '--max-workers 1']); + SERVER_PID = packagerProcess.pid; + // wait a bit to allow packager to startup + exec('sleep 15s'); + describe('Test: Android end-to-end test'); + if ( + tryExecNTimes( + () => { + return exec('node node_modules/.bin/_mocha android-e2e-test.js') + .code; + }, + numberOfRetries, + () => exec('sleep 10s'), + ) + ) { + throw new Error( + 'Failed to run Android end-to-end tests. Most likely the code is broken.', + ); + } + } + + if (argv.ios) { + cd('ios'); + // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn + const packagerEnv = Object.create(process.env); + // $FlowFixMe[prop-missing] + packagerEnv.REACT_NATIVE_MAX_WORKERS = 1; + describe('Start Metro'); + const packagerProcess = spawn('yarn', ['start'], { + stdio: 'inherit', + env: packagerEnv, + }); + SERVER_PID = packagerProcess.pid; + exec('sleep 15s'); + // prepare cache to reduce chances of possible red screen "Can't find variable __fbBatchedBridge..." + exec( + 'response=$(curl --write-out %{http_code} --silent --output /dev/null localhost:8081/index.bundle?platform=ios&dev=true)', + ); + echo(`Metro is running, ${SERVER_PID}`); + + describe('Install CocoaPod dependencies'); + exec('bundle exec pod install'); + + describe('Test: iOS end-to-end test'); + if ( + // TODO: Get target OS and simulator from .tests.env + tryExecNTimes( + () => { + return exec( [ - 'xcbeautify', - '--report', - 'junit', - '--reportPath', - '"~/react-native/reports/junit/iOS-e2e/results.xml"', + 'xcodebuild', + '-workspace', + '"HelloWorld.xcworkspace"', + '-destination', + '"platform=iOS Simulator,name=iPhone 8,OS=13.3"', + '-scheme', + '"HelloWorld"', + '-sdk', + 'iphonesimulator', + '-UseModernBuildSystem=NO', + 'test', ].join(' ') + - ' && exit ${PIPESTATUS[0]}', - ).code; - }, - numberOfRetries, - () => exec('sleep 10s'), - ) - ) { - throw new Error( - 'Failed to run iOS end-to-end tests. Most likely the code is broken.', - ); - } - cd('..'); - } - - if (argv.js) { - // Check the packager produces a bundle (doesn't throw an error) - describe('Test: Verify packager can generate an Android bundle'); - if ( - exec( - 'yarn react-native bundle --verbose --entry-file index.js --platform android --dev true --bundle-output android-bundle.js --max-workers 1', - ).code - ) { - throw new Error('Could not build Android bundle'); + ' | ' + + [ + 'xcbeautify', + '--report', + 'junit', + '--reportPath', + '"~/react-native/reports/junit/iOS-e2e/results.xml"', + ].join(' ') + + ' && exit ${PIPESTATUS[0]}', + ).code; + }, + numberOfRetries, + () => exec('sleep 10s'), + ) + ) { + throw new Error( + 'Failed to run iOS end-to-end tests. Most likely the code is broken.', + ); + } + cd('..'); } - describe('Test: Verify packager can generate an iOS bundle'); - if ( - exec( - 'yarn react-native bundle --entry-file index.js --platform ios --dev true --bundle-output ios-bundle.js --max-workers 1', - ).code - ) { - throw new Error('Could not build iOS bundle'); - } + if (argv.js) { + // Check the packager produces a bundle (doesn't throw an error) + describe('Test: Verify packager can generate an Android bundle'); + if ( + exec( + 'yarn react-native bundle --verbose --entry-file index.js --platform android --dev true --bundle-output android-bundle.js --max-workers 1', + ).code + ) { + throw new Error('Could not build Android bundle'); + } - describe('Test: TypeScript typechecking'); - if (exec('yarn tsc').code) { - throw new Error('Typechecking errors were found'); - } + describe('Test: Verify packager can generate an iOS bundle'); + if ( + exec( + 'yarn react-native bundle --entry-file index.js --platform ios --dev true --bundle-output ios-bundle.js --max-workers 1', + ).code + ) { + throw new Error('Could not build iOS bundle'); + } - describe('Test: Jest tests'); - if (exec('yarn test').code) { - throw new Error('Jest tests failed'); - } + describe('Test: TypeScript typechecking'); + if (exec('yarn tsc').code) { + throw new Error('Typechecking errors were found'); + } - // TODO: ESLint infinitely hangs when running in the environment created by - // this script, but not projects generated by `react-native init`. - /* + describe('Test: Jest tests'); + if (exec('yarn test').code) { + throw new Error('Jest tests failed'); + } + + // TODO: ESLint infinitely hangs when running in the environment created by + // this script, but not projects generated by `react-native init`. + /* describe('Test: ESLint/Prettier linting and formatting'); if (exec('yarn lint').code) { echo('linting errors were found'); exitCode = 1; throw Error(exitCode); }*/ + } + exitCode = 0; + } finally { + describe('Clean up'); + if (SERVER_PID) { + echo(`Killing packager ${SERVER_PID}`); + exec(`kill -9 ${SERVER_PID}`); + // this is quite drastic but packager starts a daemon that we can't kill by killing the parent process + // it will be fixed in April (quote David Aurelio), so until then we will kill the zombie by the port number + exec("lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill"); + } + if (APPIUM_PID) { + echo(`Killing appium ${APPIUM_PID}`); + exec(`kill -9 ${APPIUM_PID}`); + } + if (VERDACCIO_PID) { + echo(`Killing verdaccio ${VERDACCIO_PID}`); + exec(`kill -9 ${VERDACCIO_PID}`); + } } - exitCode = 0; -} finally { - describe('Clean up'); - if (SERVER_PID) { - echo(`Killing packager ${SERVER_PID}`); - exec(`kill -9 ${SERVER_PID}`); - // this is quite drastic but packager starts a daemon that we can't kill by killing the parent process - // it will be fixed in April (quote David Aurelio), so until then we will kill the zombie by the port number - exec("lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill"); - } - if (APPIUM_PID) { - echo(`Killing appium ${APPIUM_PID}`); - exec(`kill -9 ${APPIUM_PID}`); - } - if (VERDACCIO_PID) { - echo(`Killing verdaccio ${VERDACCIO_PID}`); - exec(`kill -9 ${VERDACCIO_PID}`); - } + exit(exitCode); +} + +if (require.main === module) { + // eslint-disable-next-line no-void + void main(); } -exit(exitCode); diff --git a/scripts/monorepo/__tests__/bump-package-version-test.js b/scripts/monorepo/__tests__/bump-package-version-test.js index 1481e5ac408..00dcccb7359 100644 --- a/scripts/monorepo/__tests__/bump-package-version-test.js +++ b/scripts/monorepo/__tests__/bump-package-version-test.js @@ -16,8 +16,6 @@ jest.mock('fs', () => ({ readFileSync: jest.fn(() => '{}'), })); -jest.mock('../for-each-package', () => callback => {}); - describe('bumpPackageVersionTest', () => { it('updates patch version of the package', () => { const mockedPackageLocation = '~/packages/assets'; diff --git a/scripts/monorepo/__tests__/for-each-package-test.js b/scripts/monorepo/__tests__/for-each-package-test.js deleted file mode 100644 index c3154d61940..00000000000 --- a/scripts/monorepo/__tests__/for-each-package-test.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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 forEachPackage = require('../for-each-package'); -const {readdirSync, readFileSync} = require('fs'); -const path = require('path'); - -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 by default', () => { - const callback = jest.fn(); - readdirSync.mockImplementationOnce(() => [ - {name: 'react-native', isDirectory: () => true}, - ]); - - forEachPackage(callback); - - expect(callback).not.toHaveBeenCalled(); - }); - - it('includes react-native, if such option is provided', () => { - const callback = jest.fn(); - const mockedPackageManifest = '{"name": "react-native"}'; - const mockedParsedPackageManifest = JSON.parse(mockedPackageManifest); - const mockedPackageName = 'react-native'; - - readdirSync.mockImplementationOnce(() => [ - {name: 'react-native', isDirectory: () => true}, - ]); - readFileSync.mockImplementationOnce(() => mockedPackageManifest); - - forEachPackage(callback, {includeReactNative: true}); - - expect(callback).toHaveBeenCalledWith( - path.join(__dirname, '..', '..', '..', 'packages', mockedPackageName), - path.join('packages', mockedPackageName), - mockedParsedPackageManifest, - ); - }); -}); diff --git a/scripts/monorepo/align-package-versions.js b/scripts/monorepo/align-package-versions.js index d4ca5fc36b6..b30d4088b3f 100644 --- a/scripts/monorepo/align-package-versions.js +++ b/scripts/monorepo/align-package-versions.js @@ -4,10 +4,16 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow * @format + * @oncall react_native */ -const forEachPackage = require('./for-each-package'); +/*:: +import type {PackageJson} from '../utils/monorepo'; +*/ + +const {getPackages} = require('../utils/monorepo'); const {readFileSync, writeFileSync} = require('fs'); const path = require('path'); @@ -19,12 +25,13 @@ const TEMPLATE_LOCATION = path.join( 'template', ); -const readJSONFile = pathToFile => JSON.parse(readFileSync(pathToFile)); +const readJSONFile = (pathToFile /*: string */) /*: PackageJson */ => + JSON.parse(readFileSync(pathToFile, 'utf-8')); const checkIfShouldUpdateDependencyPackageVersion = ( - consumerPackageAbsolutePath, - updatedPackageName, - updatedPackageVersion, + consumerPackageAbsolutePath /*: string */, + updatedPackageName /*: string */, + updatedPackageVersion /*: string */, ) => { const consumerPackageManifestPath = path.join( consumerPackageAbsolutePath, @@ -91,8 +98,18 @@ const checkIfShouldUpdateDependencyPackageVersion = ( } }; -const alignPackageVersions = () => { - forEachPackage((_, __, packageManifest) => { +async function alignPackageVersions() { + const allPackages = await getPackages({ + includeReactNative: true, + includePrivate: true, + }); + const packagesExcludingReactNative = Object.keys(allPackages).filter( + packageName => packageName !== 'react-native', + ); + + for (const packageName of packagesExcludingReactNative) { + const {packageJson: packageManifest} = allPackages[packageName]; + checkIfShouldUpdateDependencyPackageVersion( ROOT_LOCATION, packageManifest.name, @@ -105,16 +122,14 @@ const alignPackageVersions = () => { packageManifest.version, ); - forEachPackage( - pathToPackage => - checkIfShouldUpdateDependencyPackageVersion( - pathToPackage, - packageManifest.name, - packageManifest.version, - ), - {includeReactNative: true}, - ); - }); -}; + for (const {path: pathToPackage} of Object.values(allPackages)) { + checkIfShouldUpdateDependencyPackageVersion( + pathToPackage, + packageManifest.name, + packageManifest.version, + ); + } + } +} module.exports = alignPackageVersions; diff --git a/scripts/monorepo/bump-all-updated-packages/index.js b/scripts/monorepo/bump-all-updated-packages/index.js index b619c3129c4..ae8bb555a6b 100644 --- a/scripts/monorepo/bump-all-updated-packages/index.js +++ b/scripts/monorepo/bump-all-updated-packages/index.js @@ -9,12 +9,14 @@ * @oncall react_native */ +const {REPO_ROOT} = require('../../consts'); const {getPackageVersionStrByTag} = require('../../npm-utils'); const { isReleaseBranch, parseVersion, } = require('../../releases/utils/version-utils'); const {getBranchName} = require('../../scm-utils'); +const {getPackages} = require('../../utils/monorepo'); const alignPackageVersions = require('../align-package-versions'); const checkForGitChanges = require('../check-for-git-changes'); const { @@ -24,7 +26,6 @@ const { NO_COMMIT_CHOICE, PUBLISH_PACKAGES_TAG, } = require('../constants'); -const forEachPackage = require('../for-each-package'); const bumpPackageVersion = require('./bump-package-version'); const detectPackageUnreleasedChanges = require('./bump-utils'); const chalk = require('chalk'); @@ -33,8 +34,6 @@ const inquirer = require('inquirer'); const path = require('path'); const {echo, exec, exit} = require('shelljs'); -const ROOT_LOCATION = path.join(__dirname, '..', '..', '..'); - const buildExecutor = ( packageAbsolutePath /*: string */, @@ -53,7 +52,7 @@ const buildExecutor = !detectPackageUnreleasedChanges( packageRelativePathFromRoot, packageName, - ROOT_LOCATION, + REPO_ROOT, ) ) { return; @@ -99,16 +98,6 @@ const buildExecutor = }); }; -const buildAllExecutors = () => { - const executors = []; - - forEachPackage((...params) => { - executors.push(buildExecutor(...params)); - }); - - return executors; -}; - const main = async () => { if (checkForGitChanges()) { echo( @@ -119,8 +108,18 @@ const main = async () => { exit(1); } - const executors = buildAllExecutors(); - for (const executor of executors) { + const packages = await getPackages({ + includeReactNative: false, + includePrivate: true, + }); + + for (const pkg of Object.values(packages)) { + const executor = buildExecutor( + pkg.path, + path.relative(REPO_ROOT, pkg.path), + pkg.packageJson, + ); + await executor() .catch(() => exit(1)) .then(() => echo()); @@ -132,7 +131,7 @@ const main = async () => { } echo('Aligning new versions across monorepo...'); - alignPackageVersions(); + await alignPackageVersions(); echo(chalk.green('Done!\n')); // Figure out the npm dist-tags we want for all monorepo packages we're bumping @@ -219,7 +218,7 @@ const main = async () => { case COMMIT_WITH_GENERIC_MESSAGE_CHOICE: { exec(`git commit -am "${GENERIC_COMMIT_MESSAGE}${tagString}"`, { - cwd: ROOT_LOCATION, + cwd: REPO_ROOT, silent: true, }); @@ -229,17 +228,17 @@ const main = async () => { case COMMIT_WITH_CUSTOM_MESSAGE_CHOICE: { // exec from shelljs currently does not support interactive input // https://github.com/shelljs/shelljs/wiki/FAQ#running-interactive-programs-with-exec - execSync('git commit -a', {cwd: ROOT_LOCATION, stdio: 'inherit'}); + execSync('git commit -a', {cwd: REPO_ROOT, stdio: 'inherit'}); const enteredCommitMessage = exec('git log -n 1 --format=format:%B', { - cwd: ROOT_LOCATION, + cwd: REPO_ROOT, silent: true, }).stdout.trim(); const commitMessageWithTag = enteredCommitMessage + `\n\n${PUBLISH_PACKAGES_TAG}${tagString}`; exec(`git commit --amend -m "${commitMessageWithTag}"`, { - cwd: ROOT_LOCATION, + cwd: REPO_ROOT, silent: true, }); diff --git a/scripts/monorepo/for-each-package.js b/scripts/monorepo/for-each-package.js deleted file mode 100644 index 49d7c9eee3f..00000000000 --- a/scripts/monorepo/for-each-package.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * 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 {PACKAGES_DIR} = require('../consts'); -const {readdirSync, readFileSync} = require('fs'); -const path = require('path'); - -const DEFAULT_OPTIONS /*: Options */ = {includeReactNative: false}; - -/*:: -type PackageJSON = { - name: string, - private?: ?boolean, - version: string, - dependencies: {[string]: string}, - devDependencies: {[string]: string}, - ... -}; - -type Options = { - includeReactNative?: ?boolean, -}; -*/ - -/** - * Function, which returns an array of all directories inside specified location - */ -const getDirectories = (source /*: string */) /*: Array */ => - readdirSync(source, {withFileTypes: true}) - .filter(file => file.isDirectory()) - .map(directory => directory.name.toString()); -/** - * Iterate through every package inside /packages (ignoring react-native) and call provided callback for each of them - * - * @deprecated Use scripts/releases/utils/monorepo.js#getPackages instead - */ -const forEachPackage = ( - callback /*: (string, string, PackageJSON) => void */, - options /*: Options */ = DEFAULT_OPTIONS, -) => { - const {includeReactNative} = options; - - // We filter react-native package on purpose, so that no CI's script will be executed for this package in future - // Unless includeReactNative options is provided - const packagesDirectories = getDirectories(PACKAGES_DIR).filter( - directoryName => - directoryName !== 'react-native' || includeReactNative === true, - ); - - packagesDirectories.forEach(packageDirectory => { - const packageAbsolutePath = path.join(PACKAGES_DIR, packageDirectory); - const packageRelativePathFromRoot = path.join('packages', packageDirectory); - - const packageManifest = JSON.parse( - readFileSync(path.join(packageAbsolutePath, 'package.json')).toString(), - ); - - callback(packageAbsolutePath, packageRelativePathFromRoot, packageManifest); - }); -}; - -module.exports = forEachPackage; diff --git a/scripts/monorepo/print/index.js b/scripts/monorepo/print/index.js index 5846837ddfa..4783c5c02f3 100644 --- a/scripts/monorepo/print/index.js +++ b/scripts/monorepo/print/index.js @@ -8,7 +8,7 @@ */ const {getVersionsBySpec} = require('../../npm-utils'); -const forEachPackage = require('../for-each-package'); +const {getPackages} = require('../../utils/monorepo'); const {exit} = require('shelljs'); const yargs = require('yargs'); @@ -41,40 +41,46 @@ function reversePatchComp(semverA, semverB) { return patchB - patchA; } -const main = () => { +async function main() { const data = []; - forEachPackage( - (_packageAbsolutePath, _packageRelativePathFromRoot, packageManifest) => { - const isPublic = !packageManifest.private; - if ( - type === 'all' || - (type === 'private' && !isPublic) || - (type === 'public' && isPublic) - ) { - const packageInfo = { - 'Public?': isPublic ? '\u{2705}' : '\u{274C}', - Name: packageManifest.name, - 'Version (main)': packageManifest.version, - }; + const packages = await getPackages({ + includeReactNative: true, + includePrivate: true, + }); - if (isPublic && minor !== 0) { - try { - const versions = getVersionsBySpec( - packageManifest.name, - `^0.${minor}.0`, - ).sort(reversePatchComp); - packageInfo[`Version (${minor})`] = versions[0]; - } catch (e) { - packageInfo[`Version (${minor})`] = e.message; - } + for (const {packageJson} of Object.values(packages)) { + const isPublic = !packageJson.private; + if ( + type === 'all' || + (type === 'private' && !isPublic) || + (type === 'public' && isPublic) + ) { + const packageInfo = { + 'Public?': isPublic ? '\u{2705}' : '\u{274C}', + Name: packageJson.name, + 'Version (main)': packageJson.version, + }; + + if (isPublic && minor !== 0) { + try { + const versions = getVersionsBySpec( + packageJson.name, + `^0.${minor}.0`, + ).sort(reversePatchComp); + packageInfo[`Version (${minor})`] = versions[0]; + } catch (e) { + packageInfo[`Version (${minor})`] = e.message; } - data.push(packageInfo); } - }, - {includeReactNative: true}, - ); + data.push(packageInfo); + } + } + console.table(data); exit(0); -}; +} -main(); +if (require.main === module) { + // eslint-disable-next-line no-void + void main(); +} diff --git a/scripts/releases-ci/__tests__/publish-npm-test.js b/scripts/releases-ci/__tests__/publish-npm-test.js index c8852d37a50..3dd4230b14f 100644 --- a/scripts/releases-ci/__tests__/publish-npm-test.js +++ b/scripts/releases-ci/__tests__/publish-npm-test.js @@ -121,14 +121,14 @@ describe('publish-npm', () => { describe("publishNpm('nightly')", () => { beforeAll(() => { - jest.mock('../../releases/utils/monorepo', () => ({ - ...jest.requireActual('../../releases/utils/monorepo'), + jest.mock('../../utils/monorepo', () => ({ + ...jest.requireActual('../../utils/monorepo'), getPackages: getPackagesMock, })); }); afterAll(() => { - jest.unmock('../../releases/utils/monorepo'); + jest.unmock('../../utils/monorepo'); }); it('should publish', async () => { diff --git a/scripts/releases-ci/__tests__/publish-updated-packages-test.js b/scripts/releases-ci/__tests__/publish-updated-packages-test.js index 9f7845ba93e..c2b1918c819 100644 --- a/scripts/releases-ci/__tests__/publish-updated-packages-test.js +++ b/scripts/releases-ci/__tests__/publish-updated-packages-test.js @@ -21,7 +21,7 @@ const fetchMock = jest.fn(); jest.mock('child_process', () => ({execSync})); jest.mock('shelljs', () => ({exec: execMock})); -jest.mock('../../releases/utils/monorepo', () => ({ +jest.mock('../../utils/monorepo', () => ({ getPackages: getPackagesMock, })); // $FlowIgnore[cannot-write] diff --git a/scripts/releases-ci/publish-npm.js b/scripts/releases-ci/publish-npm.js index ddb38248f96..26933699cb9 100755 --- a/scripts/releases-ci/publish-npm.js +++ b/scripts/releases-ci/publish-npm.js @@ -20,11 +20,11 @@ const {getNpmInfo, publishPackage} = require('../npm-utils'); const {removeNewArchFlags} = require('../releases/remove-new-arch-flags'); const {setReactNativeVersion} = require('../releases/set-rn-version'); const setVersion = require('../releases/set-version'); -const {getPackages} = require('../releases/utils/monorepo'); const { generateAndroidArtifacts, publishAndroidArtifactsToMaven, } = require('../releases/utils/release-utils'); +const {getPackages} = require('../utils/monorepo'); const path = require('path'); const yargs = require('yargs'); diff --git a/scripts/releases-ci/publish-updated-packages.js b/scripts/releases-ci/publish-updated-packages.js index f20a7d5b45b..56b4bd61003 100644 --- a/scripts/releases-ci/publish-updated-packages.js +++ b/scripts/releases-ci/publish-updated-packages.js @@ -11,7 +11,7 @@ const {PUBLISH_PACKAGES_TAG} = require('../monorepo/constants'); const {publishPackage} = require('../npm-utils'); -const {getPackages} = require('../releases/utils/monorepo'); +const {getPackages} = require('../utils/monorepo'); const {parseArgs} = require('@pkgjs/parseargs'); const {execSync} = require('child_process'); diff --git a/scripts/releases-local/trigger-react-native-release.js b/scripts/releases-local/trigger-react-native-release.js index 266c5cccc3e..b9fc887741a 100644 --- a/scripts/releases-local/trigger-react-native-release.js +++ b/scripts/releases-local/trigger-react-native-release.js @@ -14,15 +14,16 @@ const {REPO_ROOT} = require('../consts'); const detectPackageUnreleasedChanges = require('../monorepo/bump-all-updated-packages/bump-utils.js'); const checkForGitChanges = require('../monorepo/check-for-git-changes'); -const forEachPackage = require('../monorepo/for-each-package'); const {failIfTagExists} = require('../releases/utils/release-utils'); const { isReleaseBranch, parseVersion, } = require('../releases/utils/version-utils'); const {exitIfNotOnGit, getBranchName} = require('../scm-utils'); +const {getPackages} = require('../utils/monorepo'); const chalk = require('chalk'); const inquirer = require('inquirer'); +const path = require('path'); const request = require('request'); const {echo, exit} = require('shelljs'); const yargs = require('yargs'); @@ -92,22 +93,22 @@ const buildExecutor = } }; -const buildAllExecutors = () => { - const executors = []; - - forEachPackage((...params) => { - executors.push(buildExecutor(...params)); - }); - - return executors; -}; - async function exitIfUnreleasedPackages() { // use the other script to verify that there's no packages in the monorepo // that have changes that haven't been released - const executors = buildAllExecutors(); - for (const executor of executors) { + const packages = await getPackages({ + includeReactNative: false, + includePrivate: true, + }); + + for (const pkg of Object.values(packages)) { + const executor = buildExecutor( + pkg.path, + path.relative(REPO_ROOT, pkg.path), + pkg.packageJson, + ); + await executor().catch(error => { echo(chalk.red(error)); // need to throw upward diff --git a/scripts/releases/set-version/index.js b/scripts/releases/set-version/index.js index 443863565dc..20d241d8910 100644 --- a/scripts/releases/set-version/index.js +++ b/scripts/releases/set-version/index.js @@ -12,11 +12,11 @@ 'use strict'; /*:: -import type {PackageJson} from '../utils/monorepo'; +import type {PackageJson} from '../../utils/monorepo'; */ +const {getPackages} = require('../../utils/monorepo'); const {setReactNativeVersion} = require('../set-rn-version'); -const {getPackages} = require('../utils/monorepo'); const {promises: fs} = require('fs'); const path = require('path'); const yargs = require('yargs'); diff --git a/scripts/releases/utils/monorepo.js b/scripts/utils/monorepo.js similarity index 91% rename from scripts/releases/utils/monorepo.js rename to scripts/utils/monorepo.js index 4908f951e8e..627e44b63f2 100644 --- a/scripts/releases/utils/monorepo.js +++ b/scripts/utils/monorepo.js @@ -9,7 +9,7 @@ * @oncall react_native */ -const {REPO_ROOT} = require('../../consts'); +const {REPO_ROOT} = require('../consts'); const fs = require('fs'); const glob = require('glob'); const path = require('path'); @@ -67,7 +67,7 @@ async function getPackages( }) .map(async packageJsonPath => { const packagePath = path.dirname(packageJsonPath); - const packageJson = JSON.parse( + const packageJson /*: PackageJson */ = JSON.parse( await fs.promises.readFile(packageJsonPath, 'utf-8'), ); @@ -84,7 +84,7 @@ async function getPackages( return Object.fromEntries( packagesEntries.filter( - ([_, {packageJson}]) => !packageJson.private || includePrivate, + ([_, {packageJson}]) => packageJson.private !== true || includePrivate, ), ); }