rechrome 1.28.0 → 1.28.1

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.
package/README.md CHANGED
@@ -285,7 +285,7 @@ directory. Pass `-s=<name>` for a named sub-session, or `--isolate` for a throwa
285
285
  ```bash
286
286
  git clone https://github.com/snomiao/rechrome.git
287
287
  cd rechrome
288
- bun install
288
+ bun install # `prepare` also builds vendor/ from vendor-src/, so the checkout has a working CLI
289
289
  bun link # makes this checkout the global rechrome / rech
290
290
  bun test ./*.test.ts ./*.spec.ts ./scripts/*.test.ts # rechrome's own tests (plain `bun test` also finds the vendored forks' suites)
291
291
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rechrome",
3
- "version": "1.28.0",
3
+ "version": "1.28.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snomiao/rechrome.git"
@@ -32,7 +32,9 @@
32
32
  "serve": "bun run rechrome.ts serve",
33
33
  "test": "bun test",
34
34
  "vendor-cli": "bash scripts/vendor-cli.sh",
35
- "prepublishOnly": "bash scripts/vendor-cli.sh && sed 's/\\.ts/\\.js/g' rechrome.ts > rechrome.js && sed 's/\\.ts/\\.js/g' serve.ts > serve.js && bun build extension-token.ts --target=bun --outfile=extension-token.js && bun build daemon-manager.ts --target=bun --outfile=daemon-manager.js && bun build listeners.ts --target=bun --outfile=listeners.js"
35
+ "prepare": "bash scripts/vendor-cli.sh --prepare",
36
+ "check-pack": "bash scripts/check-pack.sh",
37
+ "prepublishOnly": "bash scripts/vendor-cli.sh && sed 's/\\.ts/\\.js/g' rechrome.ts > rechrome.js && sed 's/\\.ts/\\.js/g' serve.ts > serve.js && bun build extension-token.ts --target=bun --outfile=extension-token.js && bun build daemon-manager.ts --target=bun --outfile=daemon-manager.js && bun build listeners.ts --target=bun --outfile=listeners.js && bash scripts/check-pack.sh"
36
38
  },
37
39
  "devDependencies": {
38
40
  "@types/bun": "latest",
package/rechrome.js CHANGED
@@ -10,6 +10,7 @@ import { hostname, homedir, networkInterfaces } from "os";
10
10
  import { isIPv4 } from "net";
11
11
  import { join, basename, dirname } from "path";
12
12
  import { pathToFileURL } from "url";
13
+ import { createRequire } from "node:module";
13
14
  import { spawn as cpSpawn } from "child_process";
14
15
  import { readFile, writeFile, rename, chmod, mkdir } from "node:fs/promises";
15
16
  import { oxmgrInstallCommand, pickDaemonManager, type DaemonManager } from "./daemon-manager.js";
@@ -1446,23 +1447,41 @@ async function pmList(mgr: DaemonManager = daemonManager()): Promise<string> {
1446
1447
  return await new Response(proc.stdout).text();
1447
1448
  }
1448
1449
 
1450
+ // A candidate playwright-cli entry is usable only if the playwright-core it requires actually
1451
+ // resolves FROM that entry — the wrapper does `require('playwright-core/lib/tools/cli-client/program')`,
1452
+ // a deep subpath reachable only through the fork's patched `exports` map. existsSync on the .js is
1453
+ // not the same check: an uninitialised/half-built lib/playwright-cli submodule leaves the wrapper on
1454
+ // disk with no resolvable core, and the failure surfaces later as MODULE_NOT_FOUND inside the daemon.
1455
+ export function playwrightCliIsUsable(jsEntry: string): boolean {
1456
+ try {
1457
+ createRequire(jsEntry).resolve("playwright-core/lib/tools/cli-client/program");
1458
+ return true;
1459
+ } catch {
1460
+ return false;
1461
+ }
1462
+ }
1463
+
1449
1464
  // Resolve which playwright-cli the daemon runs to drive Chrome. Priority:
1450
1465
  // 1. PLAYWRIGHT_CLI env override — explicit, already a full command string.
1451
1466
  // 2. Vendored fork in a git checkout (lib/playwright-cli/playwright-cli.js) — the patched
1452
1467
  // multi-tab CLI + patched playwright-core (PLAYWRIGHT_MCP_PROFILE_DIRECTORY etc.).
1453
1468
  // 3. The fork bundled into the npm tarball (vendor/playwright-cli/playwright-cli.js, produced by
1454
- // scripts/vendor-cli.sh at prepublish). This is the batteries-included default for
1455
- // `bun i -g rechrome`: self-contained, no @playwright/cli dep, no browser-binary download.
1469
+ // scripts/vendor-cli.sh at prepublish, and by `prepare` on `bun install` in a checkout). This
1470
+ // is the batteries-included default for `bun i -g rechrome`: self-contained, no
1471
+ // @playwright/cli dep, no browser-binary download.
1456
1472
  // 4. Bare `playwright-cli-multi-tab` on PATH — legacy fallback for a pre-existing global link.
1473
+ // Candidates 2–3 must also pass playwrightCliIsUsable(), so a present-but-broken one falls through.
1474
+ // lib/ stays ahead of vendor/ on purpose: a dev who built the fork wants their patched core, not
1475
+ // the (possibly older) vendor-src snapshot that `prepare` unpacks into vendor/.
1457
1476
  // A resolved .js entry is run through `node` on Windows (which can't exec a .js by shebang) and
1458
1477
  // bare on POSIX (its `#!/usr/bin/env node` shebang runs it under node, which the relay handshake
1459
1478
  // needs — see daemonInstall). serve splits the result on spaces into argv.
1460
- export function resolvePlaywrightCli(): string {
1479
+ export function resolvePlaywrightCli(root: string = import.meta.dir): string {
1461
1480
  if (process.env.PLAYWRIGHT_CLI) return process.env.PLAYWRIGHT_CLI;
1462
1481
  const jsEntry = [
1463
- join(import.meta.dir, "lib/playwright-cli/playwright-cli.js"),
1464
- join(import.meta.dir, "vendor/playwright-cli/playwright-cli.js"),
1465
- ].find(existsSync);
1482
+ join(root, "lib/playwright-cli/playwright-cli.js"),
1483
+ join(root, "vendor/playwright-cli/playwright-cli.js"),
1484
+ ].filter(existsSync).find(playwrightCliIsUsable);
1466
1485
  if (jsEntry) return IS_WINDOWS ? `node ${jsEntry}` : jsEntry;
1467
1486
  return "playwright-cli-multi-tab";
1468
1487
  }
@@ -2586,7 +2605,8 @@ export function rechCli(argv: string[], handlers: RechHandlers) {
2586
2605
  ? handlers.urlList()
2587
2606
  : handlers.printProfileUri(a.profile, a.listener, { local: a.local, save: a.save }))
2588
2607
  .command("connect <url>", "Use a URL from another machine in this project (checks it first)", y => y
2589
- .positional("url", { type: "string", demandOption: true }),
2608
+ .positional("url", { type: "string", demandOption: true, describe: "The URL printed by `rech url <profile>` on the machine with Chrome. Quote it: it contains #" })
2609
+ .example("rech connect 'https://host.example.js.net/rechrome/?profile=you%40example.com#key=…'", ""),
2590
2610
  a => handlers.connect(a.url))
2591
2611
  .command(["listener", "listeners"], "Control who can connect: listeners, allowed profiles, keys, public URLs", y => y
2592
2612
  .command(["ls", "list", "$0"], "List listeners (keys hidden)", {}, () => handlers.listListeners())
@@ -2630,8 +2650,16 @@ export function rechCli(argv: string[], handlers: RechHandlers) {
2630
2650
  .demandCommand(1)
2631
2651
  .strict()
2632
2652
  .help()
2653
+ .alias("help", "h")
2633
2654
  .version(false)
2634
- .fail((message, error) => { throw error ?? new Error(message); });
2655
+ // A parse error (missing argument, unknown option…) shows that command's help above the
2656
+ // error, so the fix is visible. Errors thrown by a command's own handler pass through.
2657
+ .fail((message, error, y) => {
2658
+ if (error) throw error;
2659
+ // Straight to stderr: Bun's console.error would paint the whole help red.
2660
+ y.showHelp((help: string) => process.stderr.write(`${help}\n\n`));
2661
+ throw new Error(`rech: ${/^Not enough non-option arguments/.test(message) ? "missing a required argument; see the usage line above" : message}`);
2662
+ });
2635
2663
  }
2636
2664
 
2637
2665
  if (import.meta.main) {
package/rechrome.ts CHANGED
@@ -10,6 +10,7 @@ import { hostname, homedir, networkInterfaces } from "os";
10
10
  import { isIPv4 } from "net";
11
11
  import { join, basename, dirname } from "path";
12
12
  import { pathToFileURL } from "url";
13
+ import { createRequire } from "node:module";
13
14
  import { spawn as cpSpawn } from "child_process";
14
15
  import { readFile, writeFile, rename, chmod, mkdir } from "node:fs/promises";
15
16
  import { oxmgrInstallCommand, pickDaemonManager, type DaemonManager } from "./daemon-manager.ts";
@@ -1446,23 +1447,41 @@ async function pmList(mgr: DaemonManager = daemonManager()): Promise<string> {
1446
1447
  return await new Response(proc.stdout).text();
1447
1448
  }
1448
1449
 
1450
+ // A candidate playwright-cli entry is usable only if the playwright-core it requires actually
1451
+ // resolves FROM that entry — the wrapper does `require('playwright-core/lib/tools/cli-client/program')`,
1452
+ // a deep subpath reachable only through the fork's patched `exports` map. existsSync on the .js is
1453
+ // not the same check: an uninitialised/half-built lib/playwright-cli submodule leaves the wrapper on
1454
+ // disk with no resolvable core, and the failure surfaces later as MODULE_NOT_FOUND inside the daemon.
1455
+ export function playwrightCliIsUsable(jsEntry: string): boolean {
1456
+ try {
1457
+ createRequire(jsEntry).resolve("playwright-core/lib/tools/cli-client/program");
1458
+ return true;
1459
+ } catch {
1460
+ return false;
1461
+ }
1462
+ }
1463
+
1449
1464
  // Resolve which playwright-cli the daemon runs to drive Chrome. Priority:
1450
1465
  // 1. PLAYWRIGHT_CLI env override — explicit, already a full command string.
1451
1466
  // 2. Vendored fork in a git checkout (lib/playwright-cli/playwright-cli.js) — the patched
1452
1467
  // multi-tab CLI + patched playwright-core (PLAYWRIGHT_MCP_PROFILE_DIRECTORY etc.).
1453
1468
  // 3. The fork bundled into the npm tarball (vendor/playwright-cli/playwright-cli.js, produced by
1454
- // scripts/vendor-cli.sh at prepublish). This is the batteries-included default for
1455
- // `bun i -g rechrome`: self-contained, no @playwright/cli dep, no browser-binary download.
1469
+ // scripts/vendor-cli.sh at prepublish, and by `prepare` on `bun install` in a checkout). This
1470
+ // is the batteries-included default for `bun i -g rechrome`: self-contained, no
1471
+ // @playwright/cli dep, no browser-binary download.
1456
1472
  // 4. Bare `playwright-cli-multi-tab` on PATH — legacy fallback for a pre-existing global link.
1473
+ // Candidates 2–3 must also pass playwrightCliIsUsable(), so a present-but-broken one falls through.
1474
+ // lib/ stays ahead of vendor/ on purpose: a dev who built the fork wants their patched core, not
1475
+ // the (possibly older) vendor-src snapshot that `prepare` unpacks into vendor/.
1457
1476
  // A resolved .js entry is run through `node` on Windows (which can't exec a .js by shebang) and
1458
1477
  // bare on POSIX (its `#!/usr/bin/env node` shebang runs it under node, which the relay handshake
1459
1478
  // needs — see daemonInstall). serve splits the result on spaces into argv.
1460
- export function resolvePlaywrightCli(): string {
1479
+ export function resolvePlaywrightCli(root: string = import.meta.dir): string {
1461
1480
  if (process.env.PLAYWRIGHT_CLI) return process.env.PLAYWRIGHT_CLI;
1462
1481
  const jsEntry = [
1463
- join(import.meta.dir, "lib/playwright-cli/playwright-cli.js"),
1464
- join(import.meta.dir, "vendor/playwright-cli/playwright-cli.js"),
1465
- ].find(existsSync);
1482
+ join(root, "lib/playwright-cli/playwright-cli.js"),
1483
+ join(root, "vendor/playwright-cli/playwright-cli.js"),
1484
+ ].filter(existsSync).find(playwrightCliIsUsable);
1466
1485
  if (jsEntry) return IS_WINDOWS ? `node ${jsEntry}` : jsEntry;
1467
1486
  return "playwright-cli-multi-tab";
1468
1487
  }
@@ -2586,7 +2605,8 @@ export function rechCli(argv: string[], handlers: RechHandlers) {
2586
2605
  ? handlers.urlList()
2587
2606
  : handlers.printProfileUri(a.profile, a.listener, { local: a.local, save: a.save }))
2588
2607
  .command("connect <url>", "Use a URL from another machine in this project (checks it first)", y => y
2589
- .positional("url", { type: "string", demandOption: true }),
2608
+ .positional("url", { type: "string", demandOption: true, describe: "The URL printed by `rech url <profile>` on the machine with Chrome. Quote it: it contains #" })
2609
+ .example("rech connect 'https://host.example.ts.net/rechrome/?profile=you%40example.com#key=…'", ""),
2590
2610
  a => handlers.connect(a.url))
2591
2611
  .command(["listener", "listeners"], "Control who can connect: listeners, allowed profiles, keys, public URLs", y => y
2592
2612
  .command(["ls", "list", "$0"], "List listeners (keys hidden)", {}, () => handlers.listListeners())
@@ -2630,8 +2650,16 @@ export function rechCli(argv: string[], handlers: RechHandlers) {
2630
2650
  .demandCommand(1)
2631
2651
  .strict()
2632
2652
  .help()
2653
+ .alias("help", "h")
2633
2654
  .version(false)
2634
- .fail((message, error) => { throw error ?? new Error(message); });
2655
+ // A parse error (missing argument, unknown option…) shows that command's help above the
2656
+ // error, so the fix is visible. Errors thrown by a command's own handler pass through.
2657
+ .fail((message, error, y) => {
2658
+ if (error) throw error;
2659
+ // Straight to stderr: Bun's console.error would paint the whole help red.
2660
+ y.showHelp((help: string) => process.stderr.write(`${help}\n\n`));
2661
+ throw new Error(`rech: ${/^Not enough non-option arguments/.test(message) ? "missing a required argument; see the usage line above" : message}`);
2662
+ });
2635
2663
  }
2636
2664
 
2637
2665
  if (import.meta.main) {