first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
# @react-native/community-cli-plugin
> This is an internal dependency of React Native. **Please don't depend on it directly.**
CLI entry points supporting core React Native development features.
Formerly [@react-native-community/cli-plugin-metro](https://www.npmjs.com/package/@react-native-community/cli-plugin-metro).
## Commands
### `start`
Start the React Native development server.
#### Usage
```sh
npx @react-native-community/cli start [options]
```
#### Options
| Option | Description |
| - | - |
| `--port <number>` | Set the server port. |
| `--host <string>` | Set the server host. |
| `--projectRoot <path>` | Set the path to the project root. |
| `--watchFolders <list>` | Specify additional folders to be added to the watch list. |
| `--assetPlugins <list>` | Specify additional asset plugins. |
| `--sourceExts <list>` | Specify additional source extensions to bundle. |
| `--max-workers <number>` | Set the maximum number of workers the worker-pool will spawn for transforming files. Defaults to the number of the cores available on your machine. |
| `--transformer <string>` | Specify a custom transformer. |
| `--reset-cache` | Remove cached files. |
| `--custom-log-reporter-path <string>` | Specify a module path exporting a replacement for `TerminalReporter`. |
| `--https` | Enable HTTPS connections. |
| `--key <path>`| Specify path to a custom SSL key. |
| `--cert <path>` | Specify path to a custom SSL cert. |
| `--config <string>` | Path to the CLI configuration file. |
| `--no-interactive` | Disable interactive mode. |
| `--client-logs` | **[Deprecated]** Enable plain text JavaScript log streaming for all connected apps. |
### `bundle`
Build the bundle for the provided JavaScript entry file.
#### Usage
```sh
npx @react-native-community/cli bundle --entry-file <path> [options]
```
#### Options
| Option | Description |
| - | - |
| `--entry-file <path>` | Set the path to the root JavaScript entry file. |
| `--platform <string>` | Set the target platform (either `"android"` or `"ios"`). Defaults to `"ios"`. |
| `--transformer <string>` | Specify a custom transformer. |
| `--dev [boolean]` | If `false`, warnings are disabled and the bundle is minified. Defaults to `true`. |
| `--minify [boolean]` | Allows overriding whether bundle is minified. Defaults to `false` if `--dev` is set. Disabling minification can be useful for speeding up production builds for testing purposes. |
| `--bundle-output <string>` | Specify the path to store the resulting bundle. |
| `--bundle-encoding <string>` | Specify the encoding for writing the bundle (<https://nodejs.org/api/buffer.html#buffer_buffer>). |
| `--resolver-option <string...>` | Custom resolver options of the form key=value. URL-encoded. May be specified multiple times. |
| `--sourcemap-output <string>` | Specify the path to store the source map file for the resulting bundle. |
| `--sourcemap-sources-root <string>` | Set the root path for source map entries. |
| `--sourcemap-use-absolute-path` | Report `SourceMapURL` using its full path. |
| `--max-workers <number>` | Set the maximum number of workers the worker-pool will spawn for transforming files. Defaults to the number of the cores available on your machine. |
| `--assets-dest <string>` | Specify the directory path for storing assets referenced in the bundle. |
| `--reset-cache` | Remove cached files. |
| `--read-global-cache` | Attempt to fetch transformed JS code from the global cache, if configured. Defaults to `false`. |
| `--config <string>` | Path to the CLI configuration file. |
## Contributing
Changes to this package can be made locally and tested against the `rn-tester` app, per the [Contributing guide](https://reactnative.dev/contributing/overview#contributing-code). During development, this package is automatically run from source with no build step.

View File

@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.cleanAssetCatalog = cleanAssetCatalog;
exports.getImageSet = getImageSet;
exports.isCatalogAsset = isCatalogAsset;
exports.writeImageSet = writeImageSet;
var _assetPathUtils = _interopRequireDefault(require("./assetPathUtils"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function cleanAssetCatalog(catalogDir) {
const files = _fs.default
.readdirSync(catalogDir)
.filter((file) => file.endsWith(".imageset"));
for (const file of files) {
_fs.default.rmSync(_path.default.join(catalogDir, file), {
recursive: true,
force: true,
});
}
}
function getImageSet(catalogDir, asset, scales) {
const fileName = _assetPathUtils.default.getResourceIdentifier(asset);
return {
basePath: _path.default.join(catalogDir, `${fileName}.imageset`),
files: scales.map((scale, idx) => {
const suffix = scale === 1 ? "" : `@${scale}x`;
return {
name: `${fileName + suffix}.${asset.type}`,
scale,
src: asset.files[idx],
};
}),
};
}
function isCatalogAsset(asset) {
return asset.type === "png" || asset.type === "jpg" || asset.type === "jpeg";
}
function writeImageSet(imageSet) {
_fs.default.mkdirSync(imageSet.basePath, {
recursive: true,
});
for (const file of imageSet.files) {
const dest = _path.default.join(imageSet.basePath, file.name);
_fs.default.copyFileSync(file.src, dest);
}
_fs.default.writeFileSync(
_path.default.join(imageSet.basePath, "Contents.json"),
JSON.stringify({
images: imageSet.files.map((file) => ({
filename: file.name,
idiom: "universal",
scale: `${file.scale}x`,
})),
info: {
author: "xcode",
version: 1,
},
}),
);
}

View File

@@ -0,0 +1,28 @@
/**
* 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
*/
import type { AssetData } from "metro";
declare export function cleanAssetCatalog(catalogDir: string): void;
type ImageSet = {
basePath: string,
files: { name: string, src: string, scale: number }[],
};
declare export function getImageSet(
catalogDir: string,
asset: AssetData,
scales: $ReadOnlyArray<number>,
): ImageSet;
declare export function isCatalogAsset(asset: AssetData): boolean;
declare export function writeImageSet(imageSet: ImageSet): void;

View File

@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
function getAndroidAssetSuffix(scale) {
switch (scale) {
case 0.75:
return "ldpi";
case 1:
return "mdpi";
case 1.5:
return "hdpi";
case 2:
return "xhdpi";
case 3:
return "xxhdpi";
case 4:
return "xxxhdpi";
default:
return "";
}
}
const drawableFileTypes = new Set(["gif", "jpeg", "jpg", "png", "webp", "xml"]);
function getAndroidResourceFolderName(asset, scale) {
if (!drawableFileTypes.has(asset.type)) {
return "raw";
}
const suffix = getAndroidAssetSuffix(scale);
if (!suffix) {
throw new Error(
`Don't know which android drawable suffix to use for asset: ${JSON.stringify(asset)}`,
);
}
return `drawable-${suffix}`;
}
function getResourceIdentifier(asset) {
const folderPath = getBasePath(asset);
return `${folderPath}/${asset.name}`
.toLowerCase()
.replace(/\//g, "_")
.replace(/([^a-z0-9_])/g, "")
.replace(/^(?:assets|assetsunstable_path)_/, "");
}
function getBasePath(asset) {
let basePath = asset.httpServerLocation;
if (basePath[0] === "/") {
basePath = basePath.substr(1);
}
return basePath;
}
var _default = (exports.default = {
drawableFileTypes,
getAndroidAssetSuffix,
getAndroidResourceFolderName,
getResourceIdentifier,
getBasePath,
});

View File

@@ -0,0 +1,42 @@
/**
* 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 PackagerAsset = $ReadOnly<{
httpServerLocation: string,
name: string,
type: string,
...
}>;
/**
* FIXME: using number to represent discrete scale numbers is fragile in essence because of
* floating point numbers imprecision.
*/
declare function getAndroidAssetSuffix(scale: number): string;
// See https://developer.android.com/guide/topics/resources/drawable-resource.html
declare const drawableFileTypes: Set<string>;
declare function getAndroidResourceFolderName(
asset: PackagerAsset,
scale: number,
): string;
declare function getResourceIdentifier(asset: PackagerAsset): string;
declare function getBasePath(asset: PackagerAsset): string;
declare export default {
drawableFileTypes: typeof drawableFileTypes,
getAndroidAssetSuffix: typeof getAndroidAssetSuffix,
getAndroidResourceFolderName: typeof getAndroidResourceFolderName,
getResourceIdentifier: typeof getResourceIdentifier,
getBasePath: typeof getBasePath,
};

View File

@@ -0,0 +1,83 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.unstable_buildBundleWithConfig = exports.default = void 0;
var _loadMetroConfig = _interopRequireDefault(
require("../../utils/loadMetroConfig"),
);
var _parseKeyValueParamArray = _interopRequireDefault(
require("../../utils/parseKeyValueParamArray"),
);
var _saveAssets = _interopRequireDefault(require("./saveAssets"));
var _fs = require("fs");
var _metro = require("metro");
var _path = _interopRequireDefault(require("path"));
var _util = require("util");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
async function buildBundle(_argv, ctx, args, bundleImpl) {
const config = await (0, _loadMetroConfig.default)(ctx, {
maxWorkers: args.maxWorkers,
resetCache: args.resetCache,
config: args.config,
});
return buildBundleWithConfig(args, config, bundleImpl);
}
async function buildBundleWithConfig(args, config, bundleImpl) {
const customResolverOptions = (0, _parseKeyValueParamArray.default)(
args.resolverOption ?? [],
);
if (config.resolver.platforms.indexOf(args.platform) === -1) {
console.error(
`${(0, _util.styleText)("red", "error")}: Invalid platform ${args.platform ? `"${(0, _util.styleText)("bold", args.platform)}" ` : ""}selected.`,
);
console.info(
`Available platforms are: ${config.resolver.platforms.map((x) => `"${(0, _util.styleText)("bold", x)}"`).join(", ")}. If you are trying to bundle for an out-of-tree platform, it may not be installed.`,
);
throw new Error("Bundling failed");
}
process.env.NODE_ENV = args.dev ? "development" : "production";
let sourceMapUrl = args.sourcemapOutput;
if (sourceMapUrl != null && !args.sourcemapUseAbsolutePath) {
sourceMapUrl = _path.default.basename(sourceMapUrl);
}
const runBuildOptions = {
assets: args.assetsDest != null,
bundleOut: args.bundleOutput,
customResolverOptions,
dev: args.dev,
entry: args.entryFile,
minify: args.minify !== undefined ? args.minify : !args.dev,
output: bundleImpl,
platform: args.platform,
sourceMap: args.sourcemapOutput != null,
sourceMapOut: args.sourcemapOutput,
sourceMapUrl,
unstable_transformProfile: args.unstableTransformProfile,
};
await _fs.promises.mkdir(_path.default.dirname(args.bundleOutput), {
recursive: true,
mode: 0o755,
});
const result = await (0, _metro.runBuild)(config, runBuildOptions);
if (args.assetsDest == null) {
console.warn("Warning: Assets destination folder is not set, skipping...");
return;
}
if (result.assets == null) {
throw new Error("Assets missing from Metro's runBuild result");
}
const outputAssets = result.assets;
await (0, _saveAssets.default)(
outputAssets,
args.platform,
args.assetsDest,
args.assetCatalogDest,
);
}
const unstable_buildBundleWithConfig = (exports.unstable_buildBundleWithConfig =
buildBundleWithConfig);
var _default = (exports.default = buildBundle);

View File

@@ -0,0 +1,63 @@
/**
* 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
*/
import type { Config } from "@react-native-community/cli-types";
import type { RunBuildOptions } from "metro";
import type { ConfigT } from "metro-config";
export type BundleCommandArgs = {
assetsDest?: string,
assetCatalogDest?: string,
entryFile: string,
resetCache: boolean,
resetGlobalCache: boolean,
transformer?: string,
minify?: boolean,
config?: string,
platform: string,
dev: boolean,
bundleOutput: string,
bundleEncoding?: "utf8" | "utf16le" | "ascii",
maxWorkers?: number,
sourcemapOutput?: string,
sourcemapSourcesRoot?: string,
sourcemapUseAbsolutePath: boolean,
verbose: boolean,
unstableTransformProfile: "hermes-stable" | "hermes-canary" | "default",
indexedRamBundle?: boolean,
resolverOption?: Array<string>,
};
declare function buildBundle(
_argv: Array<string>,
ctx: Config,
args: BundleCommandArgs,
bundleImpl?: RunBuildOptions["output"],
): Promise<void>;
declare function buildBundleWithConfig(
args: BundleCommandArgs,
config: ConfigT,
bundleImpl?: RunBuildOptions["output"],
): Promise<void>;
/**
* UNSTABLE: This function is likely to be relocated and its API changed in
* the near future. `@react-native/community-cli-plugin` should not be directly
* depended on by projects or integrators -- this is exported for legacy
* compatibility.
*
* Create a bundle using a pre-loaded Metro config. The config can be
* re-used for several bundling calls if multiple platforms are being
* bundled.
*/
declare export const unstable_buildBundleWithConfig: typeof buildBundleWithConfig;
declare export default typeof buildBundle;

View File

@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _assetPathUtils = _interopRequireDefault(require("./assetPathUtils"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
async function createKeepFileAsync(assets, outputDirectory) {
if (!assets.length) {
return;
}
const assetsList = [];
for (const asset of assets) {
const prefix = _assetPathUtils.default.drawableFileTypes.has(asset.type)
? "drawable"
: "raw";
assetsList.push(
`@${prefix}/${_assetPathUtils.default.getResourceIdentifier(asset)}`,
);
}
const keepPath = _path.default.join(outputDirectory, "raw/keep.xml");
const content = `<resources xmlns:tools="http://schemas.android.com/tools" tools:keep="${assetsList.join(",")}" />\n`;
await _fs.default.promises.mkdir(_path.default.dirname(keepPath), {
recursive: true,
});
await _fs.default.promises.writeFile(keepPath, content);
}
var _default = (exports.default = createKeepFileAsync);

View File

@@ -0,0 +1,18 @@
/**
* 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
*/
import type { AssetData } from "metro";
declare function createKeepFileAsync(
assets: $ReadOnlyArray<AssetData>,
outputDirectory: string,
): Promise<void>;
declare export default typeof createKeepFileAsync;

View File

@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
const ALLOWED_SCALES = {
ios: [1, 2, 3],
};
function filterPlatformAssetScales(platform, scales) {
const whitelist = ALLOWED_SCALES[platform];
if (!whitelist) {
return scales;
}
const result = scales.filter((scale) => whitelist.indexOf(scale) > -1);
if (result.length === 0 && scales.length > 0) {
const maxScale = whitelist[whitelist.length - 1];
for (const scale of scales) {
if (scale > maxScale) {
result.push(scale);
break;
}
}
if (result.length === 0) {
result.push(scales[scales.length - 1]);
}
}
return result;
}
var _default = (exports.default = filterPlatformAssetScales);

View File

@@ -0,0 +1,16 @@
/**
* 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
*/
declare function filterPlatformAssetScales(
platform: string,
scales: $ReadOnlyArray<number>,
): $ReadOnlyArray<number>;
declare export default typeof filterPlatformAssetScales;

View File

@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _assetPathUtils = _interopRequireDefault(require("./assetPathUtils"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function getAssetDestPathAndroid(asset, scale) {
const androidFolder = _assetPathUtils.default.getAndroidResourceFolderName(
asset,
scale,
);
const fileName = _assetPathUtils.default.getResourceIdentifier(asset);
return _path.default.join(androidFolder, `${fileName}.${asset.type}`);
}
var _default = (exports.default = getAssetDestPathAndroid);

View File

@@ -0,0 +1,18 @@
/**
* 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
*/
import type { PackagerAsset } from "./assetPathUtils";
declare function getAssetDestPathAndroid(
asset: PackagerAsset,
scale: number,
): string;
declare export default typeof getAssetDestPathAndroid;

View File

@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function getAssetDestPathIOS(asset, scale) {
const suffix = scale === 1 ? "" : `@${scale}x`;
const fileName = `${asset.name + suffix}.${asset.type}`;
return _path.default.join(
asset.httpServerLocation.substr(1).replace(/\.\.\//g, "_"),
fileName,
);
}
var _default = (exports.default = getAssetDestPathIOS);

View File

@@ -0,0 +1,18 @@
/**
* 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
*/
import type { PackagerAsset } from "./assetPathUtils";
declare function getAssetDestPathIOS(
asset: PackagerAsset,
scale: number,
): string;
declare export default typeof getAssetDestPathIOS;

View File

@@ -0,0 +1,118 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _buildBundle = _interopRequireDefault(require("./buildBundle"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const bundleCommand = {
name: "bundle",
description: "Build the bundle for the provided JavaScript entry file.",
func: _buildBundle.default,
options: [
{
name: "--entry-file <path>",
description:
"Path to the root JS file, either absolute or relative to JS root",
},
{
name: "--platform <string>",
description: 'Either "ios" or "android"',
default: "ios",
},
{
name: "--transformer <string>",
description: "Specify a custom transformer to be used",
},
{
name: "--dev [boolean]",
description: "If false, warnings are disabled and the bundle is minified",
parse: (val) => val !== "false",
default: true,
},
{
name: "--minify [boolean]",
description:
"Allows overriding whether bundle is minified. This defaults to " +
"false if dev is true, and true if dev is false. Disabling minification " +
"can be useful for speeding up production builds for testing purposes.",
parse: (val) => val !== "false",
},
{
name: "--bundle-output <string>",
description:
"File name where to store the resulting bundle, ex. /tmp/groups.bundle",
},
{
name: "--bundle-encoding <string>",
description:
"Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer).",
default: "utf8",
},
{
name: "--max-workers <number>",
description:
"Specifies the maximum number of workers the worker-pool " +
"will spawn for transforming files. This defaults to the number of the " +
"cores available on your machine.",
parse: (workers) => Number(workers),
},
{
name: "--sourcemap-output <string>",
description:
"File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map",
},
{
name: "--sourcemap-sources-root <string>",
description:
"Path to make sourcemap's sources entries relative to, ex. /root/dir",
},
{
name: "--sourcemap-use-absolute-path",
description: "Report SourceMapURL using its full path",
default: false,
},
{
name: "--assets-dest <string>",
description:
"Directory name where to store assets referenced in the bundle",
},
{
name: "--unstable-transform-profile <string>",
description:
"Experimental, transform JS for a specific JS engine. Currently supported: hermes, hermes-canary, default",
default: "default",
},
{
name: "--asset-catalog-dest [string]",
description: "Path where to create an iOS Asset Catalog for images",
},
{
name: "--reset-cache",
description: "Removes cached files",
default: false,
},
{
name: "--read-global-cache",
description:
"Try to fetch transformed JS code from the global cache, if configured.",
default: false,
},
{
name: "--config <string>",
description: "Path to the CLI configuration file",
parse: (val) => _path.default.resolve(val),
},
{
name: "--resolver-option <string...>",
description:
"Custom resolver options of the form key=value. URL-encoded. May be specified multiple times.",
parse: (val, previous = []) => previous.concat([val]),
},
],
};
var _default = (exports.default = bundleCommand);

View File

@@ -0,0 +1,17 @@
/**
* 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
*/
import type { Command } from "@react-native-community/cli-types";
export type { BundleCommandArgs } from "./buildBundle";
declare const bundleCommand: Command;
declare export default typeof bundleCommand;

View File

@@ -0,0 +1,129 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _assetCatalogIOS = require("./assetCatalogIOS");
var _createKeepFileAsync = _interopRequireDefault(
require("./createKeepFileAsync"),
);
var _filterPlatformAssetScales = _interopRequireDefault(
require("./filterPlatformAssetScales"),
);
var _getAssetDestPathAndroid = _interopRequireDefault(
require("./getAssetDestPathAndroid"),
);
var _getAssetDestPathIOS = _interopRequireDefault(
require("./getAssetDestPathIOS"),
);
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _util = require("util");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
async function saveAssets(assets, platform, assetsDest, assetCatalogDest) {
if (assetsDest == null) {
console.warn("Warning: Assets destination folder is not set, skipping...");
return;
}
const filesToCopy = {};
const getAssetDestPath =
platform === "android"
? _getAssetDestPathAndroid.default
: _getAssetDestPathIOS.default;
const addAssetToCopy = (asset) => {
const validScales = new Set(
(0, _filterPlatformAssetScales.default)(platform, asset.scales),
);
asset.scales.forEach((scale, idx) => {
if (!validScales.has(scale)) {
return;
}
const src = asset.files[idx];
const dest = _path.default.join(
assetsDest,
getAssetDestPath(asset, scale),
);
filesToCopy[src] = dest;
});
};
if (platform === "ios" && assetCatalogDest != null) {
const catalogDir = _path.default.join(
assetCatalogDest,
"RNAssets.xcassets",
);
if (!_fs.default.existsSync(catalogDir)) {
console.error(
`${(0, _util.styleText)("red", "error")}: Could not find asset catalog 'RNAssets.xcassets' in ${assetCatalogDest}. Make sure to create it if it does not exist.`,
);
return;
}
console.info("Adding images to asset catalog", catalogDir);
(0, _assetCatalogIOS.cleanAssetCatalog)(catalogDir);
for (const asset of assets) {
if ((0, _assetCatalogIOS.isCatalogAsset)(asset)) {
const imageSet = (0, _assetCatalogIOS.getImageSet)(
catalogDir,
asset,
(0, _filterPlatformAssetScales.default)(platform, asset.scales),
);
(0, _assetCatalogIOS.writeImageSet)(imageSet);
} else {
addAssetToCopy(asset);
}
}
console.info("Done adding images to asset catalog");
} else {
assets.forEach(addAssetToCopy);
}
if (platform === "android") {
await (0, _createKeepFileAsync.default)(assets, assetsDest);
}
return copyAll(filesToCopy);
}
function copyAll(filesToCopy) {
const queue = Object.keys(filesToCopy);
if (queue.length === 0) {
return Promise.resolve();
}
console.info(`Copying ${queue.length} asset files`);
return new Promise((resolve, reject) => {
const copyNext = (error) => {
if (error) {
reject(error);
return;
}
if (queue.length === 0) {
console.info("Done copying assets");
resolve();
} else {
const src = queue.shift();
const dest = filesToCopy[src];
copy(src, dest, copyNext);
}
};
copyNext();
});
}
function copy(src, dest, callback) {
const destDir = _path.default.dirname(dest);
_fs.default.mkdir(
destDir,
{
recursive: true,
},
(err) => {
if (err) {
callback(err);
return;
}
_fs.default
.createReadStream(src)
.pipe(_fs.default.createWriteStream(dest))
.on("finish", callback);
},
);
}
var _default = (exports.default = saveAssets);

View File

@@ -0,0 +1,20 @@
/**
* 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
*/
import type { AssetData } from "metro";
declare function saveAssets(
assets: $ReadOnlyArray<AssetData>,
platform: string,
assetsDest?: string,
assetCatalogDest?: string,
): Promise<void>;
declare export default typeof saveAssets;

View File

@@ -0,0 +1,123 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _util = require("util");
class OpenDebuggerKeyboardHandler {
#devServerUrl;
#reporter;
#targetsShownForSelection = null;
constructor({ devServerUrl, reporter }) {
this.#devServerUrl = devServerUrl;
this.#reporter = reporter;
}
async #tryOpenDebuggerForTarget(target) {
this.#targetsShownForSelection = null;
this.#clearTerminalMenu();
try {
await fetch(
new URL(
"/open-debugger?target=" + encodeURIComponent(target.id),
this.#devServerUrl,
).href,
{
method: "POST",
},
);
} catch (e) {
this.#log(
"error",
"Failed to open debugger for %s (%s): %s",
target.title,
target.description,
"Network error",
);
if (e.cause != null) {
this.#log("error", "Cause: %s", e.cause);
}
this.#clearTerminalMenu();
}
}
async handleOpenDebugger() {
this.#setTerminalMenu("Fetching available debugging targets...");
this.#targetsShownForSelection = null;
try {
const res = await fetch(this.#devServerUrl + "/json/list", {
method: "POST",
});
if (res.status !== 200) {
throw new Error(`Unexpected status code: ${res.status}`);
}
const targets = await res.json();
if (!Array.isArray(targets)) {
throw new Error("Expected array.");
}
if (targets.length === 0) {
this.#log("warn", "No connected targets");
this.#clearTerminalMenu();
} else if (targets.length === 1) {
const target = targets[0];
void this.#tryOpenDebuggerForTarget(target);
} else {
this.#targetsShownForSelection = targets;
if (targets.length > 9) {
this.#log(
"warn",
"10 or more debug targets available, showing the first 9.",
);
}
this.#setTerminalMenu(
`Multiple debug targets available, please select:\n ${targets
.slice(0, 9)
.map(
({ title }, i) =>
`${(0, _util.styleText)(["white", "inverse"], ` ${i + 1} `)} - "${title}"`,
)
.join("\n ")}`,
);
}
} catch (e) {
this.#log("error", `Failed to fetch debug targets: ${e.message}`);
this.#clearTerminalMenu();
}
}
maybeHandleTargetSelection(keyName) {
if (keyName >= "1" && keyName <= "9") {
const targetIndex = Number(keyName) - 1;
if (
this.#targetsShownForSelection != null &&
targetIndex < this.#targetsShownForSelection.length
) {
const target = this.#targetsShownForSelection[targetIndex];
void this.#tryOpenDebuggerForTarget(target);
return true;
}
}
return false;
}
dismiss() {
this.#clearTerminalMenu();
this.#targetsShownForSelection = null;
}
#log(level, ...data) {
this.#reporter.update({
type: "unstable_server_log",
level,
data,
});
}
#setTerminalMenu(message) {
this.#reporter.update({
type: "unstable_server_menu_updated",
message,
});
}
#clearTerminalMenu() {
this.#reporter.update({
type: "unstable_server_menu_cleared",
});
}
}
exports.default = OpenDebuggerKeyboardHandler;

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type { TerminalReporter } from "metro";
declare export default class OpenDebuggerKeyboardHandler {
constructor($$PARAM_0$$: {
devServerUrl: string,
reporter: TerminalReporter,
}): void;
/**
* Used in response to 'j' to debug - fetch the available debug targets and:
* - If no targets, warn
* - If one target, open it
* - If more, show a list. The keyboard listener should run subsequent key
* presses through maybeHandleTargetSelection, which will launch the
* debugger if a match is made.
*/
handleOpenDebugger(): Promise<void>;
/**
* Handle key presses that correspond to a valid selection from a visible
* selection list.
*
* @return true if we've handled the key as a target selection, false if the
* caller should handle the key.
*/
maybeHandleTargetSelection(keyName: string): boolean;
/**
* Dismiss any target selection UI, if shown.
*/
dismiss(): void;
}

View File

@@ -0,0 +1,103 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = attachKeyHandlers;
var _OpenDebuggerKeyboardHandler = _interopRequireDefault(
require("./OpenDebuggerKeyboardHandler"),
);
var _invariant = _interopRequireDefault(require("invariant"));
var _readline = _interopRequireDefault(require("readline"));
var _tty = require("tty");
var _util = require("util");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const CTRL_C = "\u0003";
const CTRL_D = "\u0004";
const RELOAD_TIMEOUT = 500;
const throttle = (callback, timeout) => {
let previousCallTimestamp = 0;
return () => {
const currentCallTimestamp = new Date().getTime();
if (currentCallTimestamp - previousCallTimestamp > timeout) {
previousCallTimestamp = currentCallTimestamp;
callback();
}
};
};
function attachKeyHandlers({ devServerUrl, messageSocket, reporter }) {
if (process.stdin.isTTY !== true) {
reporter.update({
type: "unstable_server_log",
level: "info",
data: "Interactive mode is not supported in this environment",
});
return;
}
_readline.default.emitKeypressEvents(process.stdin);
setRawMode(true);
const reload = throttle(() => {
reporter.update({
type: "unstable_server_log",
level: "info",
data: "Reloading connected app(s)...",
});
messageSocket.broadcast("reload", null);
}, RELOAD_TIMEOUT);
const openDebuggerKeyboardHandler = new _OpenDebuggerKeyboardHandler.default({
reporter,
devServerUrl,
});
process.stdin.on("keypress", (str, key) => {
if (openDebuggerKeyboardHandler.maybeHandleTargetSelection(key.name)) {
return;
}
switch (key.sequence) {
case "r":
reload();
break;
case "d":
reporter.update({
type: "unstable_server_log",
level: "info",
data: "Opening Dev Menu...",
});
messageSocket.broadcast("devMenu", null);
break;
case "j":
void openDebuggerKeyboardHandler.handleOpenDebugger();
break;
case CTRL_C:
case CTRL_D:
openDebuggerKeyboardHandler.dismiss();
reporter.update({
type: "unstable_server_log",
level: "info",
data: "Stopping server",
});
setRawMode(false);
process.stdin.pause();
process.emit("SIGINT");
process.exit();
}
});
reporter.update({
type: "unstable_server_log",
level: "info",
data: `Key commands available:
${(0, _util.styleText)(["bold", "inverse"], " r ")} - reload app(s)
${(0, _util.styleText)(["bold", "inverse"], " d ")} - open Dev Menu
${(0, _util.styleText)(["bold", "inverse"], " j ")} - open DevTools
`,
});
}
function setRawMode(enable) {
(0, _invariant.default)(
process.stdin instanceof _tty.ReadStream,
"process.stdin must be a readable stream to modify raw mode",
);
process.stdin.setRawMode(enable);
}

View File

@@ -0,0 +1,20 @@
/**
* 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
*/
import type { TerminalReporter } from "metro";
declare export default function attachKeyHandlers($$PARAM_0$$: {
devServerUrl: string,
messageSocket: $ReadOnly<{
broadcast: (type: string, params?: Record<string, mixed> | null) => void,
...
}>,
reporter: TerminalReporter,
}): void;

View File

@@ -0,0 +1,101 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _runServer = _interopRequireDefault(require("./runServer"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const startCommand = {
name: "start",
func: _runServer.default,
description: "Start the React Native development server.",
options: [
{
name: "--port <number>",
parse: Number,
},
{
name: "--host <string>",
default: "",
},
{
name: "--projectRoot <path>",
description: "Path to a custom project root",
parse: (val) => _path.default.resolve(val),
},
{
name: "--watchFolders <list>",
description:
"Specify any additional folders to be added to the watch list",
parse: (val) =>
val.split(",").map((folder) => _path.default.resolve(folder)),
},
{
name: "--assetPlugins <list>",
description:
"Specify any additional asset plugins to be used by the packager by full filepath",
parse: (val) => val.split(","),
},
{
name: "--sourceExts <list>",
description:
"Specify any additional source extensions to be used by the packager",
parse: (val) => val.split(","),
},
{
name: "--max-workers <number>",
description:
"Specifies the maximum number of workers the worker-pool " +
"will spawn for transforming files. This defaults to the number of the " +
"cores available on your machine.",
parse: (workers) => Number(workers),
},
{
name: "--transformer <string>",
description: "Specify a custom transformer to be used",
},
{
name: "--reset-cache, --resetCache",
description: "Removes cached files",
},
{
name: "--custom-log-reporter-path, --customLogReporterPath <string>",
description:
"Path to a JavaScript file that exports a log reporter as a replacement for TerminalReporter",
},
{
name: "--https",
description: "Enables https connections to the server",
},
{
name: "--key <path>",
description: "Path to custom SSL key",
},
{
name: "--cert <path>",
description: "Path to custom SSL cert",
},
{
name: "--config <string>",
description: "Path to the CLI configuration file",
parse: (val) => _path.default.resolve(val),
},
{
name: "--no-interactive",
description: "Disables interactive mode",
},
{
name: "--client-logs",
description:
"[Deprecated] Enable plain text JavaScript log streaming for all " +
"connected apps. This feature is deprecated and will be removed in " +
"future.",
default: false,
},
],
};
var _default = (exports.default = startCommand);

View File

@@ -0,0 +1,17 @@
/**
* 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
*/
import type { Command } from "@react-native-community/cli-types";
export type { StartCommandArgs } from "./runServer";
declare const startCommand: Command;
declare export default typeof startCommand;

View File

@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.createDevServerMiddleware = void 0;
const debug = require("debug")("ReactNative:CommunityCliPlugin");
const unusedStubWSServer = {};
const unusedMiddlewareStub = {};
const communityMiddlewareFallback = {
createDevServerMiddleware: (params) => ({
middleware: unusedMiddlewareStub,
websocketEndpoints: {},
messageSocketEndpoint: {
server: unusedStubWSServer,
broadcast: (method, _params) => {},
},
eventsSocketEndpoint: {
server: unusedStubWSServer,
reportEvent: (event) => {},
},
}),
};
try {
const communityCliPath = require.resolve("@react-native-community/cli");
const communityCliServerApiPath = require.resolve(
"@react-native-community/cli-server-api",
{
paths: [communityCliPath],
},
);
communityMiddlewareFallback.createDevServerMiddleware = require(
communityCliServerApiPath,
).createDevServerMiddleware;
} catch {
debug(`⚠️ Unable to find @react-native-community/cli-server-api
Starting the server without the community middleware.`);
}
const createDevServerMiddleware = (exports.createDevServerMiddleware =
communityMiddlewareFallback.createDevServerMiddleware);

View File

@@ -0,0 +1,38 @@
/**
* 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
*/
import type { Server } from "connect";
import type { TerminalReportableEvent } from "metro";
type MiddlewareReturn = {
middleware: Server,
websocketEndpoints: {
[path: string]: ws$WebSocketServer,
},
messageSocketEndpoint: {
server: ws$WebSocketServer,
broadcast: (method: string, params?: Record<string, mixed> | null) => void,
},
eventsSocketEndpoint: {
server: ws$WebSocketServer,
reportEvent: (event: TerminalReportableEvent) => void,
},
...
};
declare const communityMiddlewareFallback: {
createDevServerMiddleware: (params: {
host?: string,
port: number,
watchFolders: $ReadOnlyArray<string>,
}) => MiddlewareReturn,
};
declare export const createDevServerMiddleware: typeof communityMiddlewareFallback.createDevServerMiddleware;

View File

@@ -0,0 +1,174 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _createDevMiddlewareLogger = _interopRequireDefault(
require("../../utils/createDevMiddlewareLogger"),
);
var _isDevServerRunning = _interopRequireDefault(
require("../../utils/isDevServerRunning"),
);
var _loadMetroConfig = _interopRequireDefault(
require("../../utils/loadMetroConfig"),
);
var version = _interopRequireWildcard(require("../../utils/version"));
var _attachKeyHandlers = _interopRequireDefault(require("./attachKeyHandlers"));
var _middleware = require("./middleware");
var _devMiddleware = require("@react-native/dev-middleware");
var _metro = _interopRequireDefault(require("metro"));
var _metroCore = require("metro-core");
var _path = _interopRequireDefault(require("path"));
var _url = _interopRequireDefault(require("url"));
var _util = require("util");
function _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ("object" != typeof e && "function" != typeof e))
return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
"default" !== t &&
{}.hasOwnProperty.call(e, t) &&
((i =
(o = Object.defineProperty) &&
Object.getOwnPropertyDescriptor(e, t)) &&
(i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
async function runServer(_argv, cliConfig, args) {
const metroConfig = await (0, _loadMetroConfig.default)(cliConfig, {
config: args.config,
maxWorkers: args.maxWorkers,
port: args.port,
resetCache: args.resetCache,
watchFolders: args.watchFolders,
projectRoot: args.projectRoot,
sourceExts: args.sourceExts,
});
const hostname = args.host?.length ? args.host : "localhost";
const {
projectRoot,
server: { port },
watchFolders,
} = metroConfig;
const protocol = args.https === true ? "https" : "http";
const devServerUrl = _url.default.format({
protocol,
hostname,
port,
});
console.info(
(0, _util.styleText)(
"blue",
`\nWelcome to React Native v${cliConfig.reactNativeVersion}`,
),
);
const serverStatus = await (0, _isDevServerRunning.default)(
devServerUrl,
projectRoot,
);
if (serverStatus === "matched_server_running") {
console.info(
`A dev server is already running for this project on port ${port}. Exiting.`,
);
return;
} else if (serverStatus === "port_taken") {
console.error(
`${(0, _util.styleText)("red", "error")}: Another process is running on port ${port}. Please terminate this ` +
'process and try again, or use another port with "--port".',
);
return;
}
console.info(`Starting dev server on ${devServerUrl}\n`);
if (args.assetPlugins) {
metroConfig.transformer.assetPlugins = args.assetPlugins.map((plugin) =>
require.resolve(plugin),
);
}
if (!args.clientLogs) {
metroConfig.server.forwardClientLogs = false;
}
let reportEvent;
const terminal = new _metroCore.Terminal(process.stdout);
const ReporterImpl = getReporterImpl(args.customLogReporterPath);
const terminalReporter = new ReporterImpl(terminal);
const {
middleware: communityMiddleware,
websocketEndpoints: communityWebsocketEndpoints,
messageSocketEndpoint,
eventsSocketEndpoint,
} = (0, _middleware.createDevServerMiddleware)({
host: hostname,
port,
watchFolders,
});
const { middleware, websocketEndpoints } = (0,
_devMiddleware.createDevMiddleware)({
serverBaseUrl: devServerUrl,
logger: (0, _createDevMiddlewareLogger.default)(terminalReporter),
});
metroConfig.reporter = {
update(event) {
terminalReporter.update(event);
if (reportEvent) {
reportEvent(event);
}
if (args.interactive && event.type === "initialize_done") {
terminalReporter.update({
type: "unstable_server_log",
level: "info",
data: `Dev server ready. ${(0, _util.styleText)("dim", "Press Ctrl+C to exit.")}`,
});
(0, _attachKeyHandlers.default)({
devServerUrl,
messageSocket: messageSocketEndpoint,
reporter: terminalReporter,
});
}
},
};
await _metro.default.runServer(metroConfig, {
host: args.host,
secure: args.https,
secureCert: args.cert,
secureKey: args.key,
unstable_extraMiddleware: [communityMiddleware, middleware],
websocketEndpoints: {
...communityWebsocketEndpoints,
...websocketEndpoints,
},
});
reportEvent = eventsSocketEndpoint.reportEvent;
await version.logIfUpdateAvailable(cliConfig, terminalReporter);
}
function getReporterImpl(customLogReporterPath) {
if (customLogReporterPath == null) {
return require("metro").TerminalReporter;
}
try {
return require(customLogReporterPath);
} catch (e) {
if (e.code !== "MODULE_NOT_FOUND") {
throw e;
}
return require(_path.default.resolve(customLogReporterPath));
}
}
var _default = (exports.default = runServer);

View File

@@ -0,0 +1,38 @@
/**
* 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
*/
import type { Config } from "@react-native-community/cli-types";
export type StartCommandArgs = {
assetPlugins?: string[],
cert?: string,
customLogReporterPath?: string,
host?: string,
https?: boolean,
maxWorkers?: number,
key?: string,
platforms: string[],
port?: number,
resetCache?: boolean,
sourceExts?: string[],
transformer?: string,
watchFolders?: string[],
config?: string,
projectRoot?: string,
interactive: boolean,
clientLogs: boolean,
};
declare function runServer(
_argv: Array<string>,
cliConfig: Config,
args: StartCommandArgs,
): void;
declare export default typeof runServer;

View File

@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
Object.defineProperty(exports, "bundleCommand", {
enumerable: true,
get: function () {
return _bundle.default;
},
});
Object.defineProperty(exports, "startCommand", {
enumerable: true,
get: function () {
return _start.default;
},
});
Object.defineProperty(exports, "unstable_buildBundleWithConfig", {
enumerable: true,
get: function () {
return _buildBundle.unstable_buildBundleWithConfig;
},
});
var _bundle = _interopRequireDefault(require("./commands/bundle"));
var _start = _interopRequireDefault(require("./commands/start"));
var _buildBundle = require("./commands/bundle/buildBundle");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}

View File

@@ -0,0 +1,14 @@
/**
* 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 { default as bundleCommand } from "./commands/bundle";
export { default as startCommand } from "./commands/start";
export { unstable_buildBundleWithConfig } from "./commands/bundle/buildBundle";

View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = createDevMiddlewareLogger;
function createDevMiddlewareLogger(reporter) {
return {
info: makeLogger(reporter, "info"),
warn: makeLogger(reporter, "warn"),
error: makeLogger(reporter, "error"),
};
}
function makeLogger(reporter, level) {
return (...data) =>
reporter.update({
type: "unstable_server_log",
level,
data,
});
}

View File

@@ -0,0 +1,25 @@
/**
* 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
*/
import type { TerminalReporter } from "metro";
type LoggerFn = (...message: $ReadOnlyArray<string>) => void;
/**
* Create a dev-middleware logger object that will emit logs via Metro's
* terminal reporter.
*/
declare export default function createDevMiddlewareLogger(
reporter: TerminalReporter,
): $ReadOnly<{
info: LoggerFn,
error: LoggerFn,
warn: LoggerFn,
}>;

View File

@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.inlineString = exports.UnknownProjectError = exports.CLIError = void 0;
class CLIError extends Error {
constructor(msg, originalError) {
super(inlineString(msg));
if (originalError != null) {
this.stack =
typeof originalError === "string"
? originalError
: originalError.stack || "".split("\n").slice(0, 2).join("\n");
} else {
this.stack = "";
}
}
}
exports.CLIError = CLIError;
class UnknownProjectError extends Error {}
exports.UnknownProjectError = UnknownProjectError;
const inlineString = (str = "") => str.replace(/(\s{2,})/gm, " ").trim();
exports.inlineString = inlineString;

View File

@@ -0,0 +1,24 @@
/**
* 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
*/
/**
* A custom Error that creates a single-lined message to match current styling inside CLI.
* Uses original stack trace when `originalError` is passed or erase the stack if it's not defined.
*/
declare export class CLIError extends Error {
constructor(msg: string, originalError?: Error | string): void;
}
/**
* Raised when we're unable to find a package.json
*/
declare export class UnknownProjectError extends Error {}
declare export const inlineString: (str?: string) => string;

View File

@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = isDevServerRunning;
var _net = _interopRequireDefault(require("net"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
async function isDevServerRunning(devServerUrl, projectRoot) {
const { hostname, port } = new URL(devServerUrl);
try {
if (!(await isPortOccupied(hostname, port))) {
return "not_running";
}
const statusResponse = await fetch(`${devServerUrl}/status`);
const body = await statusResponse.text();
return body === "packager-status:running" &&
statusResponse.headers.get("X-React-Native-Project-Root") === projectRoot
? "matched_server_running"
: "port_taken";
} catch (e) {
return "unknown";
}
}
async function isPortOccupied(hostname, port) {
let result = false;
const server = _net.default.createServer();
return new Promise((resolve, reject) => {
server.once("error", (e) => {
server.close();
if (e.code === "EADDRINUSE") {
result = true;
} else {
reject(e);
}
});
server.once("listening", () => {
result = false;
server.close();
});
server.once("close", () => {
resolve(result);
});
server.listen({
host: hostname,
port,
});
});
}

View File

@@ -0,0 +1,24 @@
/**
* 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
*/
/**
* Determine whether we can run the dev server.
*
* Return values:
* - `not_running`: The port is unoccupied.
* - `matched_server_running`: The port is occupied by another instance of this
* dev server (matching the passed `projectRoot`).
* - `port_taken`: The port is occupied by another process.
* - `unknown`: An error was encountered; attempt server creation anyway.
*/
declare export default function isDevServerRunning(
devServerUrl: string,
projectRoot: string,
): Promise<"not_running" | "matched_server_running" | "port_taken" | "unknown">;

View File

@@ -0,0 +1,103 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = loadMetroConfig;
var _errors = require("./errors");
var _metroPlatformResolver = require("./metroPlatformResolver");
var _metroConfig = require("metro-config");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("ReactNative:CommunityCliPlugin");
function getCommunityCliDefaultConfig(ctx, config) {
const outOfTreePlatforms = Object.keys(ctx.platforms).filter(
(platform) => ctx.platforms[platform].npmPackageName,
);
const resolver = {
platforms: [...Object.keys(ctx.platforms), "native"],
};
if (outOfTreePlatforms.length) {
resolver.resolveRequest = (0,
_metroPlatformResolver.reactNativePlatformResolver)(
outOfTreePlatforms.reduce((result, platform) => {
result[platform] = ctx.platforms[platform].npmPackageName;
return result;
}, {}),
config.resolver?.resolveRequest,
);
}
return {
resolver,
serializer: {
getModulesRunBeforeMainModule: () => [
require.resolve(
_path.default.join(
ctx.reactNativePath,
"Libraries/Core/InitializeCore",
),
{
paths: [ctx.root],
},
),
...outOfTreePlatforms.map((platform) =>
require.resolve(
`${ctx.platforms[platform].npmPackageName}/Libraries/Core/InitializeCore`,
{
paths: [ctx.root],
},
),
),
],
},
};
}
async function loadMetroConfig(ctx, options = {}) {
let RNMetroConfig = null;
try {
RNMetroConfig = require("@react-native/metro-config");
} catch (e) {
throw new Error(
"Cannot resolve `@react-native/metro-config`. Ensure it is listed in your project's `devDependencies`.",
);
}
const defaultConfig = RNMetroConfig.getDefaultConfig(ctx.root);
global.__REACT_NATIVE_METRO_CONFIG_LOADED = false;
if (typeof RNMetroConfig.setFrameworkDefaults !== "function") {
throw new Error(
"`@react-native/metro-config` does not have the expected API. Ensure it matches your React Native version.",
);
}
RNMetroConfig.setFrameworkDefaults(
getCommunityCliDefaultConfig(ctx, defaultConfig),
);
const cwd = ctx.root;
const projectConfig = await (0, _metroConfig.resolveConfig)(
options.config,
cwd,
);
if (projectConfig.isEmpty) {
throw new _errors.CLIError(`No Metro config found in ${cwd}`);
}
debug(`Reading Metro config from ${projectConfig.filepath}`);
if (!global.__REACT_NATIVE_METRO_CONFIG_LOADED) {
for (const line of `
=================================================================================================
From React Native 0.73, your project's Metro config should extend '@react-native/metro-config'
or it will fail to build. Please copy the template at:
https://github.com/react-native-community/template/blob/main/template/metro.config.js
This warning will be removed in future (https://github.com/facebook/metro/issues/1018).
=================================================================================================
`
.trim()
.split("\n")) {
console.warn(line);
}
}
return (0, _metroConfig.loadConfig)({
cwd,
...options,
});
}

View File

@@ -0,0 +1,32 @@
/**
* 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
*/
import type { Config } from "@react-native-community/cli-types";
import type { ConfigT, YargArguments } from "metro-config";
export type { Config };
export type ConfigLoadingContext = $ReadOnly<{
root: Config["root"],
reactNativePath: Config["reactNativePath"],
platforms: Config["platforms"],
...
}>;
/**
* Load Metro config.
*
* Allows the CLI to override select values in `metro.config.js` based on
* dynamic user options in `ctx`.
*/
declare export default function loadMetroConfig(
ctx: ConfigLoadingContext,
options?: YargArguments,
): Promise<ConfigT>;

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.reactNativePlatformResolver = reactNativePlatformResolver;
function reactNativePlatformResolver(platformImplementations, customResolver) {
return (context, moduleName, platform) => {
let modifiedModuleName = moduleName;
if (platform != null && platformImplementations[platform]) {
if (moduleName === "react-native") {
modifiedModuleName = platformImplementations[platform];
} else if (moduleName.startsWith("react-native/")) {
modifiedModuleName = `${platformImplementations[platform]}/${modifiedModuleName.slice("react-native/".length)}`;
}
}
if (customResolver) {
return customResolver(context, modifiedModuleName, platform);
}
return context.resolveRequest(context, modifiedModuleName, platform);
};
}

View File

@@ -0,0 +1,32 @@
/**
* 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
*/
import type { CustomResolver } from "metro-resolver";
/**
* This is an implementation of a metro resolveRequest option which will remap react-native imports
* to different npm packages based on the platform requested. This allows a single metro instance/config
* to produce bundles for multiple out of tree platforms at a time.
*
* @param platformImplementations
* A map of platform to npm package that implements that platform
*
* Ex:
* {
* windows: 'react-native-windows'
* macos: 'react-native-macos'
* }
*/
declare export function reactNativePlatformResolver(
platformImplementations: {
[platform: string]: string,
},
customResolver: ?CustomResolver,
): CustomResolver;

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = parseKeyValueParamArray;
function parseKeyValueParamArray(keyValueArray) {
const result = {};
for (const item of keyValueArray) {
if (item.indexOf("=") === -1) {
throw new Error('Expected parameter to include "=" but found: ' + item);
}
if (item.indexOf("&") !== -1) {
throw new Error('Parameter cannot include "&" but found: ' + item);
}
const params = new URLSearchParams(item);
params.forEach((value, key) => {
result[key] = value;
});
}
return result;
}

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
declare export default function parseKeyValueParamArray(
keyValueArray: $ReadOnlyArray<string>,
): Record<string, string>;

View File

@@ -0,0 +1,111 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getLatestRelease;
exports.logIfUpdateAvailable = logIfUpdateAvailable;
var _semver = _interopRequireDefault(require("semver"));
var _util = require("util");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("ReactNative:CommunityCliPlugin");
async function logIfUpdateAvailable(cliConfig, reporter) {
const { reactNativeVersion: currentVersion } = cliConfig;
let newVersion = null;
try {
const upgrade = await getLatestRelease(currentVersion);
if (upgrade) {
newVersion = upgrade;
}
} catch (e) {
debug(
"Cannot detect current version of React Native, " +
"skipping check for a newer release",
);
debug(e);
}
if (newVersion == null) {
return;
}
if (_semver.default.gt(newVersion.stable, currentVersion)) {
reporter.update({
type: "unstable_server_log",
level: "info",
data: `React Native v${newVersion.stable} is now available (your project is running on v${currentVersion}).
Changelog: ${(0, _util.styleText)(["dim", "underline"], newVersion?.changelogUrl ?? "none")}
Diff: ${(0, _util.styleText)(["dim", "underline"], newVersion?.diffUrl ?? "none")}
`,
});
}
}
function isDiffPurgeEntry(data) {
return (
[data.name, data.zipball_url, data.tarball_url, data.node_id].filter(
(e) => typeof e !== "undefined",
).length === 0
);
}
async function getLatestRelease(currentVersion) {
debug("Checking for a newer version of React Native");
try {
debug(`Current version: ${currentVersion}`);
if (["-canary", "-nightly"].some((s) => currentVersion.includes(s))) {
return;
}
debug("Checking for newer releases on GitHub");
const latestVersion = await getLatestRnDiffPurgeVersion();
if (latestVersion == null) {
debug("Failed to get latest release");
return;
}
const { stable, candidate } = latestVersion;
debug(`Latest release: ${stable} (${candidate ?? ""})`);
if (_semver.default.compare(stable, currentVersion) >= 0) {
return {
stable,
candidate,
changelogUrl: buildChangelogUrl(stable),
diffUrl: buildDiffUrl(currentVersion, stable),
};
}
} catch (e) {
debug("Something went wrong with remote version checking, moving on");
debug(e);
}
}
function buildChangelogUrl(version) {
return `https://github.com/facebook/react-native/releases/tag/v${version}`;
}
function buildDiffUrl(oldVersion, newVersion) {
return `https://react-native-community.github.io/upgrade-helper/?from=${oldVersion}&to=${newVersion}`;
}
async function getLatestRnDiffPurgeVersion() {
const resp = await fetch(
"https://api.github.com/repos/react-native-community/rn-diff-purge/tags",
{
headers: {
"User-Agent": "@react-native/community-cli-plugin",
},
},
);
const result = {
stable: "0.0.0",
};
if (resp.status !== 200) {
return;
}
const body = (await resp.json()).filter(isDiffPurgeEntry);
for (const { name: version } of body) {
if (result.candidate != null && version.includes("-rc")) {
result.candidate = version.substring(8);
continue;
}
if (!version.includes("-rc")) {
result.stable = version.substring(8);
return result;
}
}
return result;
}

View File

@@ -0,0 +1,40 @@
/**
* 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
*/
import type { Config } from "@react-native-community/cli-types";
import type { TerminalReporter } from "metro";
type Release = {
// The current stable release
stable: string,
// The current candidate release. These are only populated if the latest release is a candidate release.
candidate?: string,
changelogUrl: string,
diffUrl: string,
};
/**
* Logs out a message if the user's version is behind a stable version of React Native
*/
declare export function logIfUpdateAvailable(
cliConfig: Config,
reporter: TerminalReporter,
): Promise<void>;
/**
* Checks via GitHub API if there is a newer stable React Native release and,
* if it exists, returns the release data.
*
* If the latest release is not newer or if it's a prerelease, the function
* will return undefined.
*/
declare export default function getLatestRelease(
currentVersion: string,
): Promise<Release | void>;

View File

@@ -0,0 +1,51 @@
{
"name": "@react-native/community-cli-plugin",
"version": "0.83.2",
"description": "Core CLI commands for React Native",
"keywords": [
"react-native",
"tools"
],
"homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/community-cli-plugin#readme",
"bugs": "https://github.com/facebook/react-native/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/facebook/react-native.git",
"directory": "packages/community-cli-plugin"
},
"license": "MIT",
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
},
"files": [
"dist"
],
"dependencies": {
"@react-native/dev-middleware": "0.83.2",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"metro": "^0.83.3",
"metro-config": "^0.83.3",
"metro-core": "^0.83.3",
"semver": "^7.1.3"
},
"devDependencies": {
"metro-resolver": "^0.83.3"
},
"peerDependencies": {
"@react-native-community/cli": "*",
"@react-native/metro-config": "*"
},
"peerDependenciesMeta": {
"@react-native-community/cli": {
"optional": true
},
"@react-native/metro-config": {
"optional": true
}
},
"engines": {
"node": ">= 20.19.4"
}
}