@vincentt-xr/cli 0.1.1 → 0.1.2

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.
@@ -16,7 +16,9 @@ export interface StartPreviewOptions {
16
16
  /** stdio for the app dev serve. "ignore" (default) keeps an MCP server's stdout
17
17
  * clean; a CLI can pass "inherit" to surface build output. */
18
18
  appStdio?: "ignore" | "inherit";
19
- /** How long to wait for cloudflared to register before giving up (default 45s). */
19
+ /** How long to wait for cloudflared to register before giving up (default 90s).
20
+ * Generous because on a network that blocks the QUIC edge, cloudflared spends
21
+ * ~20-30s failing QUIC before it falls back to the http2 edge, which does work. */
20
22
  registerTimeoutMs?: number;
21
23
  /** How long to wait for the app to serve before returning anyway (default 8s).
22
24
  * Kept short so an MCP tool call stays within strict clients' ~30s timeout;
@@ -40,6 +42,9 @@ export interface RunningPreview {
40
42
  export declare function startPreview(opts: StartPreviewOptions): Promise<RunningPreview>;
41
43
  /**
42
44
  * Resolve once cloudflared reports a live edge connection, reject if it exits
43
- * first or never registers. Exported for unit testing the log-scan/timeout logic.
45
+ * first or never registers. On failure the rejection carries cloudflared's own
46
+ * recent output — otherwise "did not register" hides the real cause (an invalid
47
+ * run token, or a network that blocks the QUIC edge, the two common culprits).
48
+ * `onLine` streams each log line out live. Exported for unit testing.
44
49
  */
45
- export declare function waitForRegister(tunnel: ChildProcess, timeoutMs: number): Promise<void>;
50
+ export declare function waitForRegister(tunnel: ChildProcess, timeoutMs: number, onLine?: (line: string) => void): Promise<void>;
@@ -16,7 +16,7 @@ import { startSessionTunnel } from "./tunnel.js";
16
16
  import { proxyWeb, proxyWs } from "./proxy.js";
17
17
  import { getFreePort, isPortListening, waitForApp } from "./net.js";
18
18
  export async function startPreview(opts) {
19
- const { projectCwd, harnessPath = "/__harness", devCommand = ["npm", "run", "dev"], appStdio = "ignore", registerTimeoutMs = 45_000, appReadyTimeoutMs = 8_000, onLog = () => undefined, } = opts;
19
+ const { projectCwd, harnessPath = "/__harness", devCommand = ["npm", "run", "dev"], appStdio = "ignore", registerTimeoutMs = 90_000, appReadyTimeoutMs = 8_000, onLog = () => undefined, } = opts;
20
20
  // Resolve ports. The app port defaults to 5173; the relay + front ports are
21
21
  // harness-internal, so auto-pick free ones (the relay port is returned for the
22
22
  // MCP diag_* tools to point at) instead of colliding on fixed defaults.
@@ -70,7 +70,7 @@ export async function startPreview(opts) {
70
70
  tunnel = spawn(cloudflaredBin, ["tunnel", "run", "--token", session.runToken], {
71
71
  stdio: ["ignore", "pipe", "pipe"],
72
72
  });
73
- await waitForRegister(tunnel, registerTimeoutMs);
73
+ await waitForRegister(tunnel, registerTimeoutMs, (line) => onLog(`[cloudflared] ${line}`));
74
74
  // Don't hand back a live URL that serves a blank page: wait for the app to
75
75
  // actually respond. Non-fatal — a slow build still returns, flagged not-ready.
76
76
  const appReady = reuseApp ? true : await waitForApp(appPort, appReadyTimeoutMs);
@@ -86,11 +86,15 @@ export async function startPreview(opts) {
86
86
  }
87
87
  /**
88
88
  * Resolve once cloudflared reports a live edge connection, reject if it exits
89
- * first or never registers. Exported for unit testing the log-scan/timeout logic.
89
+ * first or never registers. On failure the rejection carries cloudflared's own
90
+ * recent output — otherwise "did not register" hides the real cause (an invalid
91
+ * run token, or a network that blocks the QUIC edge, the two common culprits).
92
+ * `onLine` streams each log line out live. Exported for unit testing.
90
93
  */
91
- export function waitForRegister(tunnel, timeoutMs) {
94
+ export function waitForRegister(tunnel, timeoutMs, onLine) {
92
95
  return new Promise((resolve, reject) => {
93
96
  let settled = false;
97
+ const recent = [];
94
98
  const finish = (fn) => {
95
99
  if (settled)
96
100
  return;
@@ -98,14 +102,25 @@ export function waitForRegister(tunnel, timeoutMs) {
98
102
  clearTimeout(timer);
99
103
  fn();
100
104
  };
105
+ const tail = () => {
106
+ const lines = recent.slice(-15).join("\n").trim();
107
+ return lines ? `\ncloudflared output:\n${lines}` : "";
108
+ };
101
109
  const scan = (buf) => {
102
- if (/Registered tunnel connection|Connection [^ ]+ registered/.test(String(buf))) {
110
+ const text = String(buf);
111
+ for (const line of text.split(/\r?\n/)) {
112
+ if (!line.trim())
113
+ continue;
114
+ recent.push(line);
115
+ onLine?.(line);
116
+ }
117
+ if (/Registered tunnel connection|Connection [^ ]+ registered/.test(text)) {
103
118
  finish(resolve);
104
119
  }
105
120
  };
106
121
  tunnel.stdout?.on("data", scan);
107
122
  tunnel.stderr?.on("data", scan);
108
- tunnel.on("exit", (code) => finish(() => reject(new Error(`cloudflared exited before registering (code ${code}).`))));
109
- const timer = setTimeout(() => finish(() => reject(new Error(`cloudflared did not register within ${timeoutMs}ms.`))), timeoutMs);
123
+ tunnel.on("exit", (code) => finish(() => reject(new Error(`cloudflared exited before registering (code ${code}).${tail()}`))));
124
+ const timer = setTimeout(() => finish(() => reject(new Error(`cloudflared did not register within ${timeoutMs}ms.${tail()}`))), timeoutMs);
110
125
  });
111
126
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincentt-xr/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Vincentt CLI — the vincentt command-line dev loop (login, create, publish, preview, diagnostics, feedback, relay, mcp)",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",