@vercel/next-browser 0.1.8 → 0.2.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/cloud.js DELETED
@@ -1,230 +0,0 @@
1
- /**
2
- * Cloud sandbox lifecycle and operations.
3
- * Wraps @vercel/sandbox SDK for creating/managing remote sandboxes.
4
- */
5
- import { readFileSync, writeFileSync, existsSync, rmSync } from "node:fs";
6
- import { dirname, join, resolve } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
- import { cloudStateFile } from "./cloud-paths.js";
9
- const __dirname = dirname(fileURLToPath(import.meta.url));
10
- async function getSandboxSDK() {
11
- try {
12
- return await import("@vercel/sandbox");
13
- }
14
- catch {
15
- throw new Error("@vercel/sandbox is not installed. Run: pnpm add @vercel/sandbox");
16
- }
17
- }
18
- function saveState(state) {
19
- writeFileSync(cloudStateFile, JSON.stringify(state, null, 2));
20
- }
21
- function loadState() {
22
- if (!existsSync(cloudStateFile))
23
- return null;
24
- try {
25
- return JSON.parse(readFileSync(cloudStateFile, "utf-8"));
26
- }
27
- catch {
28
- return null;
29
- }
30
- }
31
- function clearState() {
32
- rmSync(cloudStateFile, { force: true });
33
- }
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
- }
60
- export async function create() {
61
- if (sandbox) {
62
- return `sandbox already running: ${sandbox.sandboxId}`;
63
- }
64
- loadEnv();
65
- const existing = loadState();
66
- if (existing) {
67
- try {
68
- const { Sandbox } = await getSandboxSDK();
69
- sandbox = await Sandbox.get({ sandboxId: existing.sandboxId });
70
- if (sandbox.status === "running") {
71
- return `reconnected to ${sandbox.sandboxId}`;
72
- }
73
- }
74
- catch {
75
- // stale state
76
- }
77
- clearState();
78
- }
79
- const { Sandbox } = await getSandboxSDK();
80
- sandbox = await Sandbox.create({
81
- resources: { vcpus: 8 },
82
- timeout: 18_000_000, // 5 hours (max for Pro/Enterprise)
83
- ports: [3000],
84
- runtime: "node22",
85
- });
86
- const state = {
87
- sandboxId: sandbox.sandboxId,
88
- createdAt: new Date().toISOString(),
89
- };
90
- try {
91
- const domain = sandbox.domain(3000);
92
- state.publicUrl = domain.startsWith("http") ? domain : `https://${domain}`;
93
- }
94
- catch {
95
- // no public URL yet
96
- }
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`);
128
- return [
129
- `sandbox created: ${sandbox.sandboxId}`,
130
- state.publicUrl ? `url: ${state.publicUrl}` : null,
131
- ]
132
- .filter(Boolean)
133
- .join("\n");
134
- }
135
- export async function exec(command) {
136
- if (!sandbox)
137
- throw new Error("no sandbox running — run `cloud create` first");
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)`;
147
- }
148
- export async function destroy() {
149
- if (!sandbox) {
150
- const state = loadState();
151
- if (state) {
152
- try {
153
- loadEnv();
154
- const { Sandbox } = await getSandboxSDK();
155
- sandbox = await Sandbox.get({ sandboxId: state.sandboxId });
156
- }
157
- catch {
158
- clearState();
159
- return "no sandbox to destroy (cleared stale state)";
160
- }
161
- }
162
- else {
163
- return "no sandbox running";
164
- }
165
- }
166
- const id = sandbox.sandboxId;
167
- await sandbox.stop({ blocking: true });
168
- sandbox = null;
169
- clearState();
170
- return `destroyed ${id}`;
171
- }
172
- export async function status() {
173
- const state = loadState();
174
- if (!state)
175
- return "no sandbox running";
176
- if (sandbox) {
177
- return [
178
- `id: ${sandbox.sandboxId}`,
179
- `status: ${sandbox.status}`,
180
- `created: ${state.createdAt}`,
181
- state.publicUrl ? `url: ${state.publicUrl}` : null,
182
- ]
183
- .filter(Boolean)
184
- .join("\n");
185
- }
186
- return [
187
- `id: ${state.sandboxId}`,
188
- `status: unknown (daemon not running)`,
189
- `created: ${state.createdAt}`,
190
- state.publicUrl ? `url: ${state.publicUrl}` : null,
191
- ]
192
- .filter(Boolean)
193
- .join("\n");
194
- }
195
- /**
196
- * Load .env.local for Vercel credentials.
197
- */
198
- function loadEnv() {
199
- const candidates = [
200
- join(__dirname, "..", ".env.local"),
201
- join(__dirname, "..", "prototypes", "cloud", ".env.local"),
202
- resolve(process.cwd(), ".env.local"),
203
- ];
204
- for (const candidate of candidates) {
205
- try {
206
- const content = readFileSync(candidate, "utf-8");
207
- for (const line of content.split("\n")) {
208
- const trimmed = line.trim();
209
- if (!trimmed || trimmed.startsWith("#"))
210
- continue;
211
- const eqIdx = trimmed.indexOf("=");
212
- if (eqIdx === -1)
213
- continue;
214
- const key = trimmed.slice(0, eqIdx);
215
- let value = trimmed.slice(eqIdx + 1);
216
- if ((value.startsWith('"') && value.endsWith('"')) ||
217
- (value.startsWith("'") && value.endsWith("'"))) {
218
- value = value.slice(1, -1);
219
- }
220
- if (!process.env[key]) {
221
- process.env[key] = value;
222
- }
223
- }
224
- return;
225
- }
226
- catch {
227
- continue;
228
- }
229
- }
230
- }