mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
feat(cli): add remote debugging port option
Adds a Chromium remote debugging port flag for preview and play. The flag is only passed when launching an explicit browser/profile. HyperFrames still does not own CDP automation.
This commit is contained in:
@@ -13,7 +13,7 @@ import { resolve, dirname } from "node:path";
|
||||
import * as clack from "@clack/prompts";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { resolveProject } from "../utils/project.js";
|
||||
import { openBrowser } from "../utils/openBrowser.js";
|
||||
import { openBrowser, parseRemoteDebuggingPort } from "../utils/openBrowser.js";
|
||||
|
||||
export default defineCommand({
|
||||
meta: { name: "play", description: "Play a composition in a lightweight browser player" },
|
||||
@@ -33,6 +33,10 @@ export default defineCommand({
|
||||
type: "string",
|
||||
description: "Chromium-compatible user data directory (requires --browser-path)",
|
||||
},
|
||||
"remote-debugging-port": {
|
||||
type: "string",
|
||||
description: "Chromium remote debugging port (requires --browser-path and --user-data-dir)",
|
||||
},
|
||||
},
|
||||
async run({ args }) {
|
||||
const project = resolveProject(args.dir);
|
||||
@@ -44,6 +48,19 @@ export default defineCommand({
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
// Validation: --remote-debugging-port requires --browser-path and --user-data-dir
|
||||
if (args["remote-debugging-port"]) {
|
||||
if (!args["browser-path"]) {
|
||||
clack.log.error("--remote-debugging-port requires --browser-path");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (!args["user-data-dir"]) {
|
||||
clack.log.error("--remote-debugging-port requires --user-data-dir");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve runtime path — same logic as studioServer.ts
|
||||
const runtimePath = resolveRuntimePath();
|
||||
@@ -168,10 +185,22 @@ export default defineCommand({
|
||||
console.log();
|
||||
console.log(` ${c.dim("Press Ctrl+C to stop")}`);
|
||||
console.log();
|
||||
let remoteDebuggingPort: number | undefined;
|
||||
if (args["remote-debugging-port"]) {
|
||||
try {
|
||||
remoteDebuggingPort = parseRemoteDebuggingPort(args["remote-debugging-port"]);
|
||||
} catch (err) {
|
||||
clack.log.error((err as Error).message);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.open) {
|
||||
void openBrowser(url, {
|
||||
browserPath: args["browser-path"] as string | undefined,
|
||||
userDataDir: args["user-data-dir"] as string | undefined,
|
||||
remoteDebuggingPort,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { createRequire } from "node:module";
|
||||
import * as clack from "@clack/prompts";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { isDevMode } from "../utils/env.js";
|
||||
import { openBrowser } from "../utils/openBrowser.js";
|
||||
import { openBrowser, parseRemoteDebuggingPort } from "../utils/openBrowser.js";
|
||||
import { lintProject } from "../utils/lintProject.js";
|
||||
import { formatLintFindings } from "../utils/lintFormat.js";
|
||||
import {
|
||||
@@ -63,6 +63,10 @@ export default defineCommand({
|
||||
type: "string",
|
||||
description: "Chromium-compatible user data directory (requires --browser-path)",
|
||||
},
|
||||
"remote-debugging-port": {
|
||||
type: "string",
|
||||
description: "Chromium remote debugging port (requires --browser-path and --user-data-dir)",
|
||||
},
|
||||
},
|
||||
async run({ args }) {
|
||||
const startPort = parseInt(args.port ?? "3002", 10);
|
||||
@@ -131,18 +135,53 @@ export default defineCommand({
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
// Validation: --remote-debugging-port requires --browser-path and --user-data-dir
|
||||
if (args["remote-debugging-port"]) {
|
||||
if (!args["browser-path"]) {
|
||||
clack.log.error("--remote-debugging-port requires --browser-path");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (!args["user-data-dir"]) {
|
||||
clack.log.error("--remote-debugging-port requires --user-data-dir");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const noOpen = !args.open;
|
||||
const browserPath = args["browser-path"] as string | undefined;
|
||||
const userDataDir = args["user-data-dir"] as string | undefined;
|
||||
let remoteDebuggingPort: number | undefined;
|
||||
if (args["remote-debugging-port"]) {
|
||||
try {
|
||||
remoteDebuggingPort = parseRemoteDebuggingPort(args["remote-debugging-port"]);
|
||||
} catch (err) {
|
||||
clack.log.error((err as Error).message);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDevMode()) {
|
||||
return runDevMode(dir, { projectName, noOpen, browserPath, userDataDir });
|
||||
return runDevMode(dir, {
|
||||
projectName,
|
||||
noOpen,
|
||||
browserPath,
|
||||
userDataDir,
|
||||
remoteDebuggingPort,
|
||||
});
|
||||
}
|
||||
|
||||
// If @hyperframes/studio is installed locally, use Vite for full HMR
|
||||
if (hasLocalStudio(dir)) {
|
||||
return runLocalStudioMode(dir, { projectName, noOpen, browserPath, userDataDir });
|
||||
return runLocalStudioMode(dir, {
|
||||
projectName,
|
||||
noOpen,
|
||||
browserPath,
|
||||
userDataDir,
|
||||
remoteDebuggingPort,
|
||||
});
|
||||
}
|
||||
|
||||
const forceNew = !!args["force-new"];
|
||||
@@ -152,6 +191,7 @@ export default defineCommand({
|
||||
noOpen,
|
||||
browserPath,
|
||||
userDataDir,
|
||||
remoteDebuggingPort,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -161,7 +201,13 @@ export default defineCommand({
|
||||
*/
|
||||
async function runDevMode(
|
||||
dir: string,
|
||||
options?: { projectName?: string; noOpen?: boolean; browserPath?: string; userDataDir?: string },
|
||||
options?: {
|
||||
projectName?: string;
|
||||
noOpen?: boolean;
|
||||
browserPath?: string;
|
||||
userDataDir?: string;
|
||||
remoteDebuggingPort?: number;
|
||||
},
|
||||
): Promise<void> {
|
||||
// Find monorepo root by navigating from packages/cli/src/commands/
|
||||
const thisFile = fileURLToPath(import.meta.url);
|
||||
@@ -231,6 +277,7 @@ async function runDevMode(
|
||||
openBrowser(urlToOpen, {
|
||||
browserPath: options?.browserPath,
|
||||
userDataDir: options?.userDataDir,
|
||||
remoteDebuggingPort: options?.remoteDebuggingPort,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -294,7 +341,13 @@ function hasLocalStudio(dir: string): boolean {
|
||||
*/
|
||||
async function runLocalStudioMode(
|
||||
dir: string,
|
||||
options?: { projectName?: string; noOpen?: boolean; browserPath?: string; userDataDir?: string },
|
||||
options?: {
|
||||
projectName?: string;
|
||||
noOpen?: boolean;
|
||||
browserPath?: string;
|
||||
userDataDir?: string;
|
||||
remoteDebuggingPort?: number;
|
||||
},
|
||||
): Promise<void> {
|
||||
const req = createRequire(join(dir, "package.json"));
|
||||
const studioPkgPath = dirname(req.resolve("@hyperframes/studio/package.json"));
|
||||
@@ -346,6 +399,7 @@ async function runLocalStudioMode(
|
||||
openBrowser(`${url}#project/${pName}`, {
|
||||
browserPath: options?.browserPath,
|
||||
userDataDir: options?.userDataDir,
|
||||
remoteDebuggingPort: options?.remoteDebuggingPort,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -396,6 +450,7 @@ async function runEmbeddedMode(
|
||||
noOpen?: boolean;
|
||||
browserPath?: string;
|
||||
userDataDir?: string;
|
||||
remoteDebuggingPort?: number;
|
||||
},
|
||||
): Promise<void> {
|
||||
const { createStudioServer, loadPreviewServerBuildSignature, resolveStudioBundle } =
|
||||
@@ -458,6 +513,7 @@ async function runEmbeddedMode(
|
||||
openBrowser(`${url}#project/${pName}`, {
|
||||
browserPath: options?.browserPath,
|
||||
userDataDir: options?.userDataDir,
|
||||
remoteDebuggingPort: options?.remoteDebuggingPort,
|
||||
});
|
||||
}
|
||||
return;
|
||||
@@ -482,6 +538,7 @@ async function runEmbeddedMode(
|
||||
openBrowser(`${url}#project/${pName}`, {
|
||||
browserPath: options?.browserPath,
|
||||
userDataDir: options?.userDataDir,
|
||||
remoteDebuggingPort: options?.remoteDebuggingPort,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { buildBrowserArgs } from "./openBrowser.js";
|
||||
import { buildBrowserArgs, parseRemoteDebuggingPort } from "./openBrowser.js";
|
||||
|
||||
describe("buildBrowserArgs", () => {
|
||||
it("returns only the URL when no options are given", () => {
|
||||
@@ -37,4 +37,80 @@ describe("buildBrowserArgs", () => {
|
||||
}),
|
||||
).toEqual(["--user-data-dir=C:\\Documents and Settings\\profile", "http://localhost:3002"]);
|
||||
});
|
||||
|
||||
it("prepends --remote-debugging-port before the URL", () => {
|
||||
expect(
|
||||
buildBrowserArgs("http://localhost:3002", {
|
||||
browserPath: "/usr/bin/chromium",
|
||||
userDataDir: "/tmp/hf-profile",
|
||||
remoteDebuggingPort: 9222,
|
||||
}),
|
||||
).toEqual([
|
||||
"--user-data-dir=/tmp/hf-profile",
|
||||
"--remote-debugging-port=9222",
|
||||
"http://localhost:3002",
|
||||
]);
|
||||
});
|
||||
|
||||
it("includes all flags together", () => {
|
||||
expect(
|
||||
buildBrowserArgs("http://localhost:3002", {
|
||||
browserPath: "/usr/bin/chromium",
|
||||
userDataDir: "/tmp/hf-profile",
|
||||
remoteDebuggingPort: 9222,
|
||||
}),
|
||||
).toEqual([
|
||||
"--user-data-dir=/tmp/hf-profile",
|
||||
"--remote-debugging-port=9222",
|
||||
"http://localhost:3002",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseRemoteDebuggingPort", () => {
|
||||
it("returns undefined for undefined", () => {
|
||||
expect(parseRemoteDebuggingPort(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for empty string", () => {
|
||||
expect(parseRemoteDebuggingPort("")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("parses a valid port number", () => {
|
||||
expect(parseRemoteDebuggingPort("9222")).toBe(9222);
|
||||
});
|
||||
|
||||
it("parses port 1 (minimum)", () => {
|
||||
expect(parseRemoteDebuggingPort("1")).toBe(1);
|
||||
});
|
||||
|
||||
it("parses port 65535 (maximum)", () => {
|
||||
expect(parseRemoteDebuggingPort("65535")).toBe(65535);
|
||||
});
|
||||
|
||||
it("rejects 0", () => {
|
||||
expect(() => parseRemoteDebuggingPort("0")).toThrow(
|
||||
"--remote-debugging-port must be an integer between 1 and 65535",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects negative numbers", () => {
|
||||
expect(() => parseRemoteDebuggingPort("-1")).toThrow();
|
||||
});
|
||||
|
||||
it("rejects non-numeric input", () => {
|
||||
expect(() => parseRemoteDebuggingPort("abc")).toThrow();
|
||||
});
|
||||
|
||||
it("rejects trailing non-digits (no parseInt leakage)", () => {
|
||||
expect(() => parseRemoteDebuggingPort("9222abc")).toThrow();
|
||||
});
|
||||
|
||||
it("rejects numbers above 65535", () => {
|
||||
expect(() => parseRemoteDebuggingPort("70000")).toThrow();
|
||||
});
|
||||
|
||||
it("rejects decimals", () => {
|
||||
expect(() => parseRemoteDebuggingPort("22.5")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,30 @@ import { spawn } from "node:child_process";
|
||||
export interface OpenBrowserOptions {
|
||||
browserPath?: string;
|
||||
userDataDir?: string;
|
||||
remoteDebuggingPort?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and parse a --remote-debugging-port value.
|
||||
* Returns the port number or undefined if not provided.
|
||||
* Throws if the value is not a valid integer in 1..65535.
|
||||
*/
|
||||
export function parseRemoteDebuggingPort(value: unknown): number | undefined {
|
||||
if (value === undefined || value === null || value === "") return undefined;
|
||||
|
||||
const text = String(value);
|
||||
|
||||
if (!/^\d+$/.test(text)) {
|
||||
throw new Error("--remote-debugging-port must be an integer between 1 and 65535");
|
||||
}
|
||||
|
||||
const port = Number(text);
|
||||
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
||||
throw new Error("--remote-debugging-port must be an integer between 1 and 65535");
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,6 +39,9 @@ export function buildBrowserArgs(url: string, options: OpenBrowserOptions): stri
|
||||
if (options.userDataDir) {
|
||||
args.push(`--user-data-dir=${options.userDataDir}`);
|
||||
}
|
||||
if (options.remoteDebuggingPort !== undefined) {
|
||||
args.push(`--remote-debugging-port=${options.remoteDebuggingPort}`);
|
||||
}
|
||||
args.push(url);
|
||||
return args;
|
||||
}
|
||||
@@ -24,6 +51,7 @@ export function buildBrowserArgs(url: string, options: OpenBrowserOptions): stri
|
||||
*
|
||||
* - browserPath: spawn the given binary directly (enables Chromium flags)
|
||||
* - userDataDir: passed as --user-data-dir (requires browserPath)
|
||||
* - remoteDebuggingPort: passed as --remote-debugging-port (requires browserPath + userDataDir)
|
||||
* - otherwise: fall back to the `open` package (default browser)
|
||||
*/
|
||||
export function openBrowser(url: string, options: OpenBrowserOptions = {}): void {
|
||||
|
||||
Reference in New Issue
Block a user