зеркало из https://github.com/microsoft/rnx-kit.git
feat(cli)!: introduce standalone `rnx-cli` (#3244)
This commit is contained in:
Родитель
d21adb8957
Коммит
96a34d4794
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@rnx-kit/cli": minor
|
||||
---
|
||||
|
||||
Introduce standalone `rnx-cli`. The minimum required Node version has also been bumped to 16.17.
|
|
@ -5,26 +5,33 @@
|
|||
|
||||
Command-line interface for working with packages in your repo.
|
||||
|
||||
## Bundle a Package
|
||||
> [!NOTE]
|
||||
>
|
||||
> All commands below are also a plugin to `@react-native-community/cli`, meaning
|
||||
> they will work with both `react-native` and `rnc-cli` commands. Just make sure
|
||||
> to prefix the command with `rnx-` e.g., `rnx-cli start` becomes
|
||||
> `react-native rnx-start`. The prefix is to avoid name clashes.
|
||||
|
||||
Bundle a package using [Metro](https://facebook.github.io/metro). The bundling
|
||||
process uses optional
|
||||
[configuration](https://github.com/microsoft/rnx-kit/tree/main/packages/config)
|
||||
parameters and command-line overrides.
|
||||
## `rnx-cli bundle`
|
||||
|
||||
The command `react-native rnx-bundle` is meant to be a drop-in replacement for
|
||||
`react-native bundle`. If `rnx-bundle` ever becomes widely accepted, we will
|
||||
work on upstreaming it to `@react-native-community/cli`, along with supporting
|
||||
libraries for package configuration and Metro plugins.
|
||||
Bundle a package using [Metro][]. The bundling process uses optional
|
||||
[configuration][] parameters and command-line overrides.
|
||||
|
||||
### Example Commands
|
||||
> [!NOTE]
|
||||
>
|
||||
> This command is meant to be a drop-in replacement for `react-native bundle`.
|
||||
> If `rnx-bundle` ever becomes widely accepted, we will work on upstreaming it
|
||||
> to `@react-native-community/cli`, along with supporting libraries for package
|
||||
> configuration and Metro plugins.
|
||||
|
||||
### Example Usages
|
||||
|
||||
```sh
|
||||
yarn react-native rnx-bundle
|
||||
yarn rnx-cli bundle
|
||||
```
|
||||
|
||||
```sh
|
||||
yarn react-native rnx-bundle \
|
||||
yarn rnx-cli bundle \
|
||||
--entry-file src/index.ts \
|
||||
--bundle-output main.jsbundle \
|
||||
--platform ios \
|
||||
|
@ -48,7 +55,7 @@ yarn react-native rnx-bundle \
|
|||
],
|
||||
"@rnx-kit/metro-plugin-typescript"
|
||||
],
|
||||
"targets": ["ios", "android", "windows", "macos"],
|
||||
"targets": ["android", "ios", "macos", "windows"],
|
||||
"platforms": {
|
||||
"android": {
|
||||
"assetsDest": "dist/res"
|
||||
|
@ -86,44 +93,94 @@ dependencies.
|
|||
|
||||
### Command-Line Overrides
|
||||
|
||||
| Override | Description |
|
||||
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| --id [id] | Target bundle definition. This is only needed when the rnx-kit configuration has multiple bundle definitions. |
|
||||
| --entry-file [file] | Path to the root JavaScript or TypeScript file, either absolute or relative to the package. |
|
||||
| --platform [`ios` \| `android` \| `windows` \| `win32` \| `macos`] | Target platform. When not given, all platforms in the rnx-kit configuration are bundled. |
|
||||
| --dev [boolean] | If false, warnings are disabled and the bundle is minified (default: true). |
|
||||
| --minify [boolean] | Controls whether or not the bundle is minified. Disabling minification is useful for test builds. |
|
||||
| --bundle-output [path] | Path to the output bundle file, either absolute or relative to the package. |
|
||||
| --bundle-encoding [`utf8` \| `utf16le` \| `ascii`] | [Character encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to use when writing the bundle file. |
|
||||
| --max-workers [number] | Specifies the maximum number of parallel worker threads to use for transforming files. This defaults to the number of cores available on your machine. |
|
||||
| --sourcemap-output [string] | Path where the bundle source map is written, either absolute or relative to the package. |
|
||||
| --sourcemap-sources-root [string] | Path to use when relativizing file entries in the bundle source map. |
|
||||
| --assets-dest [path] | Path where bundle assets like images are written, either absolute or relative to the package. If not given, assets are ignored. |
|
||||
| --tree-shake [boolean] | Enable tree shaking to remove unused code and reduce the bundle size. |
|
||||
| --reset-cache | Reset the Metro cache. |
|
||||
| --config [string] | Path to the Metro configuration file. |
|
||||
| -h, --help | Show usage information. |
|
||||
<!-- @rnx-kit/cli/bundle start -->
|
||||
|
||||
## Start a Bundle Server
|
||||
| Option | Description |
|
||||
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| --id <id> | Target bundle definition; only needed when the rnx-kit configuration has multiple bundle definitions |
|
||||
| --entry-file <path> | Path to the root JavaScript or TypeScript file, either absolute or relative to the package |
|
||||
| --platform <ios|android|windows|win32|macos> | Target platform; when unspecified, all platforms in the rnx-kit configuration are bundled |
|
||||
| --dev [boolean] | If false, warnings are disabled and the bundle is minified |
|
||||
| --minify [boolean] | Controls whether or not the bundle is minified (useful for test builds) |
|
||||
| --bundle-output <string> | Path to the output bundle file, either absolute or relative to the package |
|
||||
| --bundle-encoding <utf8|utf16le|ascii> | Character encoding to use when writing the bundle file |
|
||||
| --max-workers <number> | Specifies the maximum number of parallel worker threads to use for transforming files; defaults to the number of cores available on your machine |
|
||||
| --sourcemap-output <string> | Path where the bundle source map is written, either absolute or relative to the package |
|
||||
| --sourcemap-sources-root <string> | Path to use when relativizing file entries in the bundle source map |
|
||||
| --sourcemap-use-absolute-path | Report SourceMapURL using its full path |
|
||||
| --assets-dest <path> | Path where bundle assets like images are written, either absolute or relative to the package; if unspecified, assets are ignored |
|
||||
| --unstable-transform-profile <string> | [Experimental] Transform JS for a specific JS engine; currently supported: hermes, hermes-canary, default |
|
||||
| --reset-cache | Reset the Metro cache |
|
||||
| --config <string> | Path to the Metro configuration file |
|
||||
| --tree-shake [boolean] | Enable tree shaking to remove unused code and reduce the bundle size |
|
||||
|
||||
Start a bundle server for a package using
|
||||
[Metro](https://facebook.github.io/metro). The bundle server uses optional
|
||||
[configuration](https://github.com/microsoft/rnx-kit/tree/main/packages/config)
|
||||
parameters and command-line overrides.
|
||||
<!-- @rnx-kit/cli/bundle end -->
|
||||
|
||||
The command `react-native rnx-start` is meant to be a drop-in replacement for
|
||||
`react-native start`. If `rnx-start` ever becomes widely accepted, we will work
|
||||
on upstreaming it to `@react-native-community/cli`, along with supporting
|
||||
libraries for package configuration and Metro plugins.
|
||||
## `rnx-cli config`
|
||||
|
||||
Routes to
|
||||
[`react-native config`](https://github.com/react-native-community/cli/blob/main/packages/cli-config#readme).
|
||||
|
||||
## `rnx-cli doctor`
|
||||
|
||||
Routes to
|
||||
[`react-native doctor`](https://github.com/react-native-community/cli/blob/main/packages/cli-doctor#readme).
|
||||
|
||||
## `rnx-cli info`
|
||||
|
||||
Routes to
|
||||
[`react-native info`](https://github.com/react-native-community/cli/blob/main/packages/cli-doctor#info).
|
||||
|
||||
## `rnx-cli build-android`
|
||||
|
||||
Routes to
|
||||
[`react-native build-android`](https://github.com/react-native-community/cli/blob/main/packages/cli-platform-android#build-android).
|
||||
|
||||
## `rnx-cli build-ios`
|
||||
|
||||
Routes to
|
||||
[`react-native build-ios`](https://github.com/react-native-community/cli/blob/main/packages/cli-platform-ios#build-ios).
|
||||
|
||||
## `rnx-cli log-android`
|
||||
|
||||
Routes to
|
||||
[`react-native log-android`](https://github.com/react-native-community/cli/blob/main/packages/cli-platform-android#log-android).
|
||||
|
||||
## `rnx-cli log-ios`
|
||||
|
||||
Routes to
|
||||
[`react-native log-ios`](https://github.com/react-native-community/cli/blob/main/packages/cli-platform-ios#log-ios).
|
||||
|
||||
## `rnx-cli run-android`
|
||||
|
||||
Routes to
|
||||
[`react-native run-android`](https://github.com/react-native-community/cli/blob/main/packages/cli-platform-android#run-android).
|
||||
|
||||
## `rnx-cli run-ios`
|
||||
|
||||
Routes to
|
||||
[`react-native run-ios`](https://github.com/react-native-community/cli/blob/main/packages/cli-platform-ios#run-ios).
|
||||
|
||||
## `rnx-cli start`
|
||||
|
||||
Start a bundle server for a package using [Metro][]. The bundle server uses
|
||||
optional [configuration][] parameters and command-line overrides.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This command is meant to be a drop-in replacement for `react-native start`. If
|
||||
> `rnx-start` ever becomes widely accepted, we will work on upstreaming it to
|
||||
> `@react-native-community/cli`, along with supporting libraries for package
|
||||
> configuration and Metro plugins.
|
||||
|
||||
### Example Commands
|
||||
|
||||
```sh
|
||||
yarn react-native rnx-start
|
||||
yarn rnx-cli start
|
||||
```
|
||||
|
||||
```sh
|
||||
yarn react-native rnx-start --host localhost --port 8812
|
||||
yarn rnx-cli start --host 127.0.0.1 --port 8812
|
||||
```
|
||||
|
||||
### Example Configuration
|
||||
|
@ -156,74 +213,91 @@ from the bundle configuration (or its [defaults](#bundle-defaults)).
|
|||
|
||||
### Command-Line Overrides
|
||||
|
||||
| Override | Description |
|
||||
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| --port [number] | Host port to use when listening for incoming server requests. |
|
||||
| --host [string] | Host name or address to bind when listening for incoming server requests. When not given, requests from all addresses are accepted. |
|
||||
| --projectRoot [path] | Path to the root of your react-native project. The bundle server uses this root path to resolve all web requests. |
|
||||
| --watchFolders [paths] | Additional folders which will be added to the file-watch list. Comma-separated. By default, Metro watches all project files. |
|
||||
| --assetPlugins [list] | Additional asset plugins to be used by the Metro Babel transformer. Comma-separated list containing plugin module names or absolute paths to plugin packages. |
|
||||
| --sourceExts [list] | Additional source-file extensions to include when generating bundles. Comma-separated list, excluding the leading dot. |
|
||||
| --max-workers [number] | Specifies the maximum number of parallel worker threads to use for transforming files. This defaults to the number of cores available on your machine. |
|
||||
| --reset-cache | Reset the Metro cache. |
|
||||
| --custom-log-reporter-path [string] | Path to a JavaScript file which exports a Metro `TerminalReporter` function. This replaces the default reporter, which writes all messages to the Metro console. |
|
||||
| --https | Use a secure (https) web server. When not specified, an insecure (http) web server is used. |
|
||||
| --key [path] | Path to a custom SSL private key file to use for secure (https) communication. |
|
||||
| --cert [path] | Path to a custom SSL certificate file to use for secure (https) communication. |
|
||||
| --config [string] | Path to the Metro configuration file. |
|
||||
| --no-interactive | Disables interactive mode. |
|
||||
| --id | Specify which bundle configuration to use if server configuration is missing. |
|
||||
<!-- @rnx-kit/cli/start start -->
|
||||
|
||||
## Manage Dependencies
|
||||
| Option | Description |
|
||||
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| --port <number> | Host port to use when listening for incoming server requests |
|
||||
| --host <string> | Host name or address to bind when listening for incoming server requests; when not specified, requests from all addresses are accepted |
|
||||
| --project-root <path> | Path to the root of your react-native project; the bundle server uses this path to resolve all web requests |
|
||||
| --watch-folders <paths> | Additional folders which will be added to the watched files list, comma-separated; by default, Metro watches all project files |
|
||||
| --asset-plugins <list> | Additional asset plugins to be used by Metro's Babel transformer; comma-separated list containing plugin module names or absolute paths to plugin packages |
|
||||
| --source-exts <list> | Additional source file extensions to include when generating bundles; comma-separated list, excluding the leading dot |
|
||||
| --max-workers <number> | Specifies the maximum number of parallel worker threads to use for transforming files; defaults to the number of cores available on your machine |
|
||||
| --reset-cache | Reset the Metro cache |
|
||||
| --custom-log-reporter-path <string> | Path to a JavaScript file which exports a Metro 'TerminalReporter' function; replaces the default reporter that writes all messages to the Metro console |
|
||||
| --https | Use a secure (https) web server; when not specified, an insecure (http) web server is used |
|
||||
| --key <path> | Path to a custom SSL private key file to use for secure (https) communication |
|
||||
| --cert <path> | Path to a custom SSL certificate file to use for secure (https) communication |
|
||||
| --config <string> | Path to the Metro configuration file |
|
||||
| --no-interactive | Disables interactive mode |
|
||||
| --id <string> | Specify which bundle configuration to use if server configuration is missing |
|
||||
|
||||
<!-- @rnx-kit/cli/start end -->
|
||||
|
||||
## `rnx-cli align-deps`
|
||||
|
||||
Manage dependencies within a repository and across many repositories.
|
||||
|
||||
```
|
||||
$ yarn react-native rnx-align-deps [options] [/path/to/package.json]
|
||||
```sh
|
||||
yarn rnx-cli align-deps [options] [/path/to/package.json]
|
||||
```
|
||||
|
||||
Refer to
|
||||
[@rnx-kit/align-deps](https://github.com/microsoft/rnx-kit/tree/main/packages/align-deps)
|
||||
for details.
|
||||
Refer to [@rnx-kit/align-deps][] for details.
|
||||
|
||||
## Generate a Third-Party Notice for a Package
|
||||
|
||||
Generate a 3rd-party notice, which is an aggregation of all the LICENSE files
|
||||
from your package's dependencies.
|
||||
|
||||
> NOTE: A 3rd-party notice is a **legal document**. You are solely responsble
|
||||
> for its content, even if you use this command to assist you in generating it.
|
||||
> You should consult with an attorney to ensure your notice meets all legal
|
||||
> requirements.
|
||||
|
||||
```
|
||||
$ yarn react-native rnx-write-third-party-notices [options]
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| --root-path [path] | The root of the repo. This is the starting point for finding each module in the source map dependency graph. |
|
||||
| --source-map-file [file] | The source map file associated with the package's entry file. This source map eventually leads to all package dependencies and their licenses. |
|
||||
| --json | Format the 3rd-party notice file as JSON instead of text. |
|
||||
| --output-file [file] | The path to use when writing the 3rd-party notice file. |
|
||||
| --ignore-scopes [string] | Comma-separated list of `npm` scopes to ignore when traversing the source map dependency graph. |
|
||||
| --ignore-modules [string] | Comma-separated list of modules to ignore when traversing the source map dependency graph. |
|
||||
| --preamble-text [string] | A string to prepend to the start of the 3rd-party notice. |
|
||||
| --additional-text [path] | A string to append to the end of the 3rd-party notice. |
|
||||
|
||||
## Clean a React Native Project
|
||||
|
||||
> Deprecated: This command was upstreamed to `@react-native-community/cli`. As
|
||||
> of [v8.0](https://github.com/react-native-community/cli/releases/tag/v8.0.0),
|
||||
> you can use `react-native clean` instead.
|
||||
## `rnx-cli clean`
|
||||
|
||||
Cleans your project by removing React Native related caches and modules.
|
||||
|
||||
```
|
||||
$ yarn react-native rnx-clean [options]
|
||||
```sh
|
||||
yarn rnx-cli clean [options]
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| --include [string] | Comma-separated flag of caches to clear e.g npm,yarn . When not specified , only non-platform specific caches are cleared. [android,cocoapods,npm,metro,watchman,yarn] |
|
||||
| --project-root [path] | Root path to your React Native project. When not specified, defaults to current working directory |
|
||||
<!-- @rnx-kit/cli/clean start -->
|
||||
|
||||
| Option | Description |
|
||||
| ----------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| --include <android,cocoapods,metro,npm,watchman,yarn> | Comma-separated flag of caches to clear e.g., `npm,yarn` |
|
||||
| --project-root <path> | Root path to your React Native project |
|
||||
| --verify-cache | Whether to verify the integrity of the cache |
|
||||
|
||||
<!-- @rnx-kit/cli/clean end -->
|
||||
|
||||
## `rnx-cli write-third-party-notices`
|
||||
|
||||
Generate a third-party notice, an aggregation of all the LICENSE files from your
|
||||
package's dependencies.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> A third-party notice is a **legal document**. You are solely responsble for
|
||||
> its content, even if you use this command to assist you in generating it. You
|
||||
> should consult with an attorney to ensure your notice meets all legal
|
||||
> requirements.
|
||||
|
||||
```sh
|
||||
yarn rnx-cli write-third-party-notices [options]
|
||||
```
|
||||
|
||||
<!-- @rnx-kit/cli/write-third-party-notices start -->
|
||||
|
||||
| Option | Description |
|
||||
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| --root-path <path> | The root of the repo — the starting point for finding each module in the source map dependency graph |
|
||||
| --source-map-file <file> | The source map file associated with the package's entry file — this source map eventually leads to all package dependencies and their licenses |
|
||||
| --json | Format the 3rd-party notice file as JSON instead of text |
|
||||
| --output-file <file> | The path to use when writing the 3rd-party notice file |
|
||||
| --ignore-scopes <string> | Comma-separated list of npm scopes to ignore when traversing the source map dependency graph |
|
||||
| --ignore-modules <string> | Comma-separated list of modules to ignore when traversing the source map dependency graph |
|
||||
| --preamble-text <string> | A string to prepend to the start of the 3rd-party notice |
|
||||
| --additional-text <string> | A string to append to the end of the 3rd-party notice |
|
||||
|
||||
<!-- @rnx-kit/cli/write-third-party-notices end -->
|
||||
|
||||
<!-- References -->
|
||||
|
||||
[@rnx-kit/align-deps]:
|
||||
https://github.com/microsoft/rnx-kit/tree/main/packages/align-deps#readme
|
||||
[Metro]: https://facebook.github.io/metro
|
||||
[configuration]:
|
||||
https://github.com/microsoft/rnx-kit/tree/main/packages/config#readme
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env node
|
||||
require("../lib/bin/rnx-cli.js").main();
|
|
@ -9,11 +9,15 @@
|
|||
"email": "microsoftopensource@users.noreply.github.com"
|
||||
},
|
||||
"files": [
|
||||
"bin/rnx-cli.mjs",
|
||||
"lib/**/*.d.ts",
|
||||
"lib/**/*.js",
|
||||
"react-native.config.js"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"rnx-cli": "bin/rnx-cli.cjs"
|
||||
},
|
||||
"types": "lib/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
|
@ -49,11 +53,9 @@
|
|||
"@rnx-kit/tools-language": "^2.0.0",
|
||||
"@rnx-kit/tools-node": "^2.1.1",
|
||||
"@rnx-kit/tools-react-native": "^1.4.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"commander": "^11.1.0",
|
||||
"ora": "^5.4.1",
|
||||
"qrcode": "^1.5.0",
|
||||
"readline": "^1.3.0"
|
||||
"qrcode": "^1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jest": ">=26.0",
|
||||
|
@ -74,30 +76,31 @@
|
|||
"@rnx-kit/eslint-config": "*",
|
||||
"@rnx-kit/jest-preset": "*",
|
||||
"@rnx-kit/scripts": "*",
|
||||
"@rnx-kit/tools-filesystem": "*",
|
||||
"@rnx-kit/tsconfig": "*",
|
||||
"@types/connect": "^3.4.36",
|
||||
"@types/fs-extra": "^9.0.0",
|
||||
"@types/jest": "^29.2.1",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/node-fetch": "^2.6.5",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"eslint": "^8.56.0",
|
||||
"jest": "^29.2.1",
|
||||
"memfs": "^4.0.0",
|
||||
"markdown-table": "^3.0.0",
|
||||
"metro": "^0.80.3",
|
||||
"metro-babel-transformer": "^0.80.0",
|
||||
"metro-config": "^0.80.3",
|
||||
"prettier": "^3.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-native": "^0.74.0",
|
||||
"tsx": "^4.15.0",
|
||||
"type-fest": "^4.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17"
|
||||
},
|
||||
"depcheck": {
|
||||
"ignoreMatches": [
|
||||
"connect",
|
||||
"jest-cli",
|
||||
"readline"
|
||||
"connect"
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env node --import tsx
|
||||
|
||||
import { markdownTable } from "markdown-table";
|
||||
import * as fs from "node:fs";
|
||||
import { reactNativeConfig } from "../src/index.js";
|
||||
|
||||
const README = "README.md";
|
||||
const UTF8 = { encoding: "utf-8" as const };
|
||||
|
||||
const readme = fs.readFileSync(README, UTF8);
|
||||
const updatedReadme = reactNativeConfig.commands.reduce(
|
||||
(readme, { name, options }) => {
|
||||
if (!options) {
|
||||
return readme;
|
||||
}
|
||||
|
||||
const table = markdownTable([
|
||||
["Option", "Description"],
|
||||
...options.map(({ name, description }) => {
|
||||
const flag = name
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll("|", "|");
|
||||
return [flag, description];
|
||||
}),
|
||||
]);
|
||||
|
||||
const id = name.replace("rnx-", "@rnx-kit/cli/");
|
||||
const tokenStart = `<!-- ${id} start -->`;
|
||||
const tokenEnd = `<!-- ${id} end -->`;
|
||||
return readme.replace(
|
||||
new RegExp(`${tokenStart}([^]+)${tokenEnd}`),
|
||||
`${tokenStart}\n\n${table}\n\n${tokenEnd}`
|
||||
);
|
||||
},
|
||||
readme
|
||||
);
|
||||
|
||||
if (updatedReadme !== readme) {
|
||||
fs.writeFileSync(README, updatedReadme);
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import type {
|
||||
Command as BaseCommand,
|
||||
Config as BaseConfig,
|
||||
} from "@react-native-community/cli-types";
|
||||
import {
|
||||
findPackageDependencyDir,
|
||||
readPackage,
|
||||
} from "@rnx-kit/tools-node/package";
|
||||
import {
|
||||
getCurrentState,
|
||||
getSavedState,
|
||||
saveConfigToCache,
|
||||
} from "@rnx-kit/tools-react-native/cache";
|
||||
import { resolveCommunityCLI } from "@rnx-kit/tools-react-native/context";
|
||||
import { reactNativeConfig } from "../index";
|
||||
|
||||
type Command = BaseCommand<false> | BaseCommand<true>;
|
||||
type Commands = Record<string, Command>;
|
||||
type Config = BaseConfig & { __rnxFastPath?: true; commands: Command[] };
|
||||
|
||||
const RNX_PREFIX = "rnx-";
|
||||
|
||||
function findReactNativePath(root: string, resolveSymlinks = false) {
|
||||
const dir = findPackageDependencyDir("react-native", {
|
||||
startDir: root,
|
||||
resolveSymlinks,
|
||||
});
|
||||
if (!dir) {
|
||||
throw new Error("Unable to resolve module 'react-native'");
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
export function getCoreCommands() {
|
||||
const start = RNX_PREFIX.length;
|
||||
return reactNativeConfig.commands.map((command) => ({
|
||||
...command,
|
||||
name: command.name.substring(start),
|
||||
}));
|
||||
}
|
||||
|
||||
export function uniquify(commands: Command[]): Command[] {
|
||||
const uniqueCommands: Commands = {};
|
||||
for (const command of commands) {
|
||||
const { name } = command;
|
||||
if (name.startsWith(RNX_PREFIX)) {
|
||||
command.name = name.substring(RNX_PREFIX.length);
|
||||
uniqueCommands[command.name] = command;
|
||||
} else if (!uniqueCommands[name]) {
|
||||
uniqueCommands[name] = command;
|
||||
}
|
||||
}
|
||||
return Object.values(uniqueCommands);
|
||||
}
|
||||
|
||||
export function loadContext(userCommand: string, root = process.cwd()): Config {
|
||||
// The fast path avoids traversing project dependencies because we know what
|
||||
// information our commands depend on.
|
||||
const coreCommands = getCoreCommands();
|
||||
const useFastPath = coreCommands.some(({ name }) => name === userCommand);
|
||||
if (useFastPath) {
|
||||
let reactNativePath: string;
|
||||
let reactNativeVersion: string;
|
||||
return {
|
||||
__rnxFastPath: true,
|
||||
root,
|
||||
get reactNativePath() {
|
||||
if (!reactNativePath) {
|
||||
reactNativePath = findReactNativePath(root);
|
||||
}
|
||||
return reactNativePath;
|
||||
},
|
||||
get reactNativeVersion() {
|
||||
if (!reactNativeVersion) {
|
||||
const reactNativePath = findReactNativePath(root);
|
||||
const { version } = readPackage(reactNativePath);
|
||||
reactNativeVersion = version;
|
||||
}
|
||||
return reactNativeVersion;
|
||||
},
|
||||
get dependencies(): Config["dependencies"] {
|
||||
throw new Error("Unexpected access to `dependencies`");
|
||||
},
|
||||
commands: coreCommands,
|
||||
get healthChecks(): Config["healthChecks"] {
|
||||
throw new Error("Unexpected access to `healthChecks`");
|
||||
},
|
||||
get platforms(): Config["platforms"] {
|
||||
throw new Error("Unexpected access to `platforms`");
|
||||
},
|
||||
get project(): Config["project"] {
|
||||
throw new Error("Unexpected access to `project`");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const rncli = resolveCommunityCLI(root);
|
||||
const { loadConfig } = require(rncli);
|
||||
const config =
|
||||
loadConfig.length === 1
|
||||
? loadConfig({ projectRoot: root })
|
||||
: loadConfig(root);
|
||||
|
||||
// We will always load from disk because functions cannot be serialized.
|
||||
// However, we should refresh the cache if needed.
|
||||
const state = getCurrentState(root);
|
||||
if (state !== getSavedState(root)) {
|
||||
saveConfigToCache(root, state, config);
|
||||
}
|
||||
|
||||
// Before returning the config, remove the `rnx-` prefix from our commands,
|
||||
// and ensure commands are unique.
|
||||
config.commands = uniquify(config.commands);
|
||||
|
||||
return config;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import type { Command, Config } from "@react-native-community/cli-types";
|
||||
import { resolveCommunityCLI } from "@rnx-kit/tools-react-native/context";
|
||||
|
||||
function tryImport(module: string, fromDir: string) {
|
||||
try {
|
||||
const p = require.resolve(module, { paths: [fromDir] });
|
||||
return require(p);
|
||||
} catch (_) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function findExternalCommands(config: Config): Command[] {
|
||||
if ("__rnxFastPath" in config) {
|
||||
// Fast path means we don't need to do anything here
|
||||
return [];
|
||||
}
|
||||
|
||||
const externalCommands: Command[] = [
|
||||
{
|
||||
name: "config",
|
||||
description:
|
||||
"Prints the configuration for the project and its dependencies in JSON format; used by autolinking",
|
||||
func: () => console.log(JSON.stringify(config, undefined, 2)),
|
||||
},
|
||||
];
|
||||
|
||||
const rncli = resolveCommunityCLI(config.root);
|
||||
|
||||
const cliDoctor = tryImport("@react-native-community/cli-doctor", rncli);
|
||||
if (cliDoctor?.commands) {
|
||||
const commands = Object.values(cliDoctor.commands) as Command[];
|
||||
externalCommands.push(...commands);
|
||||
}
|
||||
|
||||
return externalCommands;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { Command } from "commander";
|
||||
import * as path from "node:path";
|
||||
import { loadContext } from "./context";
|
||||
import { findExternalCommands } from "./externalCommands";
|
||||
|
||||
export function main() {
|
||||
const [, , userCommand] = process.argv;
|
||||
|
||||
const context = loadContext(userCommand);
|
||||
const allCommands = context.commands.concat(findExternalCommands(context));
|
||||
const program = new Command(path.basename(__filename, ".js"));
|
||||
|
||||
for (const {
|
||||
name,
|
||||
description,
|
||||
detached,
|
||||
options = [],
|
||||
func,
|
||||
} of allCommands) {
|
||||
const command = program.command(name).description(description ?? name);
|
||||
|
||||
if (detached) {
|
||||
command.action((args, command) => func(command.args, args, context));
|
||||
} else {
|
||||
command.action((args, command) => func(command.args, context, args));
|
||||
}
|
||||
|
||||
for (const { name, description, parse, default: def } of options) {
|
||||
if (parse) {
|
||||
command.option(name, description ?? name, (input) => parse(input), def);
|
||||
} else {
|
||||
command.option(name, description, def?.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
program.parse();
|
||||
}
|
|
@ -3,10 +3,10 @@ import type { HermesOptions } from "@rnx-kit/config";
|
|||
import { error, info } from "@rnx-kit/console";
|
||||
import { findPackageDependencyDir } from "@rnx-kit/tools-node/package";
|
||||
import { requireModuleFromMetro } from "@rnx-kit/tools-react-native/metro";
|
||||
import { spawnSync } from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
|
||||
function hermesBinaryInDir(hermesc: string): string | null {
|
||||
switch (os.platform()) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { info } from "@rnx-kit/console";
|
|||
import type { BundleArgs as MetroBundleArgs } from "@rnx-kit/metro-service";
|
||||
import { bundle } from "@rnx-kit/metro-service";
|
||||
import type { ConfigT } from "metro-config";
|
||||
import * as path from "path";
|
||||
import * as path from "node:path";
|
||||
import { ensureDir } from "../helpers/filesystem";
|
||||
import { customizeMetroConfig } from "../helpers/metro-config";
|
||||
import type { CliPlatformBundleConfig } from "./types";
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import type { Config as CLIConfig } from "@react-native-community/cli-types";
|
||||
import { spawn } from "child_process";
|
||||
import { existsSync as fileExists } from "fs";
|
||||
import * as fs from "fs/promises";
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync as fileExists } from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import ora from "ora";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { asResolvedPath } from "./helpers/parsers";
|
||||
|
||||
type Args = {
|
||||
include?: string;
|
||||
projectRoot?: string;
|
||||
verify?: boolean;
|
||||
verifyCache?: boolean;
|
||||
};
|
||||
|
||||
type Task = {
|
||||
|
@ -101,7 +101,7 @@ export async function rnxClean(
|
|||
label: "Remove node_modules",
|
||||
action: () => cleanDir(`${root}/node_modules`),
|
||||
},
|
||||
...(cliOptions.verify
|
||||
...(cliOptions.verifyCache
|
||||
? [
|
||||
{
|
||||
label: "Verify npm cache",
|
||||
|
@ -191,7 +191,7 @@ export const rnxCleanCommand = {
|
|||
parse: asResolvedPath,
|
||||
},
|
||||
{
|
||||
name: "--verify",
|
||||
name: "--verify-cache",
|
||||
description: "Whether to verify the integrity of the cache",
|
||||
default: false,
|
||||
},
|
||||
|
|
|
@ -4,7 +4,6 @@ import { keysOf } from "@rnx-kit/tools-language/properties";
|
|||
import type { PackageManifest } from "@rnx-kit/tools-node/package";
|
||||
import {
|
||||
findPackageDependencyDir,
|
||||
findPackageDir,
|
||||
readPackage,
|
||||
} from "@rnx-kit/tools-node/package";
|
||||
import { findUp } from "@rnx-kit/tools-node/path";
|
||||
|
@ -12,9 +11,11 @@ import type { AllPlatforms } from "@rnx-kit/tools-react-native";
|
|||
import { parsePlatform } from "@rnx-kit/tools-react-native";
|
||||
import type { SpawnSyncOptions } from "child_process";
|
||||
import { spawnSync } from "child_process";
|
||||
import * as fs from "fs-extra";
|
||||
import * as fs from "fs";
|
||||
import * as nodefs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { ensureDir } from "./helpers/filesystem";
|
||||
|
||||
export type AndroidArchive = {
|
||||
targetName?: string;
|
||||
|
@ -53,6 +54,7 @@ export type Context = {
|
|||
projectRoot: string;
|
||||
manifest: PackageManifest;
|
||||
options: Options;
|
||||
reactNativePath: string;
|
||||
};
|
||||
|
||||
export type AssetsConfig = {
|
||||
|
@ -69,6 +71,14 @@ const defaultAndroidConfig: Required<Required<AndroidArchive>["android"]> = {
|
|||
kotlinVersion: "1.7.22",
|
||||
};
|
||||
|
||||
function cloneFile(src: string, dest: string) {
|
||||
return fs.promises.copyFile(src, dest, fs.constants.COPYFILE_FICLONE);
|
||||
}
|
||||
|
||||
function cp_r(source: string, destination: string, fs = nodefs) {
|
||||
return fs.promises.cp(source, destination, { recursive: true });
|
||||
}
|
||||
|
||||
function ensureOption(options: Options, opt: string, flag = opt) {
|
||||
if (options[opt] == null) {
|
||||
error(`Missing required option: --${flag}`);
|
||||
|
@ -215,7 +225,7 @@ export async function assembleAarBundle(
|
|||
};
|
||||
|
||||
const outputDir = path.join(context.options.assetsDest, "aar");
|
||||
fs.ensureDirSync(outputDir);
|
||||
ensureDir(outputDir);
|
||||
|
||||
const dest = path.join(outputDir, `${targetName}-${version}.aar`);
|
||||
|
||||
|
@ -245,11 +255,6 @@ export async function assembleAarBundle(
|
|||
// Run only one Gradle task at a time
|
||||
run(gradlew, targets, { cwd: androidProject, stdio: "inherit", env });
|
||||
} else {
|
||||
const reactNativePath = findPackageDependencyDir("react-native");
|
||||
if (!reactNativePath) {
|
||||
throw new Error("Could not find 'react-native'");
|
||||
}
|
||||
|
||||
const buildDir = path.join(
|
||||
process.cwd(),
|
||||
"node_modules",
|
||||
|
@ -272,7 +277,7 @@ export async function assembleAarBundle(
|
|||
android?.kotlinVersion ?? defaultAndroidConfig.kotlinVersion;
|
||||
const buildRelativeReactNativePath = path.relative(
|
||||
buildDir,
|
||||
reactNativePath
|
||||
context.reactNativePath
|
||||
);
|
||||
|
||||
const buildGradle = [
|
||||
|
@ -319,7 +324,7 @@ export async function assembleAarBundle(
|
|||
"",
|
||||
].join("\n");
|
||||
|
||||
fs.ensureDirSync(buildDir);
|
||||
ensureDir(buildDir);
|
||||
fs.writeFileSync(path.join(buildDir, "build.gradle"), buildGradle);
|
||||
fs.writeFileSync(
|
||||
path.join(buildDir, "gradle.properties"),
|
||||
|
@ -331,32 +336,37 @@ export async function assembleAarBundle(
|
|||
run(gradlew, targets, { cwd: buildDir, stdio: "inherit", env });
|
||||
}
|
||||
|
||||
await Promise.all(targetsToCopy.map(([src, dest]) => fs.copy(src, dest)));
|
||||
await Promise.all(targetsToCopy.map(([src, dest]) => cloneFile(src, dest)));
|
||||
}
|
||||
|
||||
function copyFiles(files: unknown, destination: string): Promise<void>[] {
|
||||
function copyFiles(
|
||||
files: unknown,
|
||||
destination: string,
|
||||
fs = nodefs
|
||||
): Promise<void>[] {
|
||||
if (!Array.isArray(files) || files.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
fs.ensureDirSync(destination);
|
||||
ensureDir(destination, fs);
|
||||
return files.map((file) => {
|
||||
return fs.copy(file, `${destination}/${path.basename(file)}`);
|
||||
return cp_r(file, `${destination}/${path.basename(file)}`, fs);
|
||||
});
|
||||
}
|
||||
|
||||
export async function copyAssets(
|
||||
{ options: { assetsDest, xcassetsDest } }: Context,
|
||||
packageName: string,
|
||||
{ assets, strings, xcassets }: NativeAssets
|
||||
{ assets, strings, xcassets }: NativeAssets,
|
||||
fs = nodefs
|
||||
): Promise<void> {
|
||||
const tasks = [
|
||||
...copyFiles(assets, `${assetsDest}/assets/${packageName}`),
|
||||
...copyFiles(strings, `${assetsDest}/strings/${packageName}`),
|
||||
...copyFiles(assets, `${assetsDest}/assets/${packageName}`, fs),
|
||||
...copyFiles(strings, `${assetsDest}/strings/${packageName}`, fs),
|
||||
];
|
||||
|
||||
if (typeof xcassetsDest === "string") {
|
||||
tasks.push(...copyFiles(xcassets, xcassetsDest));
|
||||
tasks.push(...copyFiles(xcassets, xcassetsDest, fs));
|
||||
}
|
||||
|
||||
await Promise.all(tasks);
|
||||
|
@ -460,10 +470,12 @@ export async function gatherConfigs({
|
|||
*
|
||||
* @param options Options dictate what gets copied where
|
||||
*/
|
||||
export async function copyProjectAssets(options: Options): Promise<void> {
|
||||
const projectRoot = findPackageDir() || process.cwd();
|
||||
export async function copyProjectAssets(
|
||||
options: Options,
|
||||
{ root: projectRoot, reactNativePath }: CLIConfig
|
||||
): Promise<void> {
|
||||
const manifest = readPackage(projectRoot);
|
||||
const context = { projectRoot, manifest, options };
|
||||
const context = { projectRoot, manifest, options, reactNativePath };
|
||||
const assetConfigs = await gatherConfigs(context);
|
||||
if (!assetConfigs) {
|
||||
return;
|
||||
|
@ -505,7 +517,7 @@ export async function copyProjectAssets(options: Options): Promise<void> {
|
|||
(!fs.existsSync(destination) || fs.statSync(destination).isDirectory())
|
||||
) {
|
||||
info(`Copying Android Archive of "${dependencyName}"`);
|
||||
copyTasks.push(fs.copy(output, destination));
|
||||
copyTasks.push(cloneFile(output, destination));
|
||||
}
|
||||
}
|
||||
await Promise.all(copyTasks);
|
||||
|
@ -515,31 +527,31 @@ export async function copyProjectAssets(options: Options): Promise<void> {
|
|||
export const rnxCopyAssetsCommand = {
|
||||
name: "rnx-copy-assets",
|
||||
description:
|
||||
"Copies additional assets not picked by bundlers into desired directory.",
|
||||
func: (_argv: string[], _config: CLIConfig, options: Options) => {
|
||||
"Copies additional assets not picked by bundlers into desired directory",
|
||||
func: (_argv: string[], config: CLIConfig, options: Options) => {
|
||||
ensureOption(options, "platform");
|
||||
ensureOption(options, "assetsDest", "assets-dest");
|
||||
return copyProjectAssets(options);
|
||||
return copyProjectAssets(options, config);
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: "--platform <string>",
|
||||
description: "platform to target",
|
||||
description: "Platform to target",
|
||||
parse: parsePlatform,
|
||||
},
|
||||
{
|
||||
name: "--assets-dest <string>",
|
||||
description: "path of the directory to copy assets into",
|
||||
description: "Path of the directory to copy assets into",
|
||||
},
|
||||
{
|
||||
name: "--bundle-aar <boolean>",
|
||||
description: "whether to bundle AARs of dependencies",
|
||||
name: "--bundle-aar [boolean]",
|
||||
description: "Whether to bundle AARs of dependencies",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
name: "--xcassets-dest <string>",
|
||||
description:
|
||||
"path of the directory to copy Xcode asset catalogs into. Asset catalogs will only be copied if a destination path is specified.",
|
||||
"Path of the directory to copy Xcode asset catalogs into; asset catalogs will only be copied if a destination path is specified",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as nodefs from "fs";
|
||||
import * as nodefs from "fs"; // Cannot use `node:fs` because of Jest mocks
|
||||
|
||||
export function ensureDir(p: string, fs = nodefs): void {
|
||||
fs.mkdirSync(p, { recursive: true, mode: 0o755 });
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { TransformProfile } from "metro-babel-transformer";
|
||||
import * as path from "path";
|
||||
import * as path from "node:path";
|
||||
|
||||
export function asBoolean(value: string): boolean {
|
||||
switch (value) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { Command } from "@react-native-community/cli-types";
|
||||
import { rnxAlignDepsCommand } from "./align-deps";
|
||||
import { rnxBundleCommand } from "./bundle";
|
||||
import { rnxCleanCommand } from "./clean";
|
||||
|
@ -17,7 +18,7 @@ export const reactNativeConfig = {
|
|||
rnxTestCommand,
|
||||
rnxWriteThirdPartyNoticesCommand,
|
||||
rnxCleanCommand,
|
||||
],
|
||||
] as Command<false>[],
|
||||
};
|
||||
|
||||
export { rnxAlignDeps, rnxAlignDepsCommand } from "./align-deps";
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { info } from "@rnx-kit/console";
|
||||
import type { MetroTerminal } from "@rnx-kit/metro-service";
|
||||
import nodeFetch from "node-fetch";
|
||||
import readline from "node:readline";
|
||||
import qrcode from "qrcode";
|
||||
import readline from "readline";
|
||||
import type { DevServerMiddleware } from "./types";
|
||||
|
||||
type Options = {
|
||||
|
@ -44,9 +43,7 @@ export function attachKeyHandlers({
|
|||
|
||||
case "j": {
|
||||
info("Opening debugger...");
|
||||
// TODO: Remove `node-fetch` when we drop support for Node 16
|
||||
const ftch = "fetch" in globalThis ? fetch : nodeFetch;
|
||||
ftch(devServerUrl + "/open-debugger", { method: "POST" });
|
||||
fetch(devServerUrl + "/open-debugger", { method: "POST" });
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import type * as logger from "@rnx-kit/console";
|
||||
import type { Server as Middleware } from "connect";
|
||||
import type { Server as HttpServer } from "http";
|
||||
import type { Server as HttpsServer } from "https";
|
||||
import type { RunServerOptions } from "metro";
|
||||
import type { Server as HttpServer } from "node:http";
|
||||
import type { Server as HttpsServer } from "node:https";
|
||||
|
||||
// https://github.com/react-native-community/cli/blob/11.x/packages/cli-server-api/src/index.ts#L32
|
||||
type MiddlewareOptions = {
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
import type { ReportableEvent, Reporter, RunServerOptions } from "metro";
|
||||
import type { Middleware } from "metro-config";
|
||||
import type Server from "metro/src/Server";
|
||||
import * as path from "path";
|
||||
import * as path from "node:path";
|
||||
import { requireExternal } from "./helpers/externals";
|
||||
import { customizeMetroConfig } from "./helpers/metro-config";
|
||||
import { asNumber, asResolvedPath, asStringArray } from "./helpers/parsers";
|
||||
|
|
|
@ -22,7 +22,7 @@ type Options = {
|
|||
| ((config: CLIConfig) => string | boolean | number);
|
||||
};
|
||||
|
||||
const COMMAND_NAME = "rnx-test";
|
||||
const COMMAND_NAMES = ["rnx-test", "test"];
|
||||
const JEST_CLI = ["jest", "jest-cli"];
|
||||
|
||||
export function rnxTest(
|
||||
|
@ -40,7 +40,9 @@ export function rnxTest(
|
|||
}
|
||||
})();
|
||||
|
||||
const commandIndex = process.argv.indexOf(COMMAND_NAME);
|
||||
const commandIndex = process.argv.findIndex((arg) =>
|
||||
COMMAND_NAMES.includes(arg)
|
||||
);
|
||||
if (commandIndex < 0) {
|
||||
throw new Error("Failed to parse command arguments");
|
||||
}
|
||||
|
@ -50,7 +52,9 @@ export function rnxTest(
|
|||
|
||||
const platformIndex = argv.indexOf("--platform");
|
||||
if (platformIndex < 0) {
|
||||
throw new Error("A target platform must be specified");
|
||||
error("A target platform must be specified");
|
||||
process.exitCode = process.exitCode || 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove `--platform` otherwise Jest will complain about an unrecognized
|
||||
|
@ -124,7 +128,7 @@ export function jestOptions(): Options[] {
|
|||
}
|
||||
|
||||
export const rnxTestCommand = {
|
||||
name: COMMAND_NAME,
|
||||
name: COMMAND_NAMES[0],
|
||||
description: "Test runner for React Native apps",
|
||||
func: rnxTest,
|
||||
options: [
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const fs = jest.createMockFromModule("fs-extra");
|
||||
|
||||
const { vol } = require("memfs");
|
||||
|
||||
/** @type {(newMockFiles: { [filename: string]: string }) => void} */
|
||||
fs.__setMockFiles = (files) => {
|
||||
vol.reset();
|
||||
vol.fromJSON(files);
|
||||
};
|
||||
|
||||
fs.__toJSON = () => vol.toJSON();
|
||||
|
||||
fs.copy = (...args) => vol.promises.copyFile(...args);
|
||||
fs.ensureDirSync = (dir) => vol.mkdirSync(dir, { recursive: true });
|
||||
fs.existsSync = (...args) => vol.existsSync(...args);
|
||||
fs.writeFileSync = (...args) => vol.writeFileSync(...args);
|
||||
|
||||
module.exports = fs;
|
|
@ -0,0 +1,66 @@
|
|||
import type { Command } from "@react-native-community/cli-types";
|
||||
import { getCoreCommands, loadContext, uniquify } from "../../src/bin/context";
|
||||
import { reactNativeConfig } from "../../src/index";
|
||||
|
||||
jest.mock("@rnx-kit/tools-react-native/context", () => ({
|
||||
resolveCommunityCLI: () => {
|
||||
throw new Error("Expected fast path");
|
||||
},
|
||||
}));
|
||||
|
||||
describe("getCoreCommands()", () => {
|
||||
it("strips `rnx-` prefix from all commands", () => {
|
||||
const coreCommands = getCoreCommands();
|
||||
for (let i = 0; i < coreCommands.length; ++i) {
|
||||
const modified = coreCommands[i];
|
||||
const original = reactNativeConfig.commands[i];
|
||||
|
||||
expect(modified).not.toBe(original);
|
||||
expect(modified.name.startsWith("rnx-")).toBe(false);
|
||||
expect(modified.description).toBe(original.description);
|
||||
expect(modified.options).toBe(original.options);
|
||||
expect(modified.func).toBe(original.func);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("uniquify()", () => {
|
||||
function makeCommand(name: string, description: string): Command<false> {
|
||||
return { name, description } as Command<false>;
|
||||
}
|
||||
|
||||
it("ignores duplicate commands", () => {
|
||||
const start = makeCommand("start", "first start command");
|
||||
const start2 = makeCommand("start", "second start command");
|
||||
|
||||
expect(uniquify([start, start2])).toMatchObject([start]);
|
||||
});
|
||||
|
||||
it("replaces existing commands with rnx", () => {
|
||||
const start = makeCommand("start", "original start command");
|
||||
const rnxBundle = makeCommand("rnx-bundle", "rnx-bundle command");
|
||||
const rnxStart = makeCommand("rnx-start", "rnx-start command");
|
||||
|
||||
expect(uniquify([start, rnxBundle, rnxStart])).toMatchObject([
|
||||
{ name: "start", description: "rnx-start command" },
|
||||
{ name: "bundle", description: "rnx-bundle command" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadContext()", () => {
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("uses fast code path for rnx commands", () => {
|
||||
for (const { name } of getCoreCommands()) {
|
||||
expect(() => loadContext(name)).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it("uses full code path for other commands", () => {
|
||||
expect(() => loadContext("run-android")).toThrow();
|
||||
expect(() => loadContext("run-ios")).toThrow();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
import type { Config } from "@react-native-community/cli-types";
|
||||
import { findExternalCommands } from "../../src/bin/externalCommands";
|
||||
|
||||
jest.mock("@rnx-kit/tools-react-native/context", () => ({
|
||||
resolveCommunityCLI: () => "/",
|
||||
}));
|
||||
|
||||
function mockContext(context: unknown = {}): Config {
|
||||
return context as Config;
|
||||
}
|
||||
|
||||
describe("findExternalCommands()", () => {
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("returns immediately on fast path", () => {
|
||||
const context = mockContext({ __rnxFastPath: true });
|
||||
|
||||
expect(findExternalCommands(context)).toEqual([]);
|
||||
});
|
||||
|
||||
it("gracefully handles missing external dependencies", () => {
|
||||
const commands = findExternalCommands(mockContext());
|
||||
|
||||
expect(commands.length).toBe(1);
|
||||
expect(commands[0].name).toBe("config");
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import * as path from "path";
|
||||
import * as path from "node:path";
|
||||
import { assembleAarBundle } from "../../src/copy-assets";
|
||||
|
||||
jest.mock("child_process");
|
||||
|
@ -7,7 +7,6 @@ jest.unmock("@rnx-kit/console");
|
|||
|
||||
describe("assembleAarBundle", () => {
|
||||
const fs = require("fs");
|
||||
const fsx = require("fs-extra");
|
||||
|
||||
const consoleWarnSpy = jest.spyOn(global.console, "warn");
|
||||
const spawnSyncSpy = jest.spyOn(require("child_process"), "spawnSync");
|
||||
|
@ -25,17 +24,17 @@ describe("assembleAarBundle", () => {
|
|||
version: "0.0.0-dev",
|
||||
},
|
||||
options,
|
||||
reactNativePath: require.resolve("react-native"),
|
||||
};
|
||||
|
||||
const dummyManifest = JSON.stringify({ version: "0.0.0-dev" });
|
||||
|
||||
function findFiles() {
|
||||
return Object.entries(fsx.__toJSON());
|
||||
return Object.entries(fs.__toJSON());
|
||||
}
|
||||
|
||||
function mockFiles(files: Record<string, string> = {}) {
|
||||
fs.__setMockFiles(files);
|
||||
fsx.__setMockFiles(files);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as path from "path";
|
||||
import { mockFS } from "@rnx-kit/tools-filesystem/mocks";
|
||||
import * as path from "node:path";
|
||||
import { copyAssets, gatherConfigs, versionOf } from "../../src/copy-assets";
|
||||
|
||||
const options = {
|
||||
|
@ -15,41 +16,28 @@ const context = {
|
|||
version: "0.0.0-dev",
|
||||
},
|
||||
options,
|
||||
reactNativePath: require.resolve("react-native"),
|
||||
};
|
||||
|
||||
describe("copyAssets", () => {
|
||||
const fs = require("fs-extra");
|
||||
|
||||
function findFiles() {
|
||||
return Object.entries(fs.__toJSON());
|
||||
}
|
||||
|
||||
function mockFiles(files: Record<string, string> = {}) {
|
||||
fs.__setMockFiles(files);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
mockFiles();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
const mkdirOptions = JSON.stringify({ recursive: true, mode: 0o755 });
|
||||
|
||||
test("returns early if there is nothing to copy", async () => {
|
||||
await copyAssets(context, "test", {});
|
||||
expect(findFiles()).toEqual([]);
|
||||
const files = {};
|
||||
await copyAssets(context, "test", {}, mockFS(files));
|
||||
expect(Object.keys(files)).toEqual([]);
|
||||
});
|
||||
|
||||
test("copies assets", async () => {
|
||||
const filename = "arnolds-greatest-movies.md";
|
||||
const content = "all of them";
|
||||
mockFiles({ [filename]: content });
|
||||
const files = { [filename]: content };
|
||||
|
||||
await copyAssets(context, "test", { assets: [filename] });
|
||||
await copyAssets(context, "test", { assets: [filename] }, mockFS(files));
|
||||
|
||||
expect(findFiles()).toEqual([
|
||||
expect(Object.entries(files)).toEqual([
|
||||
[expect.stringContaining(filename), content],
|
||||
[expect.stringMatching(`dist[/\\\\]assets[/\\\\]test$`), mkdirOptions],
|
||||
[
|
||||
expect.stringMatching(
|
||||
`dist[/\\\\]assets[/\\\\]test[/\\\\]${filename}$`
|
||||
|
@ -62,12 +50,13 @@ describe("copyAssets", () => {
|
|||
test("copies strings", async () => {
|
||||
const filename = "arnolds-greatest-lines.md";
|
||||
const content = "all of them";
|
||||
mockFiles({ [filename]: content });
|
||||
const files = { [filename]: content };
|
||||
|
||||
await copyAssets(context, "test", { strings: [filename] });
|
||||
await copyAssets(context, "test", { strings: [filename] }, mockFS(files));
|
||||
|
||||
expect(findFiles()).toEqual([
|
||||
expect(Object.entries(files)).toEqual([
|
||||
[expect.stringContaining(filename), content],
|
||||
[expect.stringMatching(`dist[/\\\\]strings[/\\\\]test$`), mkdirOptions],
|
||||
[
|
||||
expect.stringMatching(
|
||||
`dist[/\\\\]strings[/\\\\]test[/\\\\]${filename}$`
|
||||
|
@ -80,12 +69,13 @@ describe("copyAssets", () => {
|
|||
test("copies Xcode asset catalogs", async () => {
|
||||
const filename = "arnolds-greatest-assets.xcassets";
|
||||
const content = "all of them";
|
||||
mockFiles({ [filename]: content });
|
||||
const files = { [filename]: content };
|
||||
|
||||
await copyAssets(context, "test", { xcassets: [filename] });
|
||||
await copyAssets(context, "test", { xcassets: [filename] }, mockFS(files));
|
||||
|
||||
expect(findFiles()).toEqual([
|
||||
expect(Object.entries(files)).toEqual([
|
||||
[expect.stringContaining(filename), content],
|
||||
["xcassets", mkdirOptions],
|
||||
[expect.stringMatching(`xcassets[/\\\\]${filename}$`), content],
|
||||
]);
|
||||
});
|
||||
|
@ -93,15 +83,18 @@ describe("copyAssets", () => {
|
|||
test("does not copy Xcode asset catalogs if destination path is unset", async () => {
|
||||
const filename = "arnolds-greatest-assets.xcassets";
|
||||
const content = "all of them";
|
||||
mockFiles({ [filename]: content });
|
||||
const files = { [filename]: content };
|
||||
|
||||
await copyAssets(
|
||||
{ ...context, options: { ...options, xcassetsDest: undefined } },
|
||||
"test",
|
||||
{ xcassets: [filename] }
|
||||
{ xcassets: [filename] },
|
||||
mockFS(files)
|
||||
);
|
||||
|
||||
expect(findFiles()).toEqual([[expect.stringContaining(filename), content]]);
|
||||
expect(Object.entries(files)).toEqual([
|
||||
[expect.stringContaining(filename), content],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -3,26 +3,30 @@
|
|||
"name": "@rnx-kit/test-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"bin": {
|
||||
"rnx": "../cli/bin/rnx-cli.cjs",
|
||||
"rnx.reason": "Workaround for Node not being able to find `rnx-cli` because of Yarn virtual packages"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "react-native rnx-align-deps && rnx-kit-scripts build",
|
||||
"build:ios": "rnx-kit-scripts build-ios -w SampleCrossApp -s ReactTestApp",
|
||||
"android": "rnx run-android --no-packager --appId com.msft.identity.client.sample.local",
|
||||
"build": "rnx align-deps && rnx-kit-scripts build",
|
||||
"build:android": "rnx-kit-scripts build-android clean build",
|
||||
"build:ios": "rnx-kit-scripts build-ios -w SampleCrossApp -s ReactTestApp",
|
||||
"bundle": "rnx bundle",
|
||||
"bundle+esbuild": "rnx bundle --id esbuild",
|
||||
"bundle:android": "rnx bundle --platform android",
|
||||
"bundle:ios": "rnx bundle --platform ios",
|
||||
"bundle:macos": "rnx bundle --platform macos",
|
||||
"bundle:windows": "rnx bundle --platform windows",
|
||||
"depcheck": "rnx-kit-scripts depcheck",
|
||||
"format": "rnx-kit-scripts format",
|
||||
"ios": "rnx run-ios --no-packager",
|
||||
"lint": "rnx-kit-scripts lint",
|
||||
"test": "react-native rnx-test --platform ios",
|
||||
"bundle": "react-native rnx-bundle",
|
||||
"bundle+esbuild": "react-native rnx-bundle --id esbuild",
|
||||
"bundle:android": "react-native rnx-bundle --platform android",
|
||||
"bundle:ios": "react-native rnx-bundle --platform ios",
|
||||
"bundle:macos": "react-native rnx-bundle --platform macos",
|
||||
"bundle:windows": "react-native rnx-bundle --platform windows",
|
||||
"ram-bundle": "react-native rnx-ram-bundle",
|
||||
"android": "react-native run-android --no-packager --appId com.msft.identity.client.sample.local",
|
||||
"ios": "react-native run-ios --no-packager",
|
||||
"macos": "react-native run-macos --scheme ReactTestApp --no-packager",
|
||||
"windows": "react-native run-windows --no-packager --sln windows/SampleCrossApp.sln",
|
||||
"start": "react-native rnx-start"
|
||||
"macos": "rnx run-macos --scheme ReactTestApp --no-packager",
|
||||
"ram-bundle": "rnx ram-bundle",
|
||||
"start": "rnx start",
|
||||
"test": "rnx test --platform ios",
|
||||
"windows": "rnx run-windows --no-packager --sln windows/SampleCrossApp.sln"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-webapis/web-storage": "workspace:*",
|
||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -3877,30 +3877,28 @@ __metadata:
|
|||
"@rnx-kit/metro-service": "npm:^3.1.6"
|
||||
"@rnx-kit/scripts": "npm:*"
|
||||
"@rnx-kit/third-party-notices": "npm:^1.3.4"
|
||||
"@rnx-kit/tools-filesystem": "npm:*"
|
||||
"@rnx-kit/tools-language": "npm:^2.0.0"
|
||||
"@rnx-kit/tools-node": "npm:^2.1.1"
|
||||
"@rnx-kit/tools-react-native": "npm:^1.4.0"
|
||||
"@rnx-kit/tsconfig": "npm:*"
|
||||
"@types/connect": "npm:^3.4.36"
|
||||
"@types/fs-extra": "npm:^9.0.0"
|
||||
"@types/jest": "npm:^29.2.1"
|
||||
"@types/node": "npm:^20.0.0"
|
||||
"@types/node-fetch": "npm:^2.6.5"
|
||||
"@types/qrcode": "npm:^1.4.2"
|
||||
commander: "npm:^11.1.0"
|
||||
eslint: "npm:^8.56.0"
|
||||
fs-extra: "npm:^10.0.0"
|
||||
jest: "npm:^29.2.1"
|
||||
memfs: "npm:^4.0.0"
|
||||
markdown-table: "npm:^3.0.0"
|
||||
metro: "npm:^0.80.3"
|
||||
metro-babel-transformer: "npm:^0.80.0"
|
||||
metro-config: "npm:^0.80.3"
|
||||
node-fetch: "npm:^2.6.7"
|
||||
ora: "npm:^5.4.1"
|
||||
prettier: "npm:^3.0.0"
|
||||
qrcode: "npm:^1.5.0"
|
||||
react: "npm:18.2.0"
|
||||
react-native: "npm:^0.74.0"
|
||||
readline: "npm:^1.3.0"
|
||||
tsx: "npm:^4.15.0"
|
||||
type-fest: "npm:^4.0.0"
|
||||
typescript: "npm:^5.0.0"
|
||||
peerDependencies:
|
||||
|
@ -3911,6 +3909,8 @@ __metadata:
|
|||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
bin:
|
||||
rnx-cli: bin/rnx-cli.cjs
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
|
@ -4555,6 +4555,9 @@ __metadata:
|
|||
react-native-windows: "npm:^0.74.0"
|
||||
react-test-renderer: "npm:18.2.0"
|
||||
typescript: "npm:^5.0.0"
|
||||
bin:
|
||||
rnx: ../cli/bin/rnx-cli.cjs
|
||||
rnx.reason: Workaround for Node not being able to find `rnx-cli` because of Yarn virtual packages
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
|
@ -6531,6 +6534,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^11.1.0":
|
||||
version: 11.1.0
|
||||
resolution: "commander@npm:11.1.0"
|
||||
checksum: 10c0/13cc6ac875e48780250f723fb81c1c1178d35c5decb1abb1b628b3177af08a8554e76b2c0f29de72d69eef7c864d12613272a71fabef8047922bc622ab75a179
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^2.20.0":
|
||||
version: 2.20.3
|
||||
resolution: "commander@npm:2.20.3"
|
||||
|
|
Загрузка…
Ссылка в новой задаче