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
This commit is contained in:
Alex Hunt 2024-02-20 09:55:46 -08:00 committed by Facebook GitHub Bot
parent 4ecf57ead2
commit d6bf51cad9
15 changed files with 370 additions and 480 deletions

View File

@ -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');

View File

@ -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);

View File

@ -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';

View File

@ -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,
);
});
});

View File

@ -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;

View File

@ -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,
});

View File

@ -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<string> */ =>
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;

View File

@ -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();
}

View File

@ -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 () => {

View File

@ -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]

View File

@ -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');

View File

@ -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');

View File

@ -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

View File

@ -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');

View File

@ -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,
),
);
}