@vercel/next-browser 0.1.7 → 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/README.md +148 -48
- package/dist/browser.js +362 -71
- package/dist/cli.js +108 -12
- package/dist/daemon.js +19 -3
- package/dist/paths.js +3 -1
- package/dist/suspense.js +502 -73
- package/package.json +12 -9
- package/dist/cloud-client.js +0 -72
- package/dist/cloud-daemon.js +0 -87
- package/dist/cloud-paths.js +0 -7
- package/dist/cloud.js +0 -230
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/next-browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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
|
+
}
|
package/dist/cloud-client.js
DELETED
|
@@ -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
|
-
}
|
package/dist/cloud-daemon.js
DELETED
|
@@ -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
|
-
}
|
package/dist/cloud-paths.js
DELETED
|
@@ -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");
|
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
|
-
}
|