Skip to content

Commit

Permalink
feat(cli): support multiple debug targets (#3376)
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 authored Jan 29, 2025
1 parent 66d9673 commit 79e2abd
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/calm-adults-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rnx-kit/cli": patch
---

Support multiple debug targets
3 changes: 3 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ packageExtensions:
# https://github.com/microsoft/fluentui/pull/30964
"@types/react":
optional: true
"@react-native/dev-middleware@*":
dependencies:
invariant: ^2.2.4
babel-plugin-transform-flow-enums@*:
peerDependencies:
"@babel/core": ^7.20.0
Expand Down
18 changes: 14 additions & 4 deletions packages/cli/src/helpers/externals.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import { resolveDependencyChain } from "@rnx-kit/tools-node/package";
import { resolveCommunityCLI } from "@rnx-kit/tools-react-native/context";
import * as fs from "node:fs";
import * as path from "node:path";
import type { CliServerApi, CoreDevMiddleware } from "../serve/types";

type ExternalModule =
| "@react-native-community/cli-server-api"
| "@react-native/dev-middleware";

function friendlyRequire<T>(modules: string[], startDir: string): T {
const target = modules.pop();
if (!target) {
throw new Error("At least one target module is required");
}

const resolvedStartDir = fs.lstatSync(startDir).isSymbolicLink()
? path.resolve(path.dirname(startDir), fs.readlinkSync(startDir))
: startDir;
try {
const modulePath = resolveDependencyChain(modules, startDir);
return require(modulePath) as T;
const finalPackageDir = resolveDependencyChain(modules, resolvedStartDir);
const targetModule = require.resolve(target, { paths: [finalPackageDir] });
return require(targetModule) as T;
} catch (_) {
const module = modules[modules.length - 1];
throw new Error(
`Cannot find module '${module}'. This probably means that ` +
`Cannot find module '${target}'. This probably means that ` +
"'@rnx-kit/cli' is not compatible with the version of 'react-native' " +
"that you are currently using. Please update to the latest version " +
"and try again. If the issue still persists after the update, please " +
Expand Down
90 changes: 74 additions & 16 deletions packages/cli/src/serve/keyboard.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,93 @@
import { info } from "@rnx-kit/console";
import type { MetroTerminal } from "@rnx-kit/metro-service";
import * as fs from "node:fs";
import type { Server } from "node:http";
import * as path from "node:path";
import readline from "node:readline";
import qrcode from "qrcode";
import type { DevServerMiddleware } from "./types";

type Options = {
type OpenDebuggerKeyboardHandler = {
handleOpenDebugger: () => Promise<void>;
maybeHandleTargetSelection: (key: string) => boolean;
dismiss: () => void;
};

type Params = {
devServerUrl: string;
help: () => void;
messageSocketEndpoint: DevServerMiddleware["messageSocketEndpoint"];
terminal: MetroTerminal["terminal"];
metroTerminal: MetroTerminal;
reactNativePath: string;
};

export function attachKeyHandlers({
function createOpenDebuggerKeyboardHandler({
devServerUrl,
help,
messageSocketEndpoint,
terminal,
}: Options) {
metroTerminal: { reporter },
reactNativePath,
}: Params): OpenDebuggerKeyboardHandler {
const resolvedPath = fs.lstatSync(reactNativePath).isSymbolicLink()
? path.resolve(
path.dirname(reactNativePath),
fs.readlinkSync(reactNativePath)
)
: reactNativePath;
try {
// Available starting with 0.76
const cliPlugin = require.resolve(
"@react-native/community-cli-plugin/package.json",
{ paths: [resolvedPath] }
);
const { default: OpenDebuggerKeyboardHandler } = require(
`${path.dirname(cliPlugin)}/dist/commands/start/OpenDebuggerKeyboardHandler`
);
return new OpenDebuggerKeyboardHandler({ devServerUrl, reporter });
} catch (_) {
return {
handleOpenDebugger: () => {
info("Opening debugger...");
fetch(devServerUrl + "/open-debugger", { method: "POST" });
return Promise.resolve();
},
maybeHandleTargetSelection: (_: string): boolean => false,
dismiss: () => undefined,
};
}
}

export function attachKeyHandlers(server: Server, params: Params) {
const openDebuggerKeyboardHandler = createOpenDebuggerKeyboardHandler(params);
const {
devServerUrl,
help,
messageSocketEndpoint,
metroTerminal: { terminal },
} = params;

process.on("SIGINT", () => {
openDebuggerKeyboardHandler.dismiss();
process.stdin.pause();
process.stdin.setRawMode(false);
info("Exiting...");
server.close();
server.closeAllConnections?.(); // This method was added in Node v18.2.0

// Even when we close all connections, clients may keep the server alive.
process.exit();
});

process.stdin.setRawMode(true);
process.stdin.on("keypress", (_key, data) => {
const { ctrl, name } = data;
if (openDebuggerKeyboardHandler.maybeHandleTargetSelection(name)) {
return;
}

if (ctrl === true) {
switch (name) {
case "c":
info("Exiting...");
process.exit();
break;
case "z":
process.emit("SIGTSTP", "SIGTSTP");
case "d":
process.emit("SIGINT");
break;
}
} else {
Expand All @@ -41,11 +101,9 @@ export function attachKeyHandlers({
help();
break;

case "j": {
info("Opening debugger...");
fetch(devServerUrl + "/open-debugger", { method: "POST" });
case "j":
openDebuggerKeyboardHandler.handleOpenDebugger();
break;
}

case "q": {
const url = `${devServerUrl}/index.bundle`;
Expand Down
11 changes: 10 additions & 1 deletion packages/cli/src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,16 @@ export async function rnxStart(
// in interactive mode, listen for keyboard events from stdin and bind
// them to specific actions.
if (interactive) {
attachKeyHandlers({ devServerUrl, help, messageSocketEndpoint, terminal });
attachKeyHandlers(serverInstance, {
devServerUrl,
help,
messageSocketEndpoint,
metroTerminal: {
terminal,
reporter: terminalReporter,
},
reactNativePath: ctx.reactNativePath,
});
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/test-app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1466,7 +1466,7 @@ PODS:
- React-logger (= 0.76.6)
- React-perflogger (= 0.76.6)
- React-utils (= 0.76.6)
- ReactNativeHost (0.5.2):
- ReactNativeHost (0.5.3):
- DoubleConversion
- glog
- RCT-Folly (= 2024.01.01.00)
Expand Down Expand Up @@ -1797,7 +1797,7 @@ SPEC CHECKSUMS:
React-utils: ca8195b8b6da1142e93c64d872e5f2e563c917fd
ReactCodegen: b95bdd8716016cb9a831cfd3f23d9b9de4dc5d8e
ReactCommon: 316f528c058ca9bc3a0c430ce46a85bbd69aa2a0
ReactNativeHost: 8474de088a9569717439270e2f95093cf056eb81
ReactNativeHost: c5bb63cd50a88da07f07b4604e51ace44c950a61
ReactTestApp-DevSupport: 66f5c681e1dace4fa01723706b4a1491657a8623
ReactTestApp-MSAL: 90d3923624b7a11b06c113bcaa3ffc964e1c9422
ReactTestApp-Resources: 70da1d78d943a1fdff6362ce3f778e5b4560c95a
Expand Down

0 comments on commit 79e2abd

Please sign in to comment.