Reorganise shared script utils (#52473)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52473

Shared utils that were located in the root of `scripts/` are now colocated closer to their dependencies or moved to `scripts/shared/` — simplifying the root directory layout.

Changelog: [Internal]

Reviewed By: robhogan

Differential Revision: D77873875

fbshipit-source-id: e04dba41a1ef811d32793931033fdfa93afad0cd
This commit is contained in:
Alex Hunt
2025-07-08 06:10:36 -07:00
committed by Facebook GitHub Bot
parent 6dfe59e1df
commit fc5e33b582
45 changed files with 141 additions and 153 deletions
+70
View File
@@ -0,0 +1,70 @@
/**
* 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,
PRIVATE_DIR,
RN_INTEGRATION_TESTS_RUNNER_DIR,
SCRIPTS_DIR,
} = require('./consts');
let isRegisteredForMonorepo = false;
/**
* Calling this function enables all Node.js packages to run from source when
* developing in the React Native repo.
*
* A call should located in each entry point file in a package (i.e. for all
* paths in "exports"), inside a special `if` condition that will be compiled
* away on build.
*
* ```js
* // Place in a package entry point
* if (!process.env.BUILD_EXCLUDE_BABEL_REGISTER) {
* require('../../../scripts/shared/babelRegister').registerForMonorepo();
* }
* ```
*/
function registerForMonorepo() {
if (isRegisteredForMonorepo) {
return;
}
if (process.env.FBSOURCE_ENV === '1') {
// $FlowExpectedError[cannot-resolve-module] - Won't resolve in OSS
require('@fb-tools/babel-register');
} else {
require('metro-babel-register')([
PACKAGES_DIR,
PRIVATE_DIR,
SCRIPTS_DIR,
RN_INTEGRATION_TESTS_RUNNER_DIR,
]);
}
isRegisteredForMonorepo = true;
}
/**
* Calling this function enables entry points under scripts/ to run from source.
*
* ```js
* // Place in a script entry point
* require('../shared/babelRegister').registerForScript();
* ```
*/
function registerForScript() {
registerForMonorepo();
}
module.exports = {
registerForMonorepo,
registerForScript,
};
+70
View File
@@ -0,0 +1,70 @@
/**
* 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
* @format
*/
const path = require('path');
/**
* The absolute path to the repo root.
*/
const REPO_ROOT /*: string */ = path.resolve(__dirname, '../..');
/**
* The absolute path to the packages directory (note: this directory alone may
* not match the "workspaces" config in package.json).
*/
const PACKAGES_DIR /*: string */ = path.join(REPO_ROOT, 'packages');
/**
* The absolute path to the repo private directory.
*/
const PRIVATE_DIR /*: string */ = path.join(REPO_ROOT, 'private');
/**
* The absolute path to the repo scripts directory.
*/
const SCRIPTS_DIR /*: string */ = path.join(REPO_ROOT, 'scripts');
/**
* The absolute path to the react-native package.
*/
const REACT_NATIVE_PACKAGE_DIR /*: string */ = path.join(
REPO_ROOT,
'packages',
'react-native',
);
/**
* The absolute path to the rn-tester package.
*/
const RN_TESTER_DIR /*: string */ = path.join(
REPO_ROOT,
'packages',
'rn-tester',
);
/**
* The absolute path to the RN integration tests runner directory.
*/
const RN_INTEGRATION_TESTS_RUNNER_DIR /*: string */ = path.join(
REPO_ROOT,
'jest',
'integration',
'runner',
);
module.exports = {
PACKAGES_DIR,
PRIVATE_DIR,
REACT_NATIVE_PACKAGE_DIR,
REPO_ROOT,
RN_TESTER_DIR,
SCRIPTS_DIR,
RN_INTEGRATION_TESTS_RUNNER_DIR,
};
+35
View File
@@ -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.
*
* @flow strict-local
* @format
*/
'use strict';
const childProcess = require('child_process');
/*::
type Commit = string;
*/
function isGitRepo() /*: boolean */ {
try {
const result = childProcess.execSync(
'git rev-parse --is-inside-work-tree',
{
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
},
);
return result.trim() === 'true';
} catch (error) {
console.error(`Failed to check git repository status: ${error}`);
}
return false;
}
module.exports = isGitRepo;
+157
View File
@@ -0,0 +1,157 @@
/**
* 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
* @format
*/
const {REPO_ROOT} = require('./consts');
const {promises: fs} = require('fs');
const glob = require('glob');
const path = require('path');
const WORKSPACES_CONFIG = '{packages,private}/*';
/*::
export type PackageJson = {
name: string,
version: string,
private?: boolean,
dependencies?: Record<string, string>,
devDependencies?: Record<string, string>,
main?: string,
...
};
type PackagesFilter = $ReadOnly<{
includeReactNative: boolean,
includePrivate?: boolean,
}>;
export type PackageInfo = {
// The name of the package
name: string,
// The absolute path to the package
path: string,
// The parsed package.json contents
packageJson: PackageJson,
};
export type ProjectInfo = {
[packageName: string]: PackageInfo,
};
*/
/**
* Locates monrepo packages and returns a mapping of package names to their
* metadata. Considers Yarn workspaces under `packages/`.
*/
async function getPackages(
filter /*: PackagesFilter */,
) /*: Promise<ProjectInfo> */ {
const {includeReactNative, includePrivate = false} = filter;
const packagesEntries = await Promise.all(
glob
.sync(`${WORKSPACES_CONFIG}/package.json`, {
cwd: REPO_ROOT,
absolute: true,
ignore: includeReactNative
? []
: ['packages/react-native/package.json'],
})
.map(parsePackageInfo),
);
return Object.fromEntries(
packagesEntries.filter(
([_, {packageJson}]) => packageJson.private !== true || includePrivate,
),
);
}
/**
* Get the parsed package metadata for the workspace root.
*/
async function getWorkspaceRoot() /*: Promise<PackageInfo> */ {
const [, packageInfo] = await parsePackageInfo(
path.join(REPO_ROOT, 'package.json'),
);
return packageInfo;
}
async function parsePackageInfo(
packageJsonPath /*: string */,
) /*: Promise<[string, PackageInfo]> */ {
const packagePath = path.dirname(packageJsonPath);
const packageJson /*: PackageJson */ = JSON.parse(
await fs.readFile(packageJsonPath, 'utf-8'),
);
return [
packageJson.name,
{
name: packageJson.name,
path: packagePath,
packageJson,
},
];
}
/**
* Update a given package with the package versions.
*/
async function updatePackageJson(
{path: packagePath, packageJson} /*: PackageInfo */,
newPackageVersions /*: $ReadOnly<{[string]: string}> */,
) /*: Promise<void> */ {
const packageName = packageJson.name;
if (packageName in newPackageVersions) {
packageJson.version = newPackageVersions[packageName];
}
for (const dependencyField of [
'dependencies',
'devDependencies',
] /*:: as const */) {
const deps = packageJson[dependencyField];
if (deps == null) {
continue;
}
for (const dependency in newPackageVersions) {
if (dependency in deps) {
deps[dependency] = newPackageVersions[dependency];
}
}
}
return writePackageJson(path.join(packagePath, 'package.json'), packageJson);
}
/**
* Write a `package.json` file to disk.
*/
async function writePackageJson(
packageJsonPath /*: string */,
packageJson /*: PackageJson */,
) /*: Promise<void> */ {
return fs.writeFile(
packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n',
);
}
module.exports = {
getPackages,
getWorkspaceRoot,
updatePackageJson,
};