@vercel/next-browser 0.1.8 → 0.3.0

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/dist/cli.js CHANGED
@@ -51,14 +51,49 @@ if (cmd === "reload") {
51
51
  const res = await send("reload");
52
52
  exit(res, res.ok ? `reloaded → ${res.data}` : "");
53
53
  }
54
- if (cmd === "capture-goto") {
55
- const res = await send("capture-goto", arg ? { url: arg } : {});
54
+ if (cmd === "perf") {
55
+ const res = await send("perf", arg ? { url: arg } : {});
56
56
  if (!res.ok)
57
57
  exit(res, "");
58
- const data = res.data;
59
- exit(res, `${data.frames} frames ${data.dir}\n` +
60
- "\n" +
61
- "frame-0000.png is the PPR shell. Remaining frames capture hydration → data.");
58
+ const d = res.data;
59
+ const lines = [`# Page Load Profile — ${d.url}`, ""];
60
+ // Core Web Vitals
61
+ lines.push("## Core Web Vitals");
62
+ const ttfbStr = d.ttfb != null ? `${d.ttfb}ms` : "—";
63
+ lines.push(` TTFB ${ttfbStr.padStart(10)}`);
64
+ if (d.lcp) {
65
+ const lcpLabel = d.lcp.element ? ` (${d.lcp.element}${d.lcp.url ? `: ${d.lcp.url.slice(0, 60)}` : ""})` : "";
66
+ lines.push(` LCP ${String(d.lcp.startTime + "ms").padStart(10)}${lcpLabel}`);
67
+ }
68
+ else {
69
+ lines.push(` LCP —`);
70
+ }
71
+ lines.push(` CLS ${String(d.cls.score).padStart(10)}`);
72
+ lines.push("");
73
+ // React Hydration
74
+ if (d.hydration) {
75
+ lines.push(`## React Hydration — ${d.hydration.duration}ms (${d.hydration.startTime}ms → ${d.hydration.endTime}ms)`);
76
+ }
77
+ else {
78
+ lines.push("## React Hydration — no data (requires profiling build)");
79
+ }
80
+ if (d.phases.length > 0) {
81
+ for (const p of d.phases) {
82
+ lines.push(` ${p.label.padEnd(28)} ${String(p.duration + "ms").padStart(10)} (${p.startTime} → ${p.endTime})`);
83
+ }
84
+ lines.push("");
85
+ }
86
+ if (d.hydratedComponents.length > 0) {
87
+ lines.push(`## Hydrated components (${d.hydratedComponents.length} total, sorted by duration)`);
88
+ const top = d.hydratedComponents.slice(0, 30);
89
+ for (const c of top) {
90
+ lines.push(` ${c.name.padEnd(40)} ${String(c.duration + "ms").padStart(10)}`);
91
+ }
92
+ if (d.hydratedComponents.length > 30) {
93
+ lines.push(` ... and ${d.hydratedComponents.length - 30} more`);
94
+ }
95
+ }
96
+ exit(res, lines.join("\n"));
62
97
  }
63
98
  if (cmd === "restart-server") {
64
99
  const res = await send("restart");
@@ -85,24 +120,84 @@ if (cmd === "goto") {
85
120
  const res = await send("goto", { url: arg });
86
121
  exit(res, res.ok ? `→ ${res.data}` : "");
87
122
  }
88
- if (cmd === "ssr-goto") {
89
- const res = await send("ssr-goto", { url: arg });
90
- exit(res, res.ok ? `→ ${res.data} (external scripts blocked)` : "");
123
+ if (cmd === "ssr" && arg === "lock") {
124
+ const res = await send("ssr-lock");
125
+ exit(res, "ssr locked external scripts blocked on all navigations");
126
+ }
127
+ if (cmd === "ssr" && arg === "unlock") {
128
+ const res = await send("ssr-unlock");
129
+ exit(res, "ssr unlocked — external scripts re-enabled");
91
130
  }
92
131
  if (cmd === "back") {
93
132
  const res = await send("back");
94
133
  exit(res, "back");
95
134
  }
135
+ if (cmd === "preview") {
136
+ const clear = args.includes("--clear");
137
+ const caption = args.filter((a) => a !== "--clear").slice(1).join(" ") || undefined;
138
+ const res = await send("preview", { caption, clear });
139
+ exit(res, res.ok ? `preview → ${res.data}` : "");
140
+ }
96
141
  if (cmd === "screenshot") {
97
- const res = await send("screenshot");
142
+ const fullPage = args.includes("--full-page");
143
+ const res = await send("screenshot", { fullPage });
98
144
  exit(res, res.ok ? String(res.data) : "");
99
145
  }
100
- if (cmd === "eval") {
146
+ if (cmd === "snapshot") {
147
+ const res = await send("snapshot");
148
+ exit(res, res.ok ? json(res.data) : "");
149
+ }
150
+ if (cmd === "click") {
101
151
  if (!arg) {
102
- console.error("usage: next-browser eval <script>");
152
+ console.error("usage: next-browser click <ref|text|selector>");
153
+ process.exit(1);
154
+ }
155
+ const res = await send("click", { selector: arg });
156
+ exit(res, "clicked");
157
+ }
158
+ if (cmd === "fill") {
159
+ const value = args[2];
160
+ if (!arg || value === undefined) {
161
+ console.error("usage: next-browser fill <ref|selector> <value>");
162
+ process.exit(1);
163
+ }
164
+ const res = await send("fill", { selector: arg, value });
165
+ exit(res, "filled");
166
+ }
167
+ if (cmd === "eval") {
168
+ // Check if first arg is a ref (e.g. "e3") — if so, second arg is the script
169
+ let ref;
170
+ let scriptArg = arg;
171
+ let fileArgIdx = 2;
172
+ if (arg && /^e\d+$/.test(arg)) {
173
+ ref = arg;
174
+ scriptArg = args[2];
175
+ fileArgIdx = 3;
176
+ }
177
+ let script;
178
+ if (scriptArg === "--file" || scriptArg === "-f") {
179
+ const filePath = args[fileArgIdx];
180
+ if (!filePath) {
181
+ console.error("usage: next-browser eval [ref] --file <path>");
182
+ process.exit(1);
183
+ }
184
+ script = readFileSync(filePath, "utf-8");
185
+ }
186
+ else if (scriptArg === "-") {
187
+ // Read from stdin
188
+ const chunks = [];
189
+ for await (const chunk of process.stdin)
190
+ chunks.push(chunk);
191
+ script = Buffer.concat(chunks).toString("utf-8");
192
+ }
193
+ else {
194
+ script = scriptArg;
195
+ }
196
+ if (!script) {
197
+ console.error("usage: next-browser eval [ref] <script>\n next-browser eval [ref] --file <path>\n echo 'script' | next-browser eval -");
103
198
  process.exit(1);
104
199
  }
105
- const res = await send("eval", { script: arg });
200
+ const res = await send("eval", { script, ...(ref ? { selector: ref } : {}) });
106
201
  exit(res, res.ok ? json(res.data) : "");
107
202
  }
108
203
  if (cmd === "tree") {
@@ -233,11 +328,12 @@ function printUsage() {
233
328
  " close close browser and daemon\n" +
234
329
  "\n" +
235
330
  " goto <url> full-page navigation (new document load)\n" +
236
- " ssr-goto <url> goto but block external scripts (SSR shell)\n" +
331
+ " ssr lock block external scripts on all navigations\n" +
332
+ " ssr unlock re-enable external scripts\n" +
237
333
  " push [path] client-side navigation (interactive picker if no path)\n" +
238
334
  " back go back in history\n" +
239
335
  " reload reload current page\n" +
240
- " capture-goto [url] capture loading sequence (PPR shell hydration → data)\n" +
336
+ " perf [url] profile page load (CWVs + React hydration timing)\n" +
241
337
  " restart-server restart the Next.js dev server (clears fs cache)\n" +
242
338
  "\n" +
243
339
  " ppr lock enter PPR instant-navigation mode\n" +
@@ -247,8 +343,14 @@ function printUsage() {
247
343
  " tree <id> inspect component (props, hooks, state, source)\n" +
248
344
  "\n" +
249
345
  " viewport [WxH] show or set viewport size (e.g. 1280x720)\n" +
250
- " screenshot save full-page screenshot to tmp file\n" +
251
- " eval <script> evaluate JS in page context\n" +
346
+ " preview [caption] [--clear] screenshot + open in viewer (accumulates)\n" +
347
+ " screenshot [--full-page] save screenshot to tmp file\n" +
348
+ " snapshot accessibility tree with interactive refs\n" +
349
+ " click <ref|sel> click an element (real pointer events)\n" +
350
+ " fill <ref|sel> <v> fill a text input\n" +
351
+ " eval [ref] <script> evaluate JS in page context\n" +
352
+ " eval --file <path> evaluate JS from a file\n" +
353
+ " eval - evaluate JS from stdin\n" +
252
354
  "\n" +
253
355
  " errors show build/runtime errors\n" +
254
356
  " logs show recent dev server log output\n" +
package/dist/daemon.js CHANGED
@@ -57,16 +57,20 @@ async function run(cmd) {
57
57
  const data = await browser.reload();
58
58
  return { ok: true, data };
59
59
  }
60
- if (cmd.action === "capture-goto") {
61
- const data = await browser.captureGoto(cmd.url);
60
+ if (cmd.action === "perf") {
61
+ const data = await browser.perf(cmd.url);
62
62
  return { ok: true, data };
63
63
  }
64
64
  if (cmd.action === "restart") {
65
65
  const data = await browser.restart();
66
66
  return { ok: true, data };
67
67
  }
68
+ if (cmd.action === "preview") {
69
+ const data = await browser.preview(cmd.caption, cmd.clear);
70
+ return { ok: true, data };
71
+ }
68
72
  if (cmd.action === "screenshot") {
69
- const data = await browser.screenshot();
73
+ const data = await browser.screenshot({ fullPage: cmd.fullPage });
70
74
  return { ok: true, data };
71
75
  }
72
76
  if (cmd.action === "links") {
@@ -81,9 +85,13 @@ async function run(cmd) {
81
85
  const data = await browser.goto(cmd.url);
82
86
  return { ok: true, data };
83
87
  }
84
- if (cmd.action === "ssr-goto") {
85
- const data = await browser.ssrGoto(cmd.url);
86
- return { ok: true, data };
88
+ if (cmd.action === "ssr-lock") {
89
+ await browser.ssrLock();
90
+ return { ok: true };
91
+ }
92
+ if (cmd.action === "ssr-unlock") {
93
+ await browser.ssrUnlock();
94
+ return { ok: true };
87
95
  }
88
96
  if (cmd.action === "back") {
89
97
  await browser.back();
@@ -97,8 +105,20 @@ async function run(cmd) {
97
105
  const data = await browser.node(cmd.nodeId);
98
106
  return { ok: true, data };
99
107
  }
108
+ if (cmd.action === "snapshot") {
109
+ const data = await browser.snapshot();
110
+ return { ok: true, data };
111
+ }
112
+ if (cmd.action === "click") {
113
+ await browser.click(cmd.selector);
114
+ return { ok: true };
115
+ }
116
+ if (cmd.action === "fill") {
117
+ await browser.fill(cmd.selector, cmd.value);
118
+ return { ok: true };
119
+ }
100
120
  if (cmd.action === "eval") {
101
- const data = await browser.evaluate(cmd.script);
121
+ const data = await browser.evaluate(cmd.script, cmd.selector);
102
122
  return { ok: true, data };
103
123
  }
104
124
  if (cmd.action === "mcp") {
package/dist/paths.js CHANGED
@@ -2,5 +2,7 @@ import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
3
  const dir = join(homedir(), ".next-browser");
4
4
  export const socketDir = dir;
5
- export const socketPath = join(dir, "default.sock");
5
+ export const socketPath = process.platform === "win32"
6
+ ? "//./pipe/next-browser-default"
7
+ : join(dir, "default.sock");
6
8
  export const pidFile = join(dir, "default.pid");
package/dist/suspense.js CHANGED
@@ -124,61 +124,46 @@ export function formatReport(report) {
124
124
  }
125
125
  lines.push("");
126
126
  }
127
- lines.push("## Dynamic holes (suspended in shell)");
128
- for (const hole of report.holes) {
129
- const name = hole.name ?? "(unnamed)";
130
- const src = hole.source ? `${hole.source[0]}:${hole.source[1]}:${hole.source[2]}` : null;
131
- lines.push(` ${name}${src ? ` at ${src}` : ""}`);
132
- if (hole.renderedBy.length > 0) {
133
- lines.push(` rendered by: ${hole.renderedBy.map((o) => {
134
- const env = o.env ? ` [${o.env}]` : "";
135
- const src = o.source ? ` at ${o.source[0]}:${o.source[1]}` : "";
136
- return `${o.name}${env}${src}`;
137
- }).join(" > ")}`);
138
- }
139
- if (hole.environments.length > 0)
140
- lines.push(` environments: ${hole.environments.join(", ")}`);
141
- if (hole.primaryBlocker) {
142
- lines.push(` primary blocker: ${hole.primaryBlocker.name} ` +
143
- `(${hole.primaryBlocker.kind}, actionability ${labelActionability(hole.primaryBlocker.actionability)})`);
144
- if (hole.fallbackSource.path) {
145
- lines.push(` fallback source: ${hole.fallbackSource.path} ` +
146
- `(${hole.fallbackSource.confidence} confidence)`);
147
- }
148
- if (hole.primaryBlocker.sourceFrame) {
149
- lines.push(` source: ${hole.primaryBlocker.sourceFrame[0] || "(anonymous)"} ` +
150
- `${hole.primaryBlocker.sourceFrame[1]}:${hole.primaryBlocker.sourceFrame[2]}`);
127
+ // Detail section only shows info NOT already in the Quick Reference table:
128
+ // owner chains, environment tags, secondary blockers, and stack frames.
129
+ const holesWithDetail = report.holes.filter((h) => h.renderedBy.length > 0 || h.environments.length > 0 || h.blockers.length > 1 || h.unknownSuspenders);
130
+ if (holesWithDetail.length > 0) {
131
+ lines.push("## Detail");
132
+ for (const hole of holesWithDetail) {
133
+ const name = hole.name ?? "(unnamed)";
134
+ lines.push(` ${name}`);
135
+ if (hole.renderedBy.length > 0) {
136
+ lines.push(` rendered by: ${hole.renderedBy.map((o) => {
137
+ const env = o.env ? ` [${o.env}]` : "";
138
+ const src = o.source ? ` at ${o.source[0]}:${o.source[1]}` : "";
139
+ return `${o.name}${env}${src}`;
140
+ }).join(" > ")}`);
151
141
  }
152
- lines.push(` next step: ${hole.recommendation}`);
153
- }
154
- if (hole.blockers.length > 0) {
155
- lines.push(" blocked by:");
156
- for (const blocker of hole.blockers) {
157
- const dur = hole.primaryBlocker?.name === blocker.name ? " [primary]" : "";
158
- const env = blocker.env ? ` [${blocker.env}]` : "";
159
- const owner = blocker.ownerName ? ` initiated by <${blocker.ownerName}>` : "";
160
- const awaiter = blocker.awaiterName ? ` awaited in <${blocker.awaiterName}>` : "";
161
- lines.push(` - ${blocker.name}: ${blocker.description || "(no description)"}${env}${dur}${owner}${awaiter}`);
162
- if (blocker.ownerFrame) {
163
- const [fn, file, line] = blocker.ownerFrame;
164
- lines.push(` owner: ${fn || "(anonymous)"} ${file}:${line}`);
165
- }
166
- if (blocker.awaiterFrame && !blocker.ownerFrame) {
167
- const [fn, file, line] = blocker.awaiterFrame;
168
- lines.push(` awaiter: ${fn || "(anonymous)"} ${file}:${line}`);
169
- }
170
- if (blocker.ownerFrame && hole.primaryBlocker?.name === blocker.name) {
171
- for (const [fn, file, line] of [blocker.ownerFrame].slice(0, 3)) {
172
- lines.push(` at ${fn || "(anonymous)"} ${file}:${line}`);
142
+ if (hole.environments.length > 0)
143
+ lines.push(` environments: ${hole.environments.join(", ")}`);
144
+ if (hole.blockers.length > 1) {
145
+ lines.push(" secondary blockers:");
146
+ for (const blocker of hole.blockers.slice(1)) {
147
+ const env = blocker.env ? ` [${blocker.env}]` : "";
148
+ const owner = blocker.ownerName ? ` initiated by <${blocker.ownerName}>` : "";
149
+ const awaiter = blocker.awaiterName ? ` awaited in <${blocker.awaiterName}>` : "";
150
+ lines.push(` - ${blocker.name}: ${blocker.description || "(no description)"}${env}${owner}${awaiter}`);
151
+ if (blocker.ownerFrame) {
152
+ const [fn, file, line] = blocker.ownerFrame;
153
+ lines.push(` owner: ${fn || "(anonymous)"} ${file}:${line}`);
154
+ }
155
+ else if (blocker.awaiterFrame) {
156
+ const [fn, file, line] = blocker.awaiterFrame;
157
+ lines.push(` awaiter: ${fn || "(anonymous)"} ${file}:${line}`);
173
158
  }
174
159
  }
175
160
  }
161
+ if (hole.unknownSuspenders) {
162
+ lines.push(` suspenders unknown: ${hole.unknownSuspenders}`);
163
+ }
176
164
  }
177
- else if (hole.unknownSuspenders) {
178
- lines.push(` suspenders unknown: ${hole.unknownSuspenders}`);
179
- }
165
+ lines.push("");
180
166
  }
181
- lines.push("");
182
167
  }
183
168
  if (report.statics.length > 0) {
184
169
  lines.push("## Static (pre-rendered in shell)");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/next-browser",
3
- "version": "0.1.8",
3
+ "version": "0.3.0",
4
4
  "description": "Headed Playwright browser with React DevTools pre-loaded",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,20 +21,23 @@
21
21
  "bin": {
22
22
  "next-browser": "./dist/cli.js"
23
23
  },
24
- "scripts": {
25
- "start": "node src/cli.ts",
26
- "typecheck": "tsc",
27
- "build": "tsc -p tsconfig.build.json",
28
- "prepack": "pnpm build"
29
- },
30
24
  "dependencies": {
31
25
  "@next/playwright": "16.2.0-canary.80",
32
26
  "playwright": "^1.50.0",
33
27
  "source-map-js": "^1.2.1"
34
28
  },
35
- "packageManager": "pnpm@10.29.2",
36
29
  "devDependencies": {
30
+ "@changesets/changelog-github": "^0.6.0",
31
+ "@changesets/cli": "^2.30.0",
37
32
  "@types/node": "^22.0.0",
38
33
  "typescript": "^5.7.0"
34
+ },
35
+ "scripts": {
36
+ "start": "node src/cli.ts",
37
+ "typecheck": "tsc",
38
+ "build": "tsc -p tsconfig.build.json",
39
+ "changeset": "changeset",
40
+ "version": "changeset version",
41
+ "release": "pnpm build && changeset publish"
39
42
  }
40
- }
43
+ }
@@ -1,72 +0,0 @@
1
- import { connect as netConnect } from "node:net";
2
- import { readFileSync, existsSync, rmSync } from "node:fs";
3
- import { spawn } from "node:child_process";
4
- import { setTimeout as sleep } from "node:timers/promises";
5
- import { fileURLToPath } from "node:url";
6
- import { cloudSocketPath, cloudPidFile } from "./cloud-paths.js";
7
- export async function cloudSend(action, payload = {}) {
8
- await ensureCloudDaemon();
9
- const socket = await connect();
10
- const id = String(Date.now());
11
- socket.write(JSON.stringify({ id, action, ...payload }) + "\n");
12
- const line = await readLine(socket);
13
- socket.end();
14
- return JSON.parse(line);
15
- }
16
- async function ensureCloudDaemon() {
17
- if (daemonAlive() && (await connect().then(ok, no)))
18
- return;
19
- const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
20
- const daemon = fileURLToPath(new URL(`./cloud-daemon${ext}`, import.meta.url));
21
- const child = spawn(process.execPath, [daemon], {
22
- detached: true,
23
- stdio: "ignore",
24
- });
25
- child.unref();
26
- for (let i = 0; i < 50; i++) {
27
- if (await connect().then(ok, no))
28
- return;
29
- await sleep(100);
30
- }
31
- throw new Error(`cloud daemon failed to start (${cloudSocketPath})`);
32
- }
33
- function daemonAlive() {
34
- if (!existsSync(cloudPidFile))
35
- return false;
36
- const pid = Number(readFileSync(cloudPidFile, "utf-8"));
37
- try {
38
- process.kill(pid, 0);
39
- return true;
40
- }
41
- catch {
42
- rmSync(cloudPidFile, { force: true });
43
- rmSync(cloudSocketPath, { force: true });
44
- return false;
45
- }
46
- }
47
- function connect() {
48
- return new Promise((resolve, reject) => {
49
- const socket = netConnect(cloudSocketPath);
50
- socket.once("connect", () => resolve(socket));
51
- socket.once("error", reject);
52
- });
53
- }
54
- function readLine(socket) {
55
- return new Promise((resolve, reject) => {
56
- let buffer = "";
57
- socket.on("data", (chunk) => {
58
- buffer += chunk;
59
- const newline = buffer.indexOf("\n");
60
- if (newline >= 0)
61
- resolve(buffer.slice(0, newline));
62
- });
63
- socket.on("error", reject);
64
- });
65
- }
66
- function ok(s) {
67
- s.destroy();
68
- return true;
69
- }
70
- function no() {
71
- return false;
72
- }
@@ -1,87 +0,0 @@
1
- import { createServer } from "node:net";
2
- import { mkdirSync, writeFileSync, rmSync } from "node:fs";
3
- import * as cloud from "./cloud.js";
4
- import { cloudSocketDir, cloudSocketPath, cloudPidFile } from "./cloud-paths.js";
5
- mkdirSync(cloudSocketDir, { recursive: true, mode: 0o700 });
6
- rmSync(cloudSocketPath, { force: true });
7
- rmSync(cloudPidFile, { force: true });
8
- writeFileSync(cloudPidFile, String(process.pid));
9
- const server = createServer((socket) => {
10
- let buffer = "";
11
- socket.on("data", (chunk) => {
12
- buffer += chunk;
13
- let newline;
14
- while ((newline = buffer.indexOf("\n")) >= 0) {
15
- const line = buffer.slice(0, newline);
16
- buffer = buffer.slice(newline + 1);
17
- if (line)
18
- dispatch(line, socket);
19
- }
20
- });
21
- socket.on("error", () => { });
22
- });
23
- server.listen(cloudSocketPath);
24
- process.on("SIGINT", shutdown);
25
- process.on("SIGTERM", shutdown);
26
- process.on("exit", cleanup);
27
- async function dispatch(line, socket) {
28
- const cmd = JSON.parse(line);
29
- const result = await run(cmd).catch((err) => ({
30
- ok: false,
31
- error: err.message,
32
- }));
33
- socket.write(JSON.stringify({ id: cmd.id, ...result }) + "\n");
34
- if (cmd.action === "destroy")
35
- setImmediate(shutdown);
36
- }
37
- async function run(cmd) {
38
- if (cmd.action === "create") {
39
- const data = await cloud.create();
40
- return { ok: true, data };
41
- }
42
- if (cmd.action === "exec") {
43
- if (!cmd.command)
44
- return { ok: false, error: "missing command" };
45
- const result = await cloud.exec(cmd.command);
46
- const output = [
47
- result.stdout,
48
- result.stderr ? `stderr:\n${result.stderr}` : "",
49
- result.exitCode !== 0 ? `exit code: ${result.exitCode}` : "",
50
- ]
51
- .filter(Boolean)
52
- .join("\n");
53
- if (result.exitCode === 0)
54
- return { ok: true, data: output };
55
- return { ok: false, error: output || `exit code: ${result.exitCode}` };
56
- }
57
- if (cmd.action === "status") {
58
- const data = await cloud.status();
59
- return { ok: true, data };
60
- }
61
- if (cmd.action === "upload") {
62
- if (!cmd.localPath || !cmd.remotePath)
63
- return { ok: false, error: "missing localPath or remotePath" };
64
- const data = await cloud.upload(cmd.localPath, cmd.remotePath);
65
- return { ok: true, data };
66
- }
67
- if (cmd.action === "destroy") {
68
- const data = await cloud.destroy();
69
- return { ok: true, data };
70
- }
71
- return { ok: false, error: `unknown action: ${cmd.action}` };
72
- }
73
- async function shutdown() {
74
- try {
75
- await cloud.destroy();
76
- }
77
- catch {
78
- // best effort
79
- }
80
- server.close();
81
- cleanup();
82
- process.exit(0);
83
- }
84
- function cleanup() {
85
- rmSync(cloudSocketPath, { force: true });
86
- rmSync(cloudPidFile, { force: true });
87
- }
@@ -1,7 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
- const dir = join(homedir(), ".next-browser");
4
- export const cloudSocketDir = dir;
5
- export const cloudSocketPath = join(dir, "cloud.sock");
6
- export const cloudPidFile = join(dir, "cloud.pid");
7
- export const cloudStateFile = join(dir, "cloud.json");