Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli): support multiple debug targets #3376

Merged
merged 1 commit into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`
tido64 marked this conversation as resolved.
Show resolved Hide resolved
);
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
Loading