@rnx-kit/cli 0.16.12 → 0.16.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/coverage/clover.xml +2 -2
  3. package/coverage/lcov-report/index.html +1 -1
  4. package/coverage/lcov-report/src/bundle/defaultPlugins.ts.html +1 -1
  5. package/coverage/lcov-report/src/bundle/index.html +1 -1
  6. package/coverage/lcov-report/src/bundle/kit-config.ts.html +1 -1
  7. package/coverage/lcov-report/src/bundle/metro.ts.html +1 -1
  8. package/coverage/lcov-report/src/bundle/overrides.ts.html +1 -1
  9. package/coverage/lcov-report/src/copy-assets.ts.html +1 -1
  10. package/coverage/lcov-report/src/index.html +1 -1
  11. package/coverage/lcov-report/src/metro-config.ts.html +1 -1
  12. package/coverage/lcov-report/src/test.ts.html +1 -1
  13. package/lib/bundle.d.ts +1 -1
  14. package/lib/bundle.d.ts.map +1 -1
  15. package/lib/bundle.js.map +1 -1
  16. package/lib/clean.d.ts.map +1 -1
  17. package/lib/clean.js.map +1 -1
  18. package/lib/ram-bundle.d.ts +1 -1
  19. package/lib/ram-bundle.d.ts.map +1 -1
  20. package/lib/ram-bundle.js.map +1 -1
  21. package/lib/serve/external.d.ts +4 -0
  22. package/lib/serve/external.d.ts.map +1 -0
  23. package/lib/serve/external.js +29 -0
  24. package/lib/serve/external.js.map +1 -0
  25. package/lib/serve/help.d.ts +7 -0
  26. package/lib/serve/help.d.ts.map +1 -0
  27. package/lib/serve/help.js +50 -0
  28. package/lib/serve/help.js.map +1 -0
  29. package/lib/serve/keyboard.d.ts +11 -0
  30. package/lib/serve/keyboard.d.ts.map +1 -0
  31. package/lib/serve/keyboard.js +61 -0
  32. package/lib/serve/keyboard.js.map +1 -0
  33. package/lib/serve/types.d.ts +76 -0
  34. package/lib/serve/types.d.ts.map +1 -0
  35. package/lib/serve/types.js +3 -0
  36. package/lib/serve/types.js.map +1 -0
  37. package/lib/start.d.ts +3 -19
  38. package/lib/start.d.ts.map +1 -1
  39. package/lib/start.js +103 -128
  40. package/lib/start.js.map +1 -1
  41. package/lib/write-third-party-notices.d.ts +1 -1
  42. package/lib/write-third-party-notices.d.ts.map +1 -1
  43. package/lib/write-third-party-notices.js.map +1 -1
  44. package/package.json +17 -13
  45. package/src/bundle.ts +1 -1
  46. package/src/clean.ts +1 -3
  47. package/src/ram-bundle.ts +1 -1
  48. package/src/serve/external.ts +50 -0
  49. package/src/serve/help.ts +60 -0
  50. package/src/serve/keyboard.ts +73 -0
  51. package/src/serve/types.ts +86 -0
  52. package/src/start.ts +138 -186
  53. package/src/write-third-party-notices.ts +1 -1
@@ -0,0 +1,86 @@
1
+ import type * as logger from "@rnx-kit/console";
2
+ import type { Server as Middleware } from "connect";
3
+ import type { Server as HttpServer } from "http";
4
+ import type { Server as HttpsServer } from "https";
5
+ import type { RunServerOptions } from "metro";
6
+
7
+ // https://github.com/react-native-community/cli/blob/11.x/packages/cli-server-api/src/index.ts#L32
8
+ type MiddlewareOptions = {
9
+ host?: string;
10
+ watchFolders: readonly string[];
11
+ port: number;
12
+ };
13
+
14
+ type WebSocketServer = Required<RunServerOptions>["websocketEndpoints"][string];
15
+
16
+ export type DevServerMiddleware = {
17
+ websocketEndpoints: RunServerOptions["websocketEndpoints"];
18
+ debuggerProxyEndpoint: {
19
+ server: WebSocketServer;
20
+ isDebuggerConnected: () => boolean;
21
+ };
22
+ messageSocketEndpoint: {
23
+ server: WebSocketServer;
24
+ broadcast: (method: string, params?: Record<string, unknown>) => void;
25
+ };
26
+ eventsSocketEndpoint: {
27
+ server: WebSocketServer;
28
+ reportEvent: (event: unknown) => void;
29
+ };
30
+ middleware: Middleware;
31
+ };
32
+
33
+ export type DevServerMiddleware6 = Pick<DevServerMiddleware, "middleware"> & {
34
+ attachToServer: (server: HttpServer | HttpsServer) => {
35
+ debuggerProxy: DevServerMiddleware["debuggerProxyEndpoint"];
36
+ eventsSocket: DevServerMiddleware["eventsSocketEndpoint"];
37
+ messageSocket: DevServerMiddleware["messageSocketEndpoint"];
38
+ };
39
+ };
40
+
41
+ /** `@react-native-community/cli-server-api` */
42
+ export type CliServerApi = {
43
+ createDevServerMiddleware: (
44
+ options: MiddlewareOptions
45
+ ) => DevServerMiddleware | DevServerMiddleware6;
46
+ indexPageMiddleware: Middleware;
47
+ };
48
+
49
+ // https://github.com/facebook/react-native/blob/d208dc422c9239d126e0da674451c5898d57319d/packages/community-cli-plugin/src/commands/start/runServer.js#L32
50
+ export type StartCommandArgs = {
51
+ assetPlugins?: string[];
52
+ cert?: string;
53
+ customLogReporterPath?: string;
54
+ host?: string;
55
+ https?: boolean;
56
+ maxWorkers?: number;
57
+ key?: string;
58
+ platforms?: string[];
59
+ port?: number;
60
+ resetCache?: boolean;
61
+ sourceExts?: string[];
62
+ transformer?: string;
63
+ watchFolders?: string[];
64
+ config?: string;
65
+ projectRoot?: string;
66
+ interactive: boolean;
67
+ };
68
+
69
+ // https://github.com/facebook/react-native/blob/3e7a873f2d1c5170a7f4c88064897e74a149c5d5/packages/dev-middleware/src/createDevMiddleware.js#L40
70
+ type DevMiddlewareAPI = {
71
+ middleware: Middleware;
72
+ websocketEndpoints: RunServerOptions["websocketEndpoints"];
73
+ };
74
+
75
+ type DevMiddlewareOptions = {
76
+ projectRoot: string;
77
+ logger?: typeof logger;
78
+ unstable_browserLauncher?: unknown;
79
+ unstable_eventReporter?: unknown;
80
+ unstable_experiments?: unknown;
81
+ };
82
+
83
+ /** `@react-native/dev-middleware` */
84
+ export type CoreDevMiddleware = {
85
+ createDevMiddleware: (options: DevMiddlewareOptions) => DevMiddlewareAPI;
86
+ };
package/src/start.ts CHANGED
@@ -1,66 +1,25 @@
1
- import type CliServerApi from "@react-native-community/cli-server-api";
2
- import type { Config as CLIConfig } from "@react-native-community/cli-types";
1
+ import type { Config } from "@react-native-community/cli-types";
2
+ import * as logger from "@rnx-kit/console";
3
3
  import {
4
4
  createTerminal,
5
+ isDevServerRunning,
5
6
  loadMetroConfig,
6
7
  startServer,
7
8
  } from "@rnx-kit/metro-service";
8
- import chalk from "chalk";
9
- import type { Server as HttpServer } from "http";
10
- import type { Server as HttpsServer } from "https";
11
9
  import type { ReportableEvent, Reporter, RunServerOptions } from "metro";
12
10
  import type { Middleware } from "metro-config";
13
11
  import type Server from "metro/src/Server";
14
- import * as os from "os";
15
12
  import * as path from "path";
16
- import qrcode from "qrcode";
17
- import readline from "readline";
18
13
  import { customizeMetroConfig } from "./metro-config";
14
+ import { requireExternal } from "./serve/external";
15
+ import { makeHelp } from "./serve/help";
16
+ import { attachKeyHandlers } from "./serve/keyboard";
19
17
  import { getKitServerConfig } from "./serve/kit-config";
20
-
21
- type DevServerMiddleware = ReturnType<
22
- (typeof CliServerApi)["createDevServerMiddleware"]
23
- >;
24
-
25
- type DevServerMiddleware6 = Pick<DevServerMiddleware, "middleware"> & {
26
- attachToServer: (server: HttpServer | HttpsServer) => {
27
- debuggerProxy: DevServerMiddleware["debuggerProxyEndpoint"];
28
- eventsSocket: DevServerMiddleware["eventsSocketEndpoint"];
29
- messageSocket: DevServerMiddleware["messageSocketEndpoint"];
30
- };
31
- };
32
-
33
- export type CLIStartOptions = {
34
- port: number;
35
- host: string;
36
- projectRoot?: string;
37
- watchFolders?: string[];
38
- assetPlugins?: string[];
39
- sourceExts?: string[];
40
- maxWorkers?: number;
41
- resetCache?: boolean;
42
- customLogReporterPath?: string;
43
- https?: boolean;
44
- key?: string;
45
- cert?: string;
46
- config?: string;
47
- interactive: boolean;
48
- id?: string;
49
- };
50
-
51
- function friendlyRequire<T>(...modules: string[]): T {
52
- try {
53
- const modulePath = modules.reduce((startDir, module) => {
54
- return require.resolve(module, { paths: [startDir] });
55
- }, process.cwd());
56
- return require(modulePath) as T;
57
- } catch (_) {
58
- const module = modules[modules.length - 1];
59
- throw new Error(
60
- `Cannot find module '${module}'. This probably means that '@rnx-kit/cli' is not compatible with the version of '@react-native-community/cli' that you are currently using. Please update to the latest version and try again. If the issue still persists after the update, please file a bug at https://github.com/microsoft/rnx-kit/issues.`
61
- );
62
- }
63
- }
18
+ import type {
19
+ DevServerMiddleware,
20
+ DevServerMiddleware6,
21
+ StartCommandArgs,
22
+ } from "./serve/types";
64
23
 
65
24
  function hasAttachToServerFunction(
66
25
  devServer: DevServerMiddleware | DevServerMiddleware6
@@ -69,75 +28,31 @@ function hasAttachToServerFunction(
69
28
  }
70
29
 
71
30
  export async function rnxStart(
72
- _argv: Array<string>,
73
- cliConfig: CLIConfig,
74
- cliOptions: CLIStartOptions
31
+ _argv: string[],
32
+ ctx: Config,
33
+ args: StartCommandArgs
75
34
  ): Promise<void> {
76
- const serverConfig = getKitServerConfig(cliOptions);
35
+ const serverConfig = getKitServerConfig(args);
77
36
 
78
- const { createDevServerMiddleware, indexPageMiddleware } = friendlyRequire<
79
- typeof CliServerApi
80
- >(
81
- "react-native",
82
- "@react-native-community/cli",
37
+ const { createDevServerMiddleware, indexPageMiddleware } = requireExternal(
83
38
  "@react-native-community/cli-server-api"
84
39
  );
85
40
 
86
41
  // interactive mode requires raw access to stdin
87
- let interactive = cliOptions.interactive;
42
+ let interactive = args.interactive;
88
43
  if (interactive) {
89
44
  interactive = process.stdin.isTTY;
90
45
  if (!interactive) {
91
- console.warn(
92
- chalk.yellow(
93
- "Warning: Interactive mode is not supported on this terminal"
94
- )
95
- );
46
+ logger.warn("Interactive mode is not supported in this environment");
96
47
  }
97
48
  }
98
49
 
99
- // create a Metro terminal and reporter for writing to the console
100
- const { terminal, reporter: terminalReporter } = createTerminal(
101
- cliOptions.customLogReporterPath
102
- );
103
-
104
- const printHelp = () => {
105
- const dim = chalk.dim;
106
- const press = dim(" › Press ");
107
- [
108
- ["r", "reload the app"],
109
- ["d", "open developer menu"],
110
- ["a", "show bundler address QR code"],
111
- ["h", "show this help message"],
112
- ["ctrl-c", "quit"],
113
- ].forEach(([key, description]) => {
114
- terminal.log(press + key + dim(` to ${description}.`));
115
- });
116
- };
117
-
118
- // create a reporter function, to be bound to the Metro configuration.
119
- // which writes to the Metro terminal and
120
- // also notifies the `reportEvent` delegate.
121
- let reportEventDelegate: Reporter["update"] | undefined = undefined;
122
- const reporter: Reporter = {
123
- update(event: ReportableEvent) {
124
- terminalReporter.update(event);
125
- if (reportEventDelegate) {
126
- reportEventDelegate(event);
127
- }
128
- if (interactive && event.type === "dep_graph_loading") {
129
- printHelp();
130
- }
131
- },
132
- };
133
-
134
50
  // load Metro configuration, applying overrides from the command line
135
- const metroConfig = await loadMetroConfig(cliConfig, {
136
- ...cliOptions,
51
+ const metroConfig = await loadMetroConfig(ctx, {
52
+ ...args,
137
53
  ...(serverConfig.projectRoot
138
54
  ? { projectRoot: path.resolve(serverConfig.projectRoot) }
139
55
  : undefined),
140
- reporter,
141
56
  ...(serverConfig.sourceExts
142
57
  ? { sourceExts: serverConfig.sourceExts }
143
58
  : undefined),
@@ -150,39 +65,89 @@ export async function rnxStart(
150
65
  : undefined),
151
66
  });
152
67
 
68
+ // create a Metro terminal and reporter for writing to the console
69
+ const { terminal, reporter: terminalReporter } = createTerminal(
70
+ args.customLogReporterPath
71
+ );
72
+
153
73
  // customize the metro config to include plugins, presets, etc.
154
- customizeMetroConfig(metroConfig, serverConfig, (message: string): void => {
155
- terminal.log(message);
156
- });
74
+ const log = (message: string): void => terminal.log(message);
75
+ customizeMetroConfig(metroConfig, serverConfig, log);
76
+
77
+ const {
78
+ projectRoot,
79
+ server: { port },
80
+ watchFolders,
81
+ } = metroConfig;
82
+ const scheme = args.https === true ? "https" : "http";
83
+ const serverStatus = await isDevServerRunning(
84
+ scheme,
85
+ args.host,
86
+ port,
87
+ projectRoot
88
+ );
89
+
90
+ switch (serverStatus) {
91
+ case "already_running":
92
+ logger.info(
93
+ `A dev server is already running for this project on port ${port}. ` +
94
+ "Exiting..."
95
+ );
96
+ return;
97
+ case "in_use":
98
+ logger.error(
99
+ `Another process is using port ${port}. Please terminate this ` +
100
+ "process and try again, or try another port with `--port`."
101
+ );
102
+ return;
103
+ }
157
104
 
158
105
  // create middleware -- a collection of plugins which handle incoming
159
106
  // http(s) requests, routing them to static pages or JS functions.
160
- const devServer = createDevServerMiddleware({
161
- host: cliOptions.host,
162
- port: metroConfig.server.port,
163
- watchFolders: metroConfig.watchFolders,
164
- });
165
- const middleware = devServer.middleware;
166
- middleware.use(indexPageMiddleware);
167
-
168
- // merge the Metro config middleware with our middleware
169
- const enhanceMiddleware = metroConfig.server.enhanceMiddleware;
170
- // @ts-expect-error We want to assign to read-only `enhanceMiddleware`
171
- metroConfig.server.enhanceMiddleware = (
172
- metroMiddleware: Middleware,
173
- metroServer: Server
174
- ) => {
175
- return middleware.use(
176
- enhanceMiddleware
177
- ? enhanceMiddleware(metroMiddleware, metroServer)
178
- : metroMiddleware
179
- );
180
- };
107
+ const host = args.host?.length ? args.host : "localhost";
108
+ const devServer = createDevServerMiddleware({ host, port, watchFolders });
109
+
110
+ const coreDevMiddleware = (() => {
111
+ try {
112
+ // https://github.com/facebook/react-native/blob/3e7a873f2d1c5170a7f4c88064897e74a149c5d5/packages/community-cli-plugin/src/commands/start/runServer.js#L115
113
+ const { createDevMiddleware } = requireExternal(
114
+ "@react-native/dev-middleware"
115
+ );
116
+ return createDevMiddleware({
117
+ projectRoot,
118
+ logger,
119
+ unstable_experiments: {
120
+ // NOTE: Only affects the /open-debugger endpoint
121
+ enableCustomDebuggerFrontend: true,
122
+ },
123
+ });
124
+ } catch (_) {
125
+ // Fallback to the behavior from before 0.73
126
+ const middleware = devServer.middleware;
127
+ middleware.use(indexPageMiddleware);
128
+
129
+ // merge the Metro config middleware with our middleware
130
+ const enhanceMiddleware = metroConfig.server.enhanceMiddleware;
131
+ // @ts-expect-error We want to override `enhanceMiddleware`
132
+ metroConfig.server.enhanceMiddleware = (
133
+ metroMiddleware: Middleware,
134
+ metroServer: Server
135
+ ) => {
136
+ return middleware.use(
137
+ enhanceMiddleware
138
+ ? enhanceMiddleware(metroMiddleware, metroServer)
139
+ : metroMiddleware
140
+ );
141
+ };
142
+ return undefined;
143
+ }
144
+ })();
181
145
 
182
146
  // `createDevServerMiddleware` changed its return type in
183
147
  // https://github.com/react-native-community/cli/pull/1560
184
148
  let websocketEndpoints: RunServerOptions["websocketEndpoints"] = undefined;
185
149
  let messageSocketEndpoint: DevServerMiddleware["messageSocketEndpoint"];
150
+ let reportEventDelegate: Reporter["update"] | undefined = undefined;
186
151
 
187
152
  if (!hasAttachToServerFunction(devServer)) {
188
153
  websocketEndpoints = devServer.websocketEndpoints;
@@ -192,15 +157,42 @@ export async function rnxStart(
192
157
  reportEventDelegate = devServer.eventsSocketEndpoint.reportEvent;
193
158
  }
194
159
 
195
- // start the Metro server
196
- const serverOptions = {
197
- host: cliOptions.host,
198
- secure: cliOptions.https,
199
- secureCert: cliOptions.cert,
200
- secureKey: cliOptions.key,
201
- websocketEndpoints,
160
+ const printHelp = makeHelp(terminal, {
161
+ hasDebugger: Boolean(coreDevMiddleware),
162
+ });
163
+
164
+ // @ts-expect-error We want to override `reporter`
165
+ metroConfig.reporter = {
166
+ update(event: ReportableEvent) {
167
+ terminalReporter.update(event);
168
+ if (reportEventDelegate) {
169
+ reportEventDelegate(event);
170
+ }
171
+ if (interactive && event.type === "dep_graph_loading") {
172
+ printHelp();
173
+ }
174
+ },
202
175
  };
203
- const serverInstance = await startServer(metroConfig, serverOptions);
176
+
177
+ const serverInstance = await startServer(metroConfig, {
178
+ host: args.host,
179
+ secure: args.https,
180
+ secureCert: args.cert,
181
+ secureKey: args.key,
182
+ ...(coreDevMiddleware
183
+ ? {
184
+ unstable_extraMiddleware: [
185
+ devServer.middleware,
186
+ indexPageMiddleware,
187
+ coreDevMiddleware.middleware,
188
+ ],
189
+ websocketEndpoints: {
190
+ ...websocketEndpoints,
191
+ ...coreDevMiddleware.websocketEndpoints,
192
+ },
193
+ }
194
+ : { websocketEndpoints }),
195
+ });
204
196
 
205
197
  if (hasAttachToServerFunction(devServer)) {
206
198
  const { messageSocket, eventsSocket } =
@@ -209,6 +201,10 @@ export async function rnxStart(
209
201
 
210
202
  // bind our `reportEvent` delegate to the Metro server
211
203
  reportEventDelegate = eventsSocket.reportEvent;
204
+ } else {
205
+ // `messageSocketEndpoint` should already be set at this point. But this
206
+ // makes TypeScript happier.
207
+ messageSocketEndpoint = devServer.messageSocketEndpoint;
212
208
  }
213
209
 
214
210
  // In Node 8, the default keep-alive for an HTTP connection is 5 seconds. In
@@ -226,55 +222,11 @@ export async function rnxStart(
226
222
  // in interactive mode, listen for keyboard events from stdin and bind
227
223
  // them to specific actions.
228
224
  if (interactive) {
229
- readline.emitKeypressEvents(process.stdin);
230
-
231
- process.stdin.setRawMode(true);
232
- process.stdin.on("keypress", (_key, data) => {
233
- const { ctrl, name } = data;
234
- if (ctrl === true) {
235
- switch (name) {
236
- case "c":
237
- terminal.log(chalk.green("Exiting..."));
238
- process.exit();
239
- break;
240
- case "z":
241
- process.emit("SIGTSTP", "SIGTSTP");
242
- break;
243
- }
244
- } else {
245
- switch (name) {
246
- case "a": {
247
- const protocol = cliOptions.https ? "https" : "http";
248
- const host = cliOptions.host || os.hostname();
249
- const port = metroConfig.server.port;
250
- const url = `${protocol}://${host}:${port}/index.bundle`;
251
- qrcode.toString(url, { type: "terminal" }, (_err, qr) => {
252
- terminal.log("");
253
- terminal.log(url + ":");
254
- terminal.log(qr);
255
- });
256
- break;
257
- }
258
-
259
- case "d":
260
- terminal.log(chalk.green("Opening developer menu..."));
261
- messageSocketEndpoint.broadcast("devMenu", undefined);
262
- break;
263
-
264
- case "h":
265
- printHelp();
266
- break;
267
-
268
- case "r":
269
- terminal.log(chalk.green("Reloading app..."));
270
- messageSocketEndpoint.broadcast("reload", undefined);
271
- break;
272
-
273
- case "return":
274
- terminal.log("");
275
- break;
276
- }
277
- }
225
+ attachKeyHandlers({
226
+ devServerUrl: `${scheme}://${host}:${port}`,
227
+ help: printHelp,
228
+ messageSocketEndpoint,
229
+ terminal,
278
230
  });
279
231
  }
280
232
  }
@@ -13,7 +13,7 @@ type CliThirdPartyNoticesOptions = {
13
13
  };
14
14
 
15
15
  export function rnxWriteThirdPartyNotices(
16
- _argv: Array<string>,
16
+ _argv: string[],
17
17
  _config: CLIConfig,
18
18
  {
19
19
  additionalText,