@vercel/next-browser 0.1.5 → 0.1.6
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/browser.js +12 -2
- package/dist/cloud-daemon.js +10 -2
- package/dist/cloud.js +67 -21
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -28,6 +28,7 @@ const installHook = readFileSync(join(extensionPath, "build", "installHook.js"),
|
|
|
28
28
|
let context = null;
|
|
29
29
|
let page = null;
|
|
30
30
|
let profileDirPath = null;
|
|
31
|
+
let initialOrigin = null;
|
|
31
32
|
// ── Browser lifecycle ────────────────────────────────────────────────────────
|
|
32
33
|
/**
|
|
33
34
|
* Launch the browser (if not already open) and optionally navigate to a URL.
|
|
@@ -41,6 +42,7 @@ export async function open(url) {
|
|
|
41
42
|
net.attach(page);
|
|
42
43
|
}
|
|
43
44
|
if (url) {
|
|
45
|
+
initialOrigin = new URL(url).origin;
|
|
44
46
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
45
47
|
}
|
|
46
48
|
}
|
|
@@ -67,6 +69,7 @@ export async function close() {
|
|
|
67
69
|
const { rmSync } = await import("node:fs");
|
|
68
70
|
rmSync(profileDirPath, { recursive: true, force: true });
|
|
69
71
|
profileDirPath = null;
|
|
72
|
+
initialOrigin = null;
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
// ── PPR lock/unlock ──────────────────────────────────────────────────────────
|
|
@@ -336,6 +339,7 @@ export async function goto(url) {
|
|
|
336
339
|
if (!page)
|
|
337
340
|
throw new Error("browser not open");
|
|
338
341
|
const target = new URL(url, page.url()).href;
|
|
342
|
+
initialOrigin = new URL(target).origin;
|
|
339
343
|
await page.goto(target, { waitUntil: "domcontentloaded" });
|
|
340
344
|
return target;
|
|
341
345
|
}
|
|
@@ -408,11 +412,17 @@ export async function evaluate(script) {
|
|
|
408
412
|
throw new Error("browser not open");
|
|
409
413
|
return page.evaluate(script);
|
|
410
414
|
}
|
|
411
|
-
/**
|
|
415
|
+
/**
|
|
416
|
+
* Call a Next.js dev server MCP tool (JSON-RPC over SSE at /_next/mcp).
|
|
417
|
+
*
|
|
418
|
+
* Uses the initial navigation origin (before any proxy redirects) rather than
|
|
419
|
+
* the current page origin. This handles microfrontends proxies that redirect
|
|
420
|
+
* e.g. localhost:3332 -> localhost:3024 but don't forward /_next/mcp.
|
|
421
|
+
*/
|
|
412
422
|
export async function mcp(tool, args) {
|
|
413
423
|
if (!page)
|
|
414
424
|
throw new Error("browser not open");
|
|
415
|
-
const origin = new URL(page.url()).origin;
|
|
425
|
+
const origin = initialOrigin ?? new URL(page.url()).origin;
|
|
416
426
|
return nextMcp.call(origin, tool, args);
|
|
417
427
|
}
|
|
418
428
|
/** Get network request log, or detail for a specific request index. */
|
package/dist/cloud-daemon.js
CHANGED
|
@@ -43,19 +43,27 @@ async function run(cmd) {
|
|
|
43
43
|
if (!cmd.command)
|
|
44
44
|
return { ok: false, error: "missing command" };
|
|
45
45
|
const result = await cloud.exec(cmd.command);
|
|
46
|
-
const
|
|
46
|
+
const output = [
|
|
47
47
|
result.stdout,
|
|
48
48
|
result.stderr ? `stderr:\n${result.stderr}` : "",
|
|
49
49
|
result.exitCode !== 0 ? `exit code: ${result.exitCode}` : "",
|
|
50
50
|
]
|
|
51
51
|
.filter(Boolean)
|
|
52
52
|
.join("\n");
|
|
53
|
-
|
|
53
|
+
if (result.exitCode === 0)
|
|
54
|
+
return { ok: true, data: output };
|
|
55
|
+
return { ok: false, error: output || `exit code: ${result.exitCode}` };
|
|
54
56
|
}
|
|
55
57
|
if (cmd.action === "status") {
|
|
56
58
|
const data = await cloud.status();
|
|
57
59
|
return { ok: true, data };
|
|
58
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
|
+
}
|
|
59
67
|
if (cmd.action === "destroy") {
|
|
60
68
|
const data = await cloud.destroy();
|
|
61
69
|
return { ok: true, data };
|
package/dist/cloud.js
CHANGED
|
@@ -7,7 +7,6 @@ import { dirname, join, resolve } from "node:path";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { cloudStateFile } from "./cloud-paths.js";
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
// Dynamic import so the main CLI doesn't fail if @vercel/sandbox isn't installed
|
|
11
10
|
async function getSandboxSDK() {
|
|
12
11
|
try {
|
|
13
12
|
return await import("@vercel/sandbox");
|
|
@@ -32,14 +31,37 @@ function loadState() {
|
|
|
32
31
|
function clearState() {
|
|
33
32
|
rmSync(cloudStateFile, { force: true });
|
|
34
33
|
}
|
|
35
|
-
// Keep the live Sandbox instance in memory (daemon process)
|
|
36
34
|
let sandbox = null;
|
|
35
|
+
/** System dependencies required for headless Chromium on Amazon Linux 2023. */
|
|
36
|
+
const CHROME_SYSTEM_DEPS = [
|
|
37
|
+
"nspr", "nss", "atk", "at-spi2-atk", "cups-libs", "libdrm",
|
|
38
|
+
"libxkbcommon", "libXcomposite", "libXdamage", "libXfixes",
|
|
39
|
+
"libXrandr", "mesa-libgbm", "alsa-lib", "cairo", "pango",
|
|
40
|
+
"glib2", "gtk3", "libX11", "libXext", "libXcursor", "libXi", "libXtst",
|
|
41
|
+
];
|
|
42
|
+
async function runInSandbox(cmd) {
|
|
43
|
+
if (!sandbox)
|
|
44
|
+
throw new Error("no sandbox");
|
|
45
|
+
const result = await sandbox.runCommand({
|
|
46
|
+
cmd: "bash",
|
|
47
|
+
args: ["-lc", cmd],
|
|
48
|
+
});
|
|
49
|
+
let stdout = "";
|
|
50
|
+
let stderr = "";
|
|
51
|
+
for await (const log of result.logs()) {
|
|
52
|
+
if (log.stream === "stdout")
|
|
53
|
+
stdout += log.data;
|
|
54
|
+
else
|
|
55
|
+
stderr += log.data;
|
|
56
|
+
}
|
|
57
|
+
await result.wait();
|
|
58
|
+
return { exitCode: result.exitCode, stdout, stderr };
|
|
59
|
+
}
|
|
37
60
|
export async function create() {
|
|
38
61
|
if (sandbox) {
|
|
39
62
|
return `sandbox already running: ${sandbox.sandboxId}`;
|
|
40
63
|
}
|
|
41
64
|
loadEnv();
|
|
42
|
-
// Check for existing state (previous daemon)
|
|
43
65
|
const existing = loadState();
|
|
44
66
|
if (existing) {
|
|
45
67
|
try {
|
|
@@ -50,14 +72,14 @@ export async function create() {
|
|
|
50
72
|
}
|
|
51
73
|
}
|
|
52
74
|
catch {
|
|
53
|
-
// stale state
|
|
75
|
+
// stale state
|
|
54
76
|
}
|
|
55
77
|
clearState();
|
|
56
78
|
}
|
|
57
79
|
const { Sandbox } = await getSandboxSDK();
|
|
58
80
|
sandbox = await Sandbox.create({
|
|
59
|
-
resources: { vcpus:
|
|
60
|
-
timeout:
|
|
81
|
+
resources: { vcpus: 8 },
|
|
82
|
+
timeout: 18_000_000, // 5 hours (max for Pro/Enterprise)
|
|
61
83
|
ports: [3000],
|
|
62
84
|
runtime: "node22",
|
|
63
85
|
});
|
|
@@ -73,6 +95,36 @@ export async function create() {
|
|
|
73
95
|
// no public URL yet
|
|
74
96
|
}
|
|
75
97
|
saveState(state);
|
|
98
|
+
// ── Full environment setup ───────────────────────────────────
|
|
99
|
+
// 1. Chrome system deps (headless Chromium on Amazon Linux 2023)
|
|
100
|
+
await runInSandbox(`sudo dnf install -y ${CHROME_SYSTEM_DEPS.join(" ")} > /dev/null 2>&1`);
|
|
101
|
+
// 2. next-browser + Playwright Chromium
|
|
102
|
+
await runInSandbox(`npm install -g @vercel/next-browser 2>&1 | tail -1`);
|
|
103
|
+
await runInSandbox(`npm install -g playwright @playwright/browser-chromium 2>&1 | tail -1`);
|
|
104
|
+
await runInSandbox(`npx playwright install chromium 2>&1 | tail -1`);
|
|
105
|
+
// 3. SSH key (for private git repos)
|
|
106
|
+
const { homedir } = await import("node:os");
|
|
107
|
+
const sshKeyPath = join(homedir(), ".ssh", "id_ed25519");
|
|
108
|
+
if (existsSync(sshKeyPath)) {
|
|
109
|
+
const key = readFileSync(sshKeyPath, "utf-8");
|
|
110
|
+
await runInSandbox(`mkdir -p ~/.ssh && chmod 700 ~/.ssh`);
|
|
111
|
+
await sandbox.writeFiles([
|
|
112
|
+
{ path: "/home/vercel-sandbox/.ssh/id_ed25519", content: Buffer.from(key) },
|
|
113
|
+
]);
|
|
114
|
+
await runInSandbox(`chmod 600 ~/.ssh/id_ed25519`);
|
|
115
|
+
await runInSandbox(`ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null`);
|
|
116
|
+
}
|
|
117
|
+
// 4. npmrc (for private packages)
|
|
118
|
+
const npmrcPath = join(homedir(), ".npmrc");
|
|
119
|
+
if (existsSync(npmrcPath)) {
|
|
120
|
+
const npmrc = readFileSync(npmrcPath, "utf-8");
|
|
121
|
+
await sandbox.writeFiles([
|
|
122
|
+
{ path: "/home/vercel-sandbox/.npmrc", content: Buffer.from(npmrc) },
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
// 5. Node 24 via nvm (many projects require it)
|
|
126
|
+
await runInSandbox(`curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash > /dev/null 2>&1`);
|
|
127
|
+
await runInSandbox(`source ~/.nvm/nvm.sh && nvm install 24 > /dev/null 2>&1`);
|
|
76
128
|
return [
|
|
77
129
|
`sandbox created: ${sandbox.sandboxId}`,
|
|
78
130
|
state.publicUrl ? `url: ${state.publicUrl}` : null,
|
|
@@ -83,20 +135,15 @@ export async function create() {
|
|
|
83
135
|
export async function exec(command) {
|
|
84
136
|
if (!sandbox)
|
|
85
137
|
throw new Error("no sandbox running — run `cloud create` first");
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
else
|
|
96
|
-
stderr += log.data;
|
|
97
|
-
}
|
|
98
|
-
await result.wait();
|
|
99
|
-
return { exitCode: result.exitCode, stdout, stderr };
|
|
138
|
+
return runInSandbox(command);
|
|
139
|
+
}
|
|
140
|
+
/** Upload a local file to the sandbox. */
|
|
141
|
+
export async function upload(localPath, remotePath) {
|
|
142
|
+
if (!sandbox)
|
|
143
|
+
throw new Error("no sandbox running — run `cloud create` first");
|
|
144
|
+
const content = readFileSync(localPath);
|
|
145
|
+
await sandbox.writeFiles([{ path: remotePath, content }]);
|
|
146
|
+
return `uploaded ${localPath} → ${remotePath} (${content.length} bytes)`;
|
|
100
147
|
}
|
|
101
148
|
export async function destroy() {
|
|
102
149
|
if (!sandbox) {
|
|
@@ -147,7 +194,6 @@ export async function status() {
|
|
|
147
194
|
}
|
|
148
195
|
/**
|
|
149
196
|
* Load .env.local for Vercel credentials.
|
|
150
|
-
* Searches from package directory and common locations.
|
|
151
197
|
*/
|
|
152
198
|
function loadEnv() {
|
|
153
199
|
const candidates = [
|