mirror of
https://github.com/react/react-native.git
synced 2026-09-28 13:23:09 +08:00
Move core-cli-utils to private/ as a reference implementation (#56922)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/56922 Move `react-native/core-cli-utils` from `packages/` to `private/`, stop publishing it to npm, and reframe it as a reference implementation of React Native CLI tooling. The package has no known external consumers and is only used internally by `private/helloworld/` and `packages/rn-tester/`. Publishing it to npm creates a maintenance surface for a package that serves no external users. See https://github.com/react-native-community/discussions-and-proposals/pull/1002. #### Changes - Move `packages/core-cli-utils/` to `private/core-cli-utils/`. - Convert all source files to CJS (require/module.exports) with Flow comment syntax (/*:: */), eliminating the runtime Babel dependency. - Remove from the JS build pipeline (`scripts/build/config.js`). - Remove the defunct `patchCoreCLIUtilsPackageJSON()` runtime patching from both `helloworld/cli.js` and `rn-tester/cli.js`, and delete both `monorepo.js` files that contained it. - Add as `devDependency` to `rn-tester` and `helloworld`. - Skip `"*"` version deps in `_prepareHelloWorld()` so they aren't rewritten to the Verdaccio-published version. - Remove redundant desktop import ignore entry (`private/**` already covers it). - Rewrite README as reference implementation documentation. Changelog: [General][Breaking] - The `react-native/core-cli-utils` package is no longer published. It remains available in the React Native repo as a reference implementation. Reviewed By: cortinico Differential Revision: D105959855 fbshipit-source-id: 42e439a45273bdeca76029eff306cdf2451308e2
This commit is contained in:
committed by
meta-codesync[bot]
parent
36bec56b0b
commit
9c3fddf9cd
@@ -1,55 +0,0 @@
|
||||
# @react-native/core-cli-utils
|
||||
|
||||

|
||||
|
||||
A collection of utilites to help Frameworks build their React Native CLI tooling. This is not intended to be used directly use users of React Native.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { Command } from 'commander';
|
||||
import cli from '@react-native/core-cli-utils';
|
||||
import debug from 'debug';
|
||||
|
||||
const android = new Command('android');
|
||||
|
||||
const frameworkFindsAndroidSrcDir = "...";
|
||||
const tasks = cli.clean.android(frameworkFindsAndroidSrcDir);
|
||||
const log = debug('fancy-framework:android');
|
||||
|
||||
android
|
||||
.command('clean')
|
||||
.description(cli.clean.android)
|
||||
.action(async () => {
|
||||
const log = debug('fancy-framework:android:clean');
|
||||
log(`🧹 let me clean your Android caches`);
|
||||
// Add other caches your framework needs besides the normal React Native caches
|
||||
// here.
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
log(`\t ${task.label}`);
|
||||
// See: https://github.com/sindresorhus/execa#lines
|
||||
const {stdout} = await task.action({ lines: true })
|
||||
log(stdout.join('\n\tGradle: '));
|
||||
} catch (e) {
|
||||
log(`\t ⚠️ whoops: ${e.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
And you'd be using it like this:
|
||||
|
||||
```bash
|
||||
$ ./fancy-framework android clean
|
||||
🧹 let me clean your Android caches
|
||||
Gradle: // a bunch of gradle output
|
||||
Gradle: ....
|
||||
```
|
||||
|
||||
## Features
|
||||
- `"@react-native/core-cli-utils/version.js"` contains the platform and tooling version requirements for react-native.
|
||||
|
||||
## Contributing
|
||||
|
||||
Changes to this package can be made locally and linked against your app. Please see the [Contributing guide](https://reactnative.dev/contributing/overview#contributing-code).
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "@react-native/core-cli-utils",
|
||||
"version": "0.87.0-main",
|
||||
"description": "React Native CLI library for Frameworks to build on",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"cli-utils",
|
||||
"react-native"
|
||||
],
|
||||
"homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/core-cli-utils#readme",
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/facebook/react-native.git",
|
||||
"directory": "packages/core-cli-utils"
|
||||
},
|
||||
"main": "./src/index.flow.js",
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
"./package.json": "./package.json",
|
||||
"./version.js": "./src/public/version.js"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./package.json": "./package.json",
|
||||
"./version.js": "./dist/public/version.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"prepack": "node ../../scripts/build/prepack.js"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"engines": {
|
||||
"node": "^22.13.0 || ^24.3.0 || >= 26.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +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
|
||||
*/
|
||||
|
||||
// @babel/register doesn't like export {foo} from './bar'; statements,
|
||||
// so we have to jump through hoops here.
|
||||
import {tasks as _android} from './private/android.js';
|
||||
import {tasks as _app} from './private/app.js';
|
||||
import {tasks as _apple} from './private/apple.js';
|
||||
import {tasks as _clean} from './private/clean.js';
|
||||
import * as _version from './public/version.js';
|
||||
|
||||
export const android = _android;
|
||||
export const app = _app;
|
||||
export const apple = _apple;
|
||||
export const clean = _clean;
|
||||
export const version = _version;
|
||||
|
||||
export type {Task} from './private/types';
|
||||
@@ -1,19 +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
|
||||
*/
|
||||
|
||||
/*::
|
||||
export type * from './index.flow';
|
||||
*/
|
||||
|
||||
if (process.env.BUILD_EXCLUDE_BABEL_REGISTER == null) {
|
||||
require('../../../scripts/shared/babelRegister').registerForMonorepo();
|
||||
}
|
||||
|
||||
module.exports = require('./index.flow');
|
||||
@@ -1,19 +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
|
||||
*/
|
||||
|
||||
/*::
|
||||
export type * from './version.flow';
|
||||
*/
|
||||
|
||||
if (process.env.BUILD_EXCLUDE_BABEL_REGISTER == null) {
|
||||
require('../../../../scripts/shared/babelRegister').registerForMonorepo();
|
||||
}
|
||||
|
||||
module.exports = require('./version.flow');
|
||||
@@ -18,7 +18,7 @@ import type {Command} from '@react-native-community/cli-types';
|
||||
// depending on and injecting:
|
||||
// - @react-native-community/cli-platform-android
|
||||
// - @react-native-community/cli-platform-ios
|
||||
// - @react-native/community-cli-plugin (via the @react-native/core-cli-utils package)
|
||||
// - @react-native/community-cli-plugin
|
||||
// - codegen command should be inhoused into @react-native-community/cli
|
||||
//
|
||||
// This is a temporary workaround.
|
||||
|
||||
@@ -12,30 +12,11 @@
|
||||
import {Command} from 'commander';
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @react-native/monorepo/sort-imports
|
||||
const {patchCoreCLIUtilsPackageJSON} = require('./scripts/monorepo');
|
||||
|
||||
function injectCoreCLIUtilsRuntimePatch() {
|
||||
patchCoreCLIUtilsPackageJSON(true);
|
||||
const cleared = {
|
||||
status: false,
|
||||
};
|
||||
['exit', 'SIGUSR1', 'SIGUSR2', 'uncaughtException'].forEach(event => {
|
||||
if (cleared.status) {
|
||||
return;
|
||||
}
|
||||
patchCoreCLIUtilsPackageJSON(false);
|
||||
cleared.status = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env.BUILD_EXCLUDE_BABEL_REGISTER == null) {
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
require('../../scripts/shared/babelRegister').registerForMonorepo();
|
||||
}
|
||||
|
||||
injectCoreCLIUtilsRuntimePatch();
|
||||
|
||||
const program /*: Command */ = require('./cli.flow.js').default;
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"@react-native-community/cli": "20.0.0",
|
||||
"@react-native-community/cli-platform-android": "20.0.0",
|
||||
"@react-native-community/cli-platform-ios": "20.0.0",
|
||||
"@react-native/core-cli-utils": "*",
|
||||
"commander": "^12.0.0",
|
||||
"listr2": "^6.4.1",
|
||||
"rxjs": "npm:@react-native-community/rxjs@6.5.4-custom"
|
||||
|
||||
@@ -1,41 +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
|
||||
*/
|
||||
|
||||
// To be able to execute the cli as a yarn script, we have to strip our yarn types.
|
||||
// This causes problems for some of our dependencies, because they live in Meta internals,
|
||||
// Github and in NPM:
|
||||
// - Github and Meta: dynamicly transpile our dependencies. They each have to register on the monorepo
|
||||
// - NPM: `yarn run build`, and it should update the package.json's exports, main and files
|
||||
function patchCoreCLIUtilsPackageJSON(patch /*: boolean */) {
|
||||
const fs = require('fs');
|
||||
const log = require('debug');
|
||||
const pkg = JSON.parse(
|
||||
fs.readFileSync('../core-cli-utils/package.json', 'utf8'),
|
||||
);
|
||||
const target = patch ? './src/monorepo.js' : './src/index.flow.js';
|
||||
if (pkg.main === target) {
|
||||
return;
|
||||
}
|
||||
pkg.main = target;
|
||||
pkg.exports['.'] = target;
|
||||
log(
|
||||
`Patched: ${JSON.stringify(
|
||||
{main: pkg.main, exports: pkg.exports},
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
'../core-cli-utils/package.json',
|
||||
JSON.stringify(pkg, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
module.exports.patchCoreCLIUtilsPackageJSON = patchCoreCLIUtilsPackageJSON;
|
||||
@@ -0,0 +1,18 @@
|
||||
# @react-native/core-cli-utils
|
||||
|
||||
A reference implementation of React Native CLI tooling. This package provides composable, ordered `Task` objects for common CLI operations including Android Gradle builds, iOS Xcode/CocoaPods workflows, Metro bundling with Hermes support, and cache cleaning.
|
||||
|
||||
This is not published to npm. Framework authors can use this code as a starting point for their own CLI tooling, but should not depend on it as a versioned API.
|
||||
|
||||
## Modules
|
||||
|
||||
- **`android`** — Gradle-based Android build tasks (assemble, build, install)
|
||||
- **`apple`** — Xcode/CocoaPods-based iOS tasks (bootstrap, build, install)
|
||||
- **`app`** — Metro bundler tasks (watch mode, bundle mode, Hermes bytecode compilation)
|
||||
- **`clean`** — Cache-cleaning tasks (Android/Gradle, Metro, npm, Watchman, Yarn, CocoaPods)
|
||||
- **`version`** — Semver version requirements for platform toolchains (Android NDK/SDK, Xcode, Node, etc.)
|
||||
|
||||
## Consumers
|
||||
|
||||
- [`private/helloworld/`](../helloworld/) — the primary consumer, using Android, iOS, and Metro modules
|
||||
- [`packages/rn-tester/`](../../packages/rn-tester/) — uses iOS bootstrap for CocoaPods setup
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@react-native/core-cli-utils",
|
||||
"version": "0.0.0",
|
||||
"description": "Reference implementation of React Native CLI tooling",
|
||||
"license": "MIT",
|
||||
"main": "./src/index.js",
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
"./package.json": "./package.json",
|
||||
"./version.js": "./src/public/version.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"metro-babel-register": "^0.84.3"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"engines": {
|
||||
"node": "^22.13.0 || ^24.3.0 || >= 26.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/*::
|
||||
export type {Task} from './private/types';
|
||||
*/
|
||||
|
||||
const {tasks: android} = require('./private/android.js');
|
||||
const {tasks: app} = require('./private/app.js');
|
||||
const {tasks: apple} = require('./private/apple.js');
|
||||
const {tasks: clean} = require('./private/clean.js');
|
||||
const version = require('./public/version.js');
|
||||
|
||||
module.exports = {android, app, apple, clean, version};
|
||||
+24
-15
@@ -8,12 +8,15 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*::
|
||||
import type {Task} from './types';
|
||||
import type {ExecaPromise} from 'execa';
|
||||
*/
|
||||
|
||||
import {isWindows, task} from './utils';
|
||||
import execa from 'execa';
|
||||
const {isWindows, task} = require('./utils');
|
||||
const execa = require('execa');
|
||||
|
||||
/*::
|
||||
type AndroidBuildMode = 'Debug' | 'Release';
|
||||
|
||||
type Path = string;
|
||||
@@ -27,12 +30,13 @@ type Config = {
|
||||
newArchitecture?: boolean,
|
||||
sdk?: Path,
|
||||
};
|
||||
*/
|
||||
|
||||
function gradle(
|
||||
taskName: string,
|
||||
args: Args,
|
||||
options: {cwd: string, env?: {[k: string]: string | void}},
|
||||
): ExecaPromise {
|
||||
taskName /*: string */,
|
||||
args /*: Args */,
|
||||
options /*: {cwd: string, env?: {[k: string]: string | void}} */,
|
||||
) /*: ExecaPromise */ {
|
||||
const gradlew = isWindows ? 'gradlew.bat' : './gradlew';
|
||||
return execa(gradlew, [taskName, ...args], {
|
||||
cwd: options.cwd,
|
||||
@@ -40,11 +44,11 @@ function gradle(
|
||||
});
|
||||
}
|
||||
|
||||
function androidSdkPath(sdk?: string): string {
|
||||
function androidSdkPath(sdk /*: ?string */) /*: string */ {
|
||||
return sdk ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK ?? '';
|
||||
}
|
||||
|
||||
function boolToStr(value: boolean): string {
|
||||
function boolToStr(value /*: boolean */) /*: string */ {
|
||||
return value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
@@ -53,9 +57,8 @@ const FIRST = 1;
|
||||
//
|
||||
// Android Tasks
|
||||
//
|
||||
export const tasks = (
|
||||
config: Config,
|
||||
): ({
|
||||
/*::
|
||||
type AndroidTasks = {
|
||||
assemble: (...gradleArgs: Args) => {
|
||||
run: Task<ExecaPromise>,
|
||||
},
|
||||
@@ -65,8 +68,11 @@ export const tasks = (
|
||||
install: (...gradleArgs: Args) => {
|
||||
run: Task<ExecaPromise>,
|
||||
},
|
||||
}) => ({
|
||||
assemble: (...gradleArgs: Args) => ({
|
||||
};
|
||||
*/
|
||||
|
||||
const tasks = (config /*: Config */) /*: AndroidTasks */ => ({
|
||||
assemble: (...gradleArgs /*: Args */) => ({
|
||||
run: task(FIRST, 'Assemble Android App', () => {
|
||||
const args = [];
|
||||
if (config.hermes != null) {
|
||||
@@ -82,7 +88,7 @@ export const tasks = (
|
||||
});
|
||||
}),
|
||||
}),
|
||||
build: (...gradleArgs: Args) => ({
|
||||
build: (...gradleArgs /*: Args */) => ({
|
||||
run: task(FIRST, 'Assembles and tests Android App', () => {
|
||||
const args = [];
|
||||
if (config.hermes != null) {
|
||||
@@ -101,10 +107,11 @@ export const tasks = (
|
||||
/**
|
||||
* Useful extra gradle arguments:
|
||||
*
|
||||
*
|
||||
* -PreactNativeDevServerPort=8081 sets the port for the installed app to point towards a Metro
|
||||
* server on (for example) 8081.
|
||||
*/
|
||||
install: (...gradleArgs: Args) => ({
|
||||
install: (...gradleArgs /*: Args */) => ({
|
||||
run: task(FIRST, 'Installs the assembled Android App', () =>
|
||||
gradle(`${config.name}:install${config.mode}`, gradleArgs, {
|
||||
cwd: config.cwd,
|
||||
@@ -118,3 +125,5 @@ export const tasks = (
|
||||
// CLI's code:
|
||||
// https://github.com/react-native-community/cli/blob/54d48a4e08a1aef334ae6168788e0157a666b4f5/packages/cli-platform-android/src/commands/runAndroid/index.ts#L272C1-L290C2
|
||||
});
|
||||
|
||||
module.exports = {tasks};
|
||||
+34
-33
@@ -8,17 +8,20 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*::
|
||||
import type {Task} from './types';
|
||||
import type {ExecaPromise} from 'execa';
|
||||
*/
|
||||
|
||||
import {task} from './utils';
|
||||
import debug from 'debug';
|
||||
import execa from 'execa';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
const {task} = require('./utils');
|
||||
const debug = require('debug');
|
||||
const execa = require('execa');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const log = debug('core-cli-utils');
|
||||
|
||||
/*::
|
||||
type BundlerOptions = {
|
||||
// Metro's config: https://metrobundler.dev/docs/configuration/
|
||||
config?: string,
|
||||
@@ -65,24 +68,37 @@ type BundlerBuild = {
|
||||
|
||||
type Bundler = BundlerWatch | BundlerBuild;
|
||||
|
||||
type Bundle = {
|
||||
validate?: Task<void>,
|
||||
javascript: Task<ExecaPromise>,
|
||||
sourcemap?: Task<void>,
|
||||
validateHermesc?: Task<ExecaPromise>,
|
||||
convert?: Task<ExecaPromise>,
|
||||
compose?: Task<ExecaPromise>,
|
||||
};
|
||||
*/
|
||||
|
||||
const FIRST = 1,
|
||||
SECOND = 2,
|
||||
THIRD = 3,
|
||||
FOURTH = 4;
|
||||
|
||||
function getNodePackagePath(packageName: string): string {
|
||||
function getNodePackagePath(packageName /*: string */) /*: string */ {
|
||||
// $FlowFixMe[prop-missing] type definition is incomplete
|
||||
return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]});
|
||||
}
|
||||
|
||||
function metro(...args: ReadonlyArray<string>): ExecaPromise {
|
||||
function metro(...args /*: ReadonlyArray<string> */) /*: ExecaPromise */ {
|
||||
log(`🚇 metro ${args.join(' ')} `);
|
||||
return execa('npx', ['--offline', 'metro', ...args]);
|
||||
}
|
||||
|
||||
export const tasks = {
|
||||
bundle: (options: BundlerOptions, ...args: ReadonlyArray<string>): Bundle => {
|
||||
const steps: Bundle = {
|
||||
const tasks = {
|
||||
bundle: (
|
||||
options /*: BundlerOptions */,
|
||||
...args /*: ReadonlyArray<string> */
|
||||
) /*: Bundle */ => {
|
||||
const steps /*: Bundle */ = {
|
||||
/* eslint-disable sort-keys */
|
||||
validate: task(FIRST, 'Check if Metro is available', () => {
|
||||
try {
|
||||
@@ -103,29 +119,16 @@ export const tasks = {
|
||||
},
|
||||
};
|
||||
|
||||
type Bundle = {
|
||||
validate?: Task<void>,
|
||||
javascript: Task<ExecaPromise>,
|
||||
sourcemap?: Task<void>,
|
||||
validateHermesc?: Task<ExecaPromise>,
|
||||
convert?: Task<ExecaPromise>,
|
||||
compose?: Task<ExecaPromise>,
|
||||
};
|
||||
|
||||
const bundleApp = (
|
||||
options: BundlerOptions,
|
||||
...metroArgs: ReadonlyArray<string>
|
||||
options /*: BundlerOptions */,
|
||||
...metroArgs /*: ReadonlyArray<string> */
|
||||
) => {
|
||||
if (options.outputJsBundle === options.outputBundle) {
|
||||
throw new Error('outputJsBundle and outputBundle cannot be the same.');
|
||||
}
|
||||
// When using Hermes, Metro should generate the JS bundle to an intermediate file
|
||||
// to then be converted to bytecode in the outputBundle. Otherwise just write to
|
||||
// the outputBundle directly.
|
||||
let output =
|
||||
options.jsvm === 'hermes' ? options.outputJsBundle : options.outputBundle;
|
||||
|
||||
// TODO: Fix this by not using Metro CLI, which appends a .js extension
|
||||
if (output === options.outputJsBundle && !output.endsWith('.js')) {
|
||||
log(
|
||||
`Appending .js to outputBundle (because metro cli does it if it's missing): ${output}`,
|
||||
@@ -134,7 +137,7 @@ const bundleApp = (
|
||||
}
|
||||
|
||||
const isSourceMaps = options.outputSourceMap != null;
|
||||
const bundle: Bundle = {
|
||||
const bundle /*: Bundle */ = {
|
||||
javascript: task(SECOND, 'Metro generating an .jsbundle', () => {
|
||||
const args = [
|
||||
'--platform',
|
||||
@@ -146,7 +149,6 @@ const bundleApp = (
|
||||
output,
|
||||
];
|
||||
if (options.jsvm === 'hermes' && !options.dev) {
|
||||
// Hermes doesn't require JS minification
|
||||
args.push('--minify', 'false');
|
||||
} else {
|
||||
args.push('--minify', options.minify ? 'true' : 'false');
|
||||
@@ -166,9 +168,9 @@ const bundleApp = (
|
||||
throw new Error('If jsvm == "hermes", hermes config must be provided.');
|
||||
}
|
||||
|
||||
const hermes: HermesConfig = options.hermes;
|
||||
const hermes /*: HermesConfig */ = options.hermes;
|
||||
|
||||
const isHermesInstalled: boolean = fs.existsSync(hermes.path);
|
||||
const isHermesInstalled /*: boolean */ = fs.existsSync(hermes.path);
|
||||
if (!isHermesInstalled) {
|
||||
throw new Error(
|
||||
'Hermes Pod must be installed before bundling.\n' +
|
||||
@@ -176,11 +178,8 @@ const bundleApp = (
|
||||
);
|
||||
}
|
||||
|
||||
const hermesc: string = path.join(hermes.path, hermes.hermesc);
|
||||
const hermesc /*: string */ = path.join(hermes.path, hermes.hermesc);
|
||||
|
||||
/*
|
||||
* Hermes only tasks:
|
||||
*/
|
||||
let composeSourceMaps;
|
||||
if (isSourceMaps) {
|
||||
bundle.sourcemap = task(
|
||||
@@ -244,3 +243,5 @@ const bundleApp = (
|
||||
|
||||
return bundle;
|
||||
};
|
||||
|
||||
module.exports = {tasks};
|
||||
+45
-33
@@ -8,14 +8,17 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*::
|
||||
import type {Task} from './types';
|
||||
import type {ExecaPromise} from 'execa';
|
||||
*/
|
||||
|
||||
import {assertDependencies, isOnPath, task} from './utils';
|
||||
import execa from 'execa';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
const {assertDependencies, isOnPath, task} = require('./utils');
|
||||
const execa = require('execa');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/*::
|
||||
type AppleBuildMode = 'Debug' | 'Release';
|
||||
|
||||
type AppleBuildOptions = {
|
||||
@@ -50,10 +53,11 @@ type AppleOptions = {
|
||||
// The environment variables to pass to the build command
|
||||
env?: {[key: string]: string | void, ...},
|
||||
};
|
||||
*/
|
||||
|
||||
function checkPodfileInSyncWithManifest(
|
||||
lockfilePath: string,
|
||||
manifestLockfilePath: string,
|
||||
lockfilePath /*: string */,
|
||||
manifestLockfilePath /*: string */,
|
||||
) {
|
||||
try {
|
||||
const expected = fs.readFileSync(lockfilePath, 'utf8');
|
||||
@@ -63,8 +67,8 @@ function checkPodfileInSyncWithManifest(
|
||||
'Please run: yarn bootstrap ios, Podfile.lock and Pods/Manifest.lock are out of sync',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('Please run: yarn run bootstrap ios: ' + e.message);
|
||||
} catch (e /*: unknown */) {
|
||||
throw new Error('Please run: yarn run bootstrap ios: ' + String(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,23 +78,36 @@ const FIRST = 1,
|
||||
FOURTH = 4,
|
||||
FIFTH = 5;
|
||||
|
||||
function getNodePackagePath(packageName: string): string {
|
||||
function getNodePackagePath(packageName /*: string */) /*: string */ {
|
||||
// $FlowFixMe[prop-missing] type definition is incomplete
|
||||
return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]});
|
||||
}
|
||||
|
||||
/*::
|
||||
type BootstrapTasks = {
|
||||
cleanupBuildFolder: Task<void>,
|
||||
runCodegen: Task<void>,
|
||||
validate: Task<void>,
|
||||
installRubyGems: Task<ExecaPromise>,
|
||||
installDependencies: Task<ExecaPromise>,
|
||||
};
|
||||
|
||||
type BuildTasks = {
|
||||
validate: Task<void>,
|
||||
hasPodsInstalled: Task<void>,
|
||||
build: Task<ExecaPromise>,
|
||||
};
|
||||
|
||||
type InstallTasks = {
|
||||
validate: Task<void>,
|
||||
install: Task<ExecaPromise>,
|
||||
};
|
||||
*/
|
||||
|
||||
/* eslint sort-keys: "off" */
|
||||
export const tasks = {
|
||||
const tasks = {
|
||||
// 1. Setup your environment for building the iOS apps
|
||||
bootstrap: (
|
||||
options: AppleBootstrapOption,
|
||||
): {
|
||||
cleanupBuildFolder: Task<void>,
|
||||
runCodegen: Task<void>,
|
||||
validate: Task<void>,
|
||||
installRubyGems: Task<ExecaPromise>,
|
||||
installDependencies: Task<ExecaPromise>,
|
||||
} => ({
|
||||
bootstrap: (options /*: AppleBootstrapOption */) /*: BootstrapTasks */ => ({
|
||||
cleanupBuildFolder: task(FIRST, 'Cleanup build folder', () => {
|
||||
execa.sync('rm', ['-rf', 'build'], {
|
||||
cwd: options.cwd,
|
||||
@@ -125,7 +142,7 @@ export const tasks = {
|
||||
}),
|
||||
),
|
||||
installDependencies: task(FIFTH, 'Install CocoaPods dependencies', () => {
|
||||
const env: {[string]: string | void} = {
|
||||
const env /*: {[string]: string | void} */ = {
|
||||
RCT_NEW_ARCH_ENABLED: options.newArchitecture ? '1' : '0',
|
||||
USE_FRAMEWORKS: options.frameworks,
|
||||
USE_HERMES: options.hermes ? '1' : '0',
|
||||
@@ -143,13 +160,9 @@ export const tasks = {
|
||||
|
||||
// 2. Build the iOS app using a setup environment
|
||||
build: (
|
||||
options: AppleBuildOptions,
|
||||
...args: ReadonlyArray<string>
|
||||
): {
|
||||
validate: Task<void>,
|
||||
hasPodsInstalled: Task<void>,
|
||||
build: Task<ExecaPromise>,
|
||||
} => ({
|
||||
options /*: AppleBuildOptions */,
|
||||
...args /*: ReadonlyArray<string> */
|
||||
) /*: BuildTasks */ => ({
|
||||
validate: task(
|
||||
FIRST,
|
||||
"Check you've run xcode-select --install for xcodebuild",
|
||||
@@ -165,8 +178,8 @@ export const tasks = {
|
||||
/* eslint-disable-next-line no-bitwise */
|
||||
fs.constants.F_OK | fs.constants.R_OK,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error('Please run: yarn run bootstrap ios: ' + e.message);
|
||||
} catch (e /*: unknown */) {
|
||||
throw new Error('Please run: yarn run bootstrap ios: ' + String(e));
|
||||
}
|
||||
}
|
||||
checkPodfileInSyncWithManifest(
|
||||
@@ -185,7 +198,6 @@ export const tasks = {
|
||||
_args.push('-scheme', options.scheme);
|
||||
}
|
||||
if (options.destination != null) {
|
||||
// The user doesn't want a generic target, they know better.
|
||||
switch (options.destination) {
|
||||
case 'simulator':
|
||||
_args.push('-sdk', 'iphonesimulator');
|
||||
@@ -204,9 +216,7 @@ export const tasks = {
|
||||
|
||||
// 3. Install the built app on a simulator or device
|
||||
ios: {
|
||||
install: (
|
||||
options: AppleInstallApp,
|
||||
): {validate: Task<void>, install: Task<ExecaPromise>} => ({
|
||||
install: (options /*: AppleInstallApp */) /*: InstallTasks */ => ({
|
||||
validate: task(
|
||||
FIRST,
|
||||
"Check you've run xcode-select --install for xcrun",
|
||||
@@ -225,3 +235,5 @@ export const tasks = {
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {tasks};
|
||||
+58
-33
@@ -8,19 +8,27 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*::
|
||||
import type {Task} from './types';
|
||||
import type {ExecaPromise, Options as ExecaOptions} from 'execa';
|
||||
*/
|
||||
|
||||
import {assertDependencies, isMacOS, isOnPath, isWindows, task} from './utils';
|
||||
import execa from 'execa';
|
||||
import {existsSync, readdirSync, rm} from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
const {
|
||||
assertDependencies,
|
||||
isMacOS,
|
||||
isOnPath,
|
||||
isWindows,
|
||||
task,
|
||||
} = require('./utils');
|
||||
const execa = require('execa');
|
||||
const {existsSync, readdirSync, rm} = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const FIRST = 1,
|
||||
SECOND = 2;
|
||||
|
||||
const rmrf = (pathname: string) => {
|
||||
const rmrf = (pathname /*: string */) => {
|
||||
if (!existsSync(pathname)) {
|
||||
return;
|
||||
}
|
||||
@@ -31,13 +39,13 @@ const rmrf = (pathname: string) => {
|
||||
* Removes the contents of a directory matching a given pattern, but keeps the directory.
|
||||
* @private
|
||||
*/
|
||||
export function deleteDirectoryContents(
|
||||
directory: string,
|
||||
filePattern: RegExp,
|
||||
): () => Promise<void> {
|
||||
function deleteDirectoryContents(
|
||||
directory /*: string */,
|
||||
filePattern /*: RegExp */,
|
||||
) /*: () => Promise<void> */ {
|
||||
return async function deleteDirectoryContentsAction() {
|
||||
const base = path.dirname(directory);
|
||||
const files = readdirSync(base).filter((filename: string) =>
|
||||
const files = readdirSync(base).filter((filename /*: string */) =>
|
||||
filePattern.test(filename),
|
||||
);
|
||||
for (const filename of files) {
|
||||
@@ -50,7 +58,7 @@ export function deleteDirectoryContents(
|
||||
* Removes a directory recursively.
|
||||
* @private
|
||||
*/
|
||||
export function deleteDirectory(directory: string): () => Promise<void> {
|
||||
function deleteDirectory(directory /*: string */) /*: () => Promise<void> */ {
|
||||
return async function cleanDirectoryAction() {
|
||||
rmrf(directory);
|
||||
};
|
||||
@@ -60,14 +68,15 @@ export function deleteDirectory(directory: string): () => Promise<void> {
|
||||
* Deletes the contents of the tmp directory matching a given pattern.
|
||||
* @private
|
||||
*/
|
||||
export function deleteTmpDirectoryContents(
|
||||
filepattern: RegExp,
|
||||
): () => Promise<void> {
|
||||
function deleteTmpDirectoryContents(
|
||||
filepattern /*: RegExp */,
|
||||
) /*: () => Promise<void> */ {
|
||||
return deleteDirectoryContents(os.tmpdir(), filepattern);
|
||||
}
|
||||
|
||||
const platformGradlew = isWindows ? 'gradlew.bat' : 'gradlew';
|
||||
|
||||
/*::
|
||||
type CocoaPodsClean = {
|
||||
clean: Task<ExecaPromise>,
|
||||
};
|
||||
@@ -104,14 +113,15 @@ type CleanTasks = {
|
||||
yarn: (projectRootDir: string) => YarnClean,
|
||||
cocoapods?: (projectRootDir: string) => CocoaPodsClean,
|
||||
};
|
||||
*/
|
||||
|
||||
// The tasks that cleanup various build artefacts.
|
||||
/* eslint sort-keys: "off" */
|
||||
export const tasks: CleanTasks = {
|
||||
const tasks /*: CleanTasks */ = {
|
||||
/**
|
||||
* Cleans up the Android Gradle cache
|
||||
*/
|
||||
android: (androidSrcDir: ?string, opts?: ExecaOptions) => ({
|
||||
android: (androidSrcDir /*: ?string */, opts /*: ?ExecaOptions */) => ({
|
||||
validate: task(FIRST, 'Check gradlew is available', () => {
|
||||
assertDependencies(isOnPath(platformGradlew, 'Gradle wrapper'));
|
||||
}),
|
||||
@@ -150,37 +160,43 @@ export const tasks: CleanTasks = {
|
||||
/**
|
||||
* Cleans up the `node_modules` folder and optionally garbage collects the npm cache.
|
||||
*/
|
||||
npm: (projectRootDir: string) => ({
|
||||
npm: (projectRootDir /*: string */) => ({
|
||||
node_modules: task(
|
||||
FIRST,
|
||||
'🧹 Clean node_modules',
|
||||
deleteDirectory(path.join(projectRootDir, 'node_modules')),
|
||||
),
|
||||
verify_cache: task(SECOND, '🔬 Verify npm cache', (opts?: ExecaOptions) =>
|
||||
execa('npm', ['cache', 'verify'], {cwd: projectRootDir, ...opts}),
|
||||
verify_cache: task(
|
||||
SECOND,
|
||||
'🔬 Verify npm cache',
|
||||
(opts /*: ?ExecaOptions */) =>
|
||||
execa('npm', ['cache', 'verify'], {cwd: projectRootDir, ...opts}),
|
||||
),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops Watchman and clears its cache
|
||||
*/
|
||||
watchman: (projectRootDir: string) => ({
|
||||
stop: task(FIRST, '✋ Stop Watchman', (opts?: ExecaOptions) =>
|
||||
watchman: (projectRootDir /*: string */) => ({
|
||||
stop: task(FIRST, '✋ Stop Watchman', (opts /*: ?ExecaOptions */) =>
|
||||
execa(isWindows ? 'tskill' : 'killall', ['watchman'], {
|
||||
cwd: projectRootDir,
|
||||
...opts,
|
||||
}),
|
||||
),
|
||||
cache: task(SECOND, '🧹 Delete Watchman cache', (opts?: ExecaOptions) =>
|
||||
execa('watchman', ['watch-del-all'], {cwd: projectRootDir, ...opts}),
|
||||
cache: task(
|
||||
SECOND,
|
||||
'🧹 Delete Watchman cache',
|
||||
(opts /*: ?ExecaOptions */) =>
|
||||
execa('watchman', ['watch-del-all'], {cwd: projectRootDir, ...opts}),
|
||||
),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Cleans up the Yarn cache
|
||||
*/
|
||||
yarn: (projectRootDir: string) => ({
|
||||
clean: task(FIRST, '🧹 Clean Yarn cache', (opts?: ExecaOptions) =>
|
||||
yarn: (projectRootDir /*: string */) => ({
|
||||
clean: task(FIRST, '🧹 Clean Yarn cache', (opts /*: ?ExecaOptions */) =>
|
||||
execa('yarn', ['cache', 'clean'], {cwd: projectRootDir, ...opts}),
|
||||
),
|
||||
}),
|
||||
@@ -190,13 +206,22 @@ if (isMacOS) {
|
||||
/**
|
||||
* Cleans up the local and global CocoaPods cache
|
||||
*/
|
||||
tasks.cocoapods = (projectRootDir: string) => ({
|
||||
// TODO: add project root
|
||||
clean: task(FIRST, '🧹 Clean CocoaPods pod cache', (opts?: ExecaOptions) =>
|
||||
execa('bundle', ['exec', 'pod', 'deintegrate'], {
|
||||
cwd: projectRootDir,
|
||||
...opts,
|
||||
}),
|
||||
tasks.cocoapods = (projectRootDir /*: string */) => ({
|
||||
clean: task(
|
||||
FIRST,
|
||||
'🧹 Clean CocoaPods pod cache',
|
||||
(opts /*: ?ExecaOptions */) =>
|
||||
execa('bundle', ['exec', 'pod', 'deintegrate'], {
|
||||
cwd: projectRootDir,
|
||||
...opts,
|
||||
}),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
deleteDirectory,
|
||||
deleteDirectoryContents,
|
||||
deleteTmpDirectoryContents,
|
||||
tasks,
|
||||
};
|
||||
+2
@@ -8,8 +8,10 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*::
|
||||
export type Task<R = unknown> = {
|
||||
order: number,
|
||||
label: string,
|
||||
action: () => R,
|
||||
};
|
||||
*/
|
||||
+29
-13
@@ -8,16 +8,18 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*::
|
||||
import type {Task} from './types';
|
||||
*/
|
||||
|
||||
import execa from 'execa';
|
||||
import os from 'os';
|
||||
const execa = require('execa');
|
||||
const os = require('os');
|
||||
|
||||
export function task<R>(
|
||||
order: number,
|
||||
label: string,
|
||||
action: Task<R>['action'],
|
||||
): Task<R> {
|
||||
function task /*:: <R> */(
|
||||
order /*: number */,
|
||||
label /*: string */,
|
||||
action /*: Task<R>['action'] */,
|
||||
) /*: Task<R> */ {
|
||||
return {
|
||||
action,
|
||||
label,
|
||||
@@ -25,19 +27,24 @@ export function task<R>(
|
||||
};
|
||||
}
|
||||
|
||||
export const isWindows = os.platform() === 'win32';
|
||||
export const isMacOS = os.platform() === 'darwin';
|
||||
const isWindows = os.platform() === 'win32';
|
||||
const isMacOS = os.platform() === 'darwin';
|
||||
|
||||
export const toPascalCase = (label: string): string =>
|
||||
const toPascalCase = (label /*: string */) /*: string */ =>
|
||||
label.length === 0 ? '' : label[0].toUpperCase() + label.slice(1);
|
||||
|
||||
/*::
|
||||
type PathCheckResult = {
|
||||
found: boolean,
|
||||
dep: string,
|
||||
description: string,
|
||||
};
|
||||
*/
|
||||
|
||||
export function isOnPath(dep: string, description: string): PathCheckResult {
|
||||
function isOnPath(
|
||||
dep /*: string */,
|
||||
description /*: string */,
|
||||
) /*: PathCheckResult */ {
|
||||
const cmd = isWindows ? ['where', [dep]] : ['command', ['-v', dep]];
|
||||
try {
|
||||
return {
|
||||
@@ -54,8 +61,8 @@ export function isOnPath(dep: string, description: string): PathCheckResult {
|
||||
}
|
||||
}
|
||||
|
||||
export function assertDependencies(
|
||||
...deps: ReadonlyArray<ReturnType<typeof isOnPath>>
|
||||
function assertDependencies(
|
||||
...deps /*: ReadonlyArray<ReturnType<typeof isOnPath>> */
|
||||
) {
|
||||
for (const {found, dep, description} of deps) {
|
||||
if (!found) {
|
||||
@@ -63,3 +70,12 @@ export function assertDependencies(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
assertDependencies,
|
||||
isMacOS,
|
||||
isOnPath,
|
||||
isWindows,
|
||||
task,
|
||||
toPascalCase,
|
||||
};
|
||||
+6
-4
@@ -26,17 +26,17 @@
|
||||
//
|
||||
// For react-native@0.75.0 you have to have a version of XCode >= 12
|
||||
|
||||
export const android = {
|
||||
const android = {
|
||||
ANDROID_NDK: '>= 23.x',
|
||||
ANDROID_SDK: '>= 33.x',
|
||||
};
|
||||
|
||||
export const apple = {
|
||||
const apple = {
|
||||
COCOAPODS: '>= 1.10.0',
|
||||
XCODE: '>= 12.x',
|
||||
};
|
||||
|
||||
export const common = {
|
||||
const common = {
|
||||
BUN: '>= 1.0.0',
|
||||
JAVA: '>= 17 <= 20',
|
||||
NODE_JS: '>= 18',
|
||||
@@ -45,8 +45,10 @@ export const common = {
|
||||
YARN: '>= 1.10.x',
|
||||
};
|
||||
|
||||
export const all = {
|
||||
const all = {
|
||||
...apple,
|
||||
...android,
|
||||
...common,
|
||||
};
|
||||
|
||||
module.exports = {all, android, apple, common};
|
||||
@@ -12,30 +12,11 @@
|
||||
import {Command} from 'commander';
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @react-native/monorepo/sort-imports
|
||||
const {patchCoreCLIUtilsPackageJSON} = require('./scripts/monorepo');
|
||||
|
||||
function injectCoreCLIUtilsRuntimePatch() {
|
||||
patchCoreCLIUtilsPackageJSON(true);
|
||||
const cleared = {
|
||||
status: false,
|
||||
};
|
||||
['exit', 'SIGUSR1', 'SIGUSR2', 'uncaughtException'].forEach(event => {
|
||||
if (cleared.status) {
|
||||
return;
|
||||
}
|
||||
patchCoreCLIUtilsPackageJSON(false);
|
||||
cleared.status = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env.BUILD_EXCLUDE_BABEL_REGISTER == null) {
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
require('../../scripts/shared/babelRegister').registerForMonorepo();
|
||||
}
|
||||
|
||||
injectCoreCLIUtilsRuntimePatch();
|
||||
|
||||
const program /*: Command */ = require('./cli.flow.js').default;
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@react-native/babel-preset": "0.87.0-main",
|
||||
"@react-native/core-cli-utils": "0.87.0-main",
|
||||
"@react-native/core-cli-utils": "*",
|
||||
"@react-native/eslint-config": "0.87.0-main",
|
||||
"@react-native/metro-config": "0.87.0-main",
|
||||
"@react-native/typescript-config": "0.87.0-main",
|
||||
|
||||
@@ -1,46 +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
|
||||
*/
|
||||
|
||||
// To be able to execute the cli as a yarn script, we have to strip our yarn types.
|
||||
// This causes problems for some of our dependencies, because they live in Meta internals,
|
||||
// Github and in NPM:
|
||||
// - Github and Meta: dynamicly transpile our dependencies. They each have to register on the monorepo
|
||||
// - NPM: `yarn run build`, and it should update the package.json's exports, main and files
|
||||
function patchCoreCLIUtilsPackageJSON(patch /*: boolean */) {
|
||||
const log = require('debug');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function repositoryPath(relativePath /*: string */) {
|
||||
return path.join(__dirname, '..', '..', '..', relativePath);
|
||||
}
|
||||
|
||||
const packageJsonPath = repositoryPath(
|
||||
'packages/core-cli-utils/package.json',
|
||||
);
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
const target = patch ? './src/monorepo.js' : './src/index.flow.js';
|
||||
if (pkg.main === target) {
|
||||
return;
|
||||
}
|
||||
pkg.main = target;
|
||||
pkg.exports['.'] = target;
|
||||
log(
|
||||
`Patched: ${JSON.stringify(
|
||||
{main: pkg.main, exports: pkg.exports},
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
|
||||
}
|
||||
|
||||
module.exports.patchCoreCLIUtilsPackageJSON = patchCoreCLIUtilsPackageJSON;
|
||||
@@ -41,10 +41,6 @@ const buildConfig: BuildConfig = {
|
||||
'community-cli-plugin': {
|
||||
target: 'node',
|
||||
},
|
||||
'core-cli-utils': {
|
||||
emitTypeScriptDefs: true,
|
||||
target: 'node',
|
||||
},
|
||||
'debugger-shell': {
|
||||
emitTypeScriptDefs: true,
|
||||
target: 'node',
|
||||
|
||||
@@ -219,12 +219,18 @@ function _prepareHelloWorld(
|
||||
// and update the dependencies and devDependencies of packages scoped as @react-native
|
||||
// to the version passed as parameter
|
||||
for (const key of Object.keys(packageJson.dependencies)) {
|
||||
if (key.startsWith('@react-native/')) {
|
||||
if (
|
||||
key.startsWith('@react-native/') &&
|
||||
packageJson.dependencies[key] !== '*'
|
||||
) {
|
||||
packageJson.dependencies[key] = version;
|
||||
}
|
||||
}
|
||||
for (const key of Object.keys(packageJson.devDependencies)) {
|
||||
if (key.startsWith('@react-native/')) {
|
||||
if (
|
||||
key.startsWith('@react-native/') &&
|
||||
packageJson.devDependencies[key] !== '*'
|
||||
) {
|
||||
packageJson.devDependencies[key] = version;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user