@anvia/sandbox 0.4.1 → 0.6.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/chunk-FTNNCT6S.js +151 -0
- package/dist/chunk-FTNNCT6S.js.map +1 -0
- package/dist/cli.d.ts +62 -0
- package/dist/cli.js +836 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +21 -2
- package/dist/index.js +192 -156
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/dist/cli.js
ADDED
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assertDockerCli
|
|
4
|
+
} from "./chunk-FTNNCT6S.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { lstat, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
|
|
11
|
+
// src/image-builder.ts
|
|
12
|
+
var defaultSandboxImageVersions = {
|
|
13
|
+
node: "24.18.0",
|
|
14
|
+
pnpm: "11.0.4",
|
|
15
|
+
bun: "1.3.14",
|
|
16
|
+
python: "3.13.14",
|
|
17
|
+
uv: "0.11.29",
|
|
18
|
+
playwright: "1.61.0"
|
|
19
|
+
};
|
|
20
|
+
var artifactPythonPackages = [
|
|
21
|
+
"matplotlib==3.11.1",
|
|
22
|
+
"seaborn==0.13.2",
|
|
23
|
+
"Pillow==12.3.0",
|
|
24
|
+
"ReportLab==5.0.0",
|
|
25
|
+
"pypdf==6.14.2",
|
|
26
|
+
"pandas==3.0.3",
|
|
27
|
+
"openpyxl==3.1.5",
|
|
28
|
+
"XlsxWriter==3.2.9",
|
|
29
|
+
"python-docx==1.2.0"
|
|
30
|
+
];
|
|
31
|
+
var runtimeOrder = ["node", "bun", "python"];
|
|
32
|
+
var featureOrder = ["artifacts", "playwright"];
|
|
33
|
+
var commonAptPackages = ["bash", "ca-certificates", "findutils", "libstdc++6", "procps"];
|
|
34
|
+
function resolveSandboxImageSpec(input) {
|
|
35
|
+
validateName(input.name);
|
|
36
|
+
const runtimes = new Set(input.runtimes ?? []);
|
|
37
|
+
const features = new Set(input.features ?? []);
|
|
38
|
+
const packages = {
|
|
39
|
+
apt: unique(input.packages?.apt ?? []),
|
|
40
|
+
npm: unique(input.packages?.npm ?? []),
|
|
41
|
+
uv: unique(input.packages?.uv ?? [])
|
|
42
|
+
};
|
|
43
|
+
for (const runtime of runtimes) validateRuntime(runtime);
|
|
44
|
+
for (const feature of features) validateFeature(feature);
|
|
45
|
+
for (const packageName of packages.apt) validateAptPackage(packageName);
|
|
46
|
+
for (const packageSpec of packages.npm) parseNpmPackageSpec(packageSpec);
|
|
47
|
+
for (const requirement of packages.uv) validateUvRequirement(requirement);
|
|
48
|
+
if (features.has("artifacts") || packages.uv.length > 0) runtimes.add("python");
|
|
49
|
+
if (features.has("playwright")) runtimes.add("node");
|
|
50
|
+
if (packages.npm.length > 0 && !runtimes.has("node") && !runtimes.has("bun")) {
|
|
51
|
+
runtimes.add("node");
|
|
52
|
+
}
|
|
53
|
+
if (runtimes.size === 0) {
|
|
54
|
+
throw new Error("Select at least one runtime or feature.");
|
|
55
|
+
}
|
|
56
|
+
const versions = { ...defaultSandboxImageVersions, ...input.versions };
|
|
57
|
+
for (const [name, version] of Object.entries(versions)) validateVersion(name, version);
|
|
58
|
+
const tag = input.tag ?? `anvia-sandbox-${input.name}:latest`;
|
|
59
|
+
validateImageTag(tag);
|
|
60
|
+
return {
|
|
61
|
+
name: input.name,
|
|
62
|
+
tag,
|
|
63
|
+
runtimes: runtimeOrder.filter((runtime) => runtimes.has(runtime)),
|
|
64
|
+
features: featureOrder.filter((feature) => features.has(feature)),
|
|
65
|
+
packages,
|
|
66
|
+
versions
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function renderSandboxImageContext(spec, generatorVersion) {
|
|
70
|
+
const files = /* @__PURE__ */ new Map();
|
|
71
|
+
const pythonRequirements = [
|
|
72
|
+
...spec.features.includes("artifacts") ? artifactPythonPackages : [],
|
|
73
|
+
...spec.packages.uv
|
|
74
|
+
];
|
|
75
|
+
const npmDependencies = npmDependenciesFor(spec);
|
|
76
|
+
files.set("Dockerfile", `${renderDockerfile(spec, pythonRequirements, npmDependencies)}
|
|
77
|
+
`);
|
|
78
|
+
files.set(
|
|
79
|
+
".dockerignore",
|
|
80
|
+
renderDockerignore(pythonRequirements.length > 0, npmDependencies.size > 0)
|
|
81
|
+
);
|
|
82
|
+
if (pythonRequirements.length > 0) {
|
|
83
|
+
files.set("pyproject.toml", renderPythonProject(spec, unique(pythonRequirements)));
|
|
84
|
+
}
|
|
85
|
+
if (npmDependencies.size > 0) {
|
|
86
|
+
files.set(
|
|
87
|
+
"package.json",
|
|
88
|
+
`${JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
private: true,
|
|
91
|
+
description: `Generated dependencies for ${spec.name}`,
|
|
92
|
+
dependencies: Object.fromEntries(npmDependencies)
|
|
93
|
+
},
|
|
94
|
+
null,
|
|
95
|
+
2
|
|
96
|
+
)}
|
|
97
|
+
`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const generatedFiles = [...files.keys(), "anvia-sandbox.json"].sort();
|
|
101
|
+
const manifest = {
|
|
102
|
+
schemaVersion: 1,
|
|
103
|
+
generatedBy: {
|
|
104
|
+
package: "@anvia/sandbox",
|
|
105
|
+
version: generatorVersion
|
|
106
|
+
},
|
|
107
|
+
...spec,
|
|
108
|
+
generatedFiles
|
|
109
|
+
};
|
|
110
|
+
files.set("anvia-sandbox.json", `${JSON.stringify(manifest, null, 2)}
|
|
111
|
+
`);
|
|
112
|
+
return { manifest, files };
|
|
113
|
+
}
|
|
114
|
+
function unpinnedSandboxImagePackages(spec) {
|
|
115
|
+
const unpinned = [];
|
|
116
|
+
for (const value of spec.packages.npm) {
|
|
117
|
+
if (parseNpmPackageSpec(value).version === "latest") unpinned.push(value);
|
|
118
|
+
}
|
|
119
|
+
for (const value of spec.packages.uv) {
|
|
120
|
+
if (!/===|==|@\s*https?:/i.test(value)) unpinned.push(value);
|
|
121
|
+
}
|
|
122
|
+
return unpinned;
|
|
123
|
+
}
|
|
124
|
+
function renderDockerfile(spec, pythonRequirements, npmDependencies) {
|
|
125
|
+
const hasNode = spec.runtimes.includes("node");
|
|
126
|
+
const hasBun = spec.runtimes.includes("bun");
|
|
127
|
+
const hasPython = spec.runtimes.includes("python");
|
|
128
|
+
const hasPlaywright = spec.features.includes("playwright");
|
|
129
|
+
const lines = [
|
|
130
|
+
"# Generated by @anvia/sandbox. Edit the manifest and regenerate instead of editing this file."
|
|
131
|
+
];
|
|
132
|
+
if (hasNode) lines.push(`FROM node:${spec.versions.node}-bookworm-slim AS node-runtime`);
|
|
133
|
+
if (hasPython) lines.push(`FROM python:${spec.versions.python}-slim-bookworm AS python-runtime`);
|
|
134
|
+
if (hasBun) lines.push(`FROM oven/bun:${spec.versions.bun}-slim AS bun-runtime`);
|
|
135
|
+
if (hasPython) lines.push(`FROM ghcr.io/astral-sh/uv:${spec.versions.uv} AS uv-runtime`);
|
|
136
|
+
if (hasPlaywright) {
|
|
137
|
+
lines.push(`FROM mcr.microsoft.com/playwright:v${spec.versions.playwright}-noble AS final`);
|
|
138
|
+
} else if (hasPython) {
|
|
139
|
+
lines.push("FROM python-runtime AS final");
|
|
140
|
+
} else if (hasNode) {
|
|
141
|
+
lines.push("FROM node-runtime AS final");
|
|
142
|
+
} else {
|
|
143
|
+
lines.push("FROM bun-runtime AS final");
|
|
144
|
+
}
|
|
145
|
+
lines.push("", "USER root");
|
|
146
|
+
if (hasPython && (hasPlaywright || !isFinalRuntime(spec, "python"))) {
|
|
147
|
+
lines.push("COPY --from=python-runtime /usr/local/ /usr/local/");
|
|
148
|
+
}
|
|
149
|
+
if (hasNode && (hasPlaywright || !isFinalRuntime(spec, "node"))) {
|
|
150
|
+
lines.push("COPY --from=node-runtime /usr/local/bin/ /usr/local/bin/");
|
|
151
|
+
lines.push(
|
|
152
|
+
"COPY --from=node-runtime /usr/local/lib/node_modules/ /usr/local/lib/node_modules/"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (hasBun && !isFinalRuntime(spec, "bun")) {
|
|
156
|
+
lines.push("COPY --from=bun-runtime /usr/local/bin/bun /usr/local/bin/bun");
|
|
157
|
+
lines.push("RUN ln -sf /usr/local/bin/bun /usr/local/bin/bunx");
|
|
158
|
+
}
|
|
159
|
+
if (hasPython) {
|
|
160
|
+
lines.push("COPY --from=uv-runtime /uv /uvx /usr/local/bin/");
|
|
161
|
+
}
|
|
162
|
+
const aptPackages = unique([
|
|
163
|
+
...commonAptPackages,
|
|
164
|
+
...spec.features.includes("artifacts") ? ["fonts-dejavu-core"] : [],
|
|
165
|
+
...spec.packages.apt
|
|
166
|
+
]).sort();
|
|
167
|
+
lines.push(
|
|
168
|
+
"",
|
|
169
|
+
"RUN apt-get update \\",
|
|
170
|
+
` && apt-get install -y --no-install-recommends ${aptPackages.map(shellQuote).join(" ")} \\`,
|
|
171
|
+
" && rm -rf /var/lib/apt/lists/*"
|
|
172
|
+
);
|
|
173
|
+
if (hasNode) {
|
|
174
|
+
lines.push(
|
|
175
|
+
"",
|
|
176
|
+
`RUN npm install --global ${shellQuote(`pnpm@${spec.versions.pnpm}`)} --no-audit --no-fund \\`,
|
|
177
|
+
" && npm cache clean --force"
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (pythonRequirements.length > 0) {
|
|
181
|
+
lines.push(
|
|
182
|
+
"",
|
|
183
|
+
"COPY pyproject.toml /opt/anvia-python/pyproject.toml",
|
|
184
|
+
"RUN cd /opt/anvia-python \\",
|
|
185
|
+
" && uv sync --no-dev --no-cache --no-install-project",
|
|
186
|
+
"ENV VIRTUAL_ENV=/opt/anvia-python/.venv",
|
|
187
|
+
"ENV PATH=/opt/anvia-python/.venv/bin:$PATH"
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (npmDependencies.size > 0) {
|
|
191
|
+
lines.push("", "COPY package.json /opt/anvia-js/package.json");
|
|
192
|
+
if (hasNode) {
|
|
193
|
+
lines.push(
|
|
194
|
+
"RUN npm install --prefix /opt/anvia-js --omit=dev --no-audit --no-fund \\",
|
|
195
|
+
" && ln -s /opt/anvia-js/node_modules /node_modules \\",
|
|
196
|
+
" && npm cache clean --force"
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
lines.push(
|
|
200
|
+
"RUN cd /opt/anvia-js \\",
|
|
201
|
+
" && bun install --production --no-save \\",
|
|
202
|
+
" && ln -s /opt/anvia-js/node_modules /node_modules"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
lines.push("ENV NODE_PATH=/opt/anvia-js/node_modules");
|
|
206
|
+
lines.push("ENV PATH=/opt/anvia-js/node_modules/.bin:$PATH");
|
|
207
|
+
}
|
|
208
|
+
lines.push(
|
|
209
|
+
"",
|
|
210
|
+
...hasPlaywright ? ["ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright"] : [],
|
|
211
|
+
"RUN mkdir -p /workspace",
|
|
212
|
+
"WORKDIR /workspace",
|
|
213
|
+
"ENTRYPOINT []",
|
|
214
|
+
`CMD ["sh", "-c", "trap 'exit 0' TERM INT; while :; do sleep 3600 & wait $!; done"]`
|
|
215
|
+
);
|
|
216
|
+
return lines.join("\n");
|
|
217
|
+
}
|
|
218
|
+
function renderDockerignore(hasPython, hasNpm) {
|
|
219
|
+
return [
|
|
220
|
+
"**",
|
|
221
|
+
"!Dockerfile",
|
|
222
|
+
"!.dockerignore",
|
|
223
|
+
"!anvia-sandbox.json",
|
|
224
|
+
...hasPython ? ["!pyproject.toml"] : [],
|
|
225
|
+
...hasNpm ? ["!package.json"] : [],
|
|
226
|
+
""
|
|
227
|
+
].join("\n");
|
|
228
|
+
}
|
|
229
|
+
function renderPythonProject(spec, requirements) {
|
|
230
|
+
const [major, minor] = spec.versions.python.split(".");
|
|
231
|
+
return [
|
|
232
|
+
"[project]",
|
|
233
|
+
`name = ${JSON.stringify(`anvia-sandbox-${spec.name}`)}`,
|
|
234
|
+
'version = "0.0.0"',
|
|
235
|
+
`requires-python = ${JSON.stringify(`>=${major}.${minor}`)}`,
|
|
236
|
+
"dependencies = [",
|
|
237
|
+
...requirements.map((requirement) => ` ${JSON.stringify(requirement)},`),
|
|
238
|
+
"]",
|
|
239
|
+
""
|
|
240
|
+
].join("\n");
|
|
241
|
+
}
|
|
242
|
+
function npmDependenciesFor(spec) {
|
|
243
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
244
|
+
if (spec.features.includes("playwright")) {
|
|
245
|
+
dependencies.set("playwright", spec.versions.playwright);
|
|
246
|
+
}
|
|
247
|
+
for (const packageSpec of spec.packages.npm) {
|
|
248
|
+
const parsed = parseNpmPackageSpec(packageSpec);
|
|
249
|
+
if (dependencies.has(parsed.name)) {
|
|
250
|
+
throw new Error(`Duplicate npm package: ${parsed.name}`);
|
|
251
|
+
}
|
|
252
|
+
dependencies.set(parsed.name, parsed.version);
|
|
253
|
+
}
|
|
254
|
+
return new Map([...dependencies.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
255
|
+
}
|
|
256
|
+
function isFinalRuntime(spec, runtime) {
|
|
257
|
+
if (spec.features.includes("playwright")) return false;
|
|
258
|
+
if (spec.runtimes.includes("python")) return runtime === "python";
|
|
259
|
+
if (spec.runtimes.includes("node")) return runtime === "node";
|
|
260
|
+
return runtime === "bun";
|
|
261
|
+
}
|
|
262
|
+
function validateName(name) {
|
|
263
|
+
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(name)) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
"Image name must be 1-63 lowercase letters, numbers, or hyphens and cannot start with a hyphen."
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function validateRuntime(runtime) {
|
|
270
|
+
if (!runtimeOrder.includes(runtime)) {
|
|
271
|
+
throw new Error(`Unknown runtime: ${runtime}. Expected node, bun, or python.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function validateFeature(feature) {
|
|
275
|
+
if (!featureOrder.includes(feature)) {
|
|
276
|
+
throw new Error(`Unknown feature: ${feature}. Expected artifacts or playwright.`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function validateAptPackage(packageName) {
|
|
280
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9.+:~=-]*$/.test(packageName)) {
|
|
281
|
+
throw new Error(`Invalid apt package: ${packageName}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function parseNpmPackageSpec(packageSpec) {
|
|
285
|
+
if (/\s/.test(packageSpec) || hasControlCharacter(packageSpec)) {
|
|
286
|
+
throw new Error(`Invalid npm package spec: ${packageSpec}`);
|
|
287
|
+
}
|
|
288
|
+
const separator = packageSpec.startsWith("@") ? packageSpec.indexOf("@", packageSpec.indexOf("/") + 1) : packageSpec.indexOf("@");
|
|
289
|
+
const name = separator === -1 ? packageSpec : packageSpec.slice(0, separator);
|
|
290
|
+
const version = separator === -1 ? "latest" : packageSpec.slice(separator + 1);
|
|
291
|
+
if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(name) || !version) {
|
|
292
|
+
throw new Error(`Invalid npm package spec: ${packageSpec}`);
|
|
293
|
+
}
|
|
294
|
+
if (/[\s'"`$;&|<>\\]/.test(version) || hasControlCharacter(version)) {
|
|
295
|
+
throw new Error(`Invalid npm package version in: ${packageSpec}`);
|
|
296
|
+
}
|
|
297
|
+
return { name, version };
|
|
298
|
+
}
|
|
299
|
+
function validateUvRequirement(requirement) {
|
|
300
|
+
if (!requirement || requirement.startsWith("-") || hasControlCharacter(requirement)) {
|
|
301
|
+
throw new Error(`Invalid uv package requirement: ${requirement}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function validateVersion(name, version) {
|
|
305
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(version)) {
|
|
306
|
+
throw new Error(`Invalid ${name} version: ${version}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function validateImageTag(tag) {
|
|
310
|
+
if (!tag || tag.startsWith("-") || tag.includes("@") || /\s/.test(tag) || hasControlCharacter(tag)) {
|
|
311
|
+
throw new Error(`Invalid Docker image tag: ${tag}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function unique(values) {
|
|
315
|
+
return [...new Set(values)];
|
|
316
|
+
}
|
|
317
|
+
function hasControlCharacter(value) {
|
|
318
|
+
return [...value].some((character) => character.charCodeAt(0) < 32);
|
|
319
|
+
}
|
|
320
|
+
function shellQuote(value) {
|
|
321
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/cli.ts
|
|
325
|
+
var generatedFileNames = /* @__PURE__ */ new Set([
|
|
326
|
+
".dockerignore",
|
|
327
|
+
"Dockerfile",
|
|
328
|
+
"anvia-sandbox.json",
|
|
329
|
+
"package.json",
|
|
330
|
+
"pyproject.toml"
|
|
331
|
+
]);
|
|
332
|
+
var commonAptPackages2 = [
|
|
333
|
+
{ value: "git", label: "Git", hint: "Source control and repository operations" },
|
|
334
|
+
{ value: "curl", label: "curl", hint: "HTTP downloads and API debugging" },
|
|
335
|
+
{ value: "jq", label: "jq", hint: "JSON processing from the shell" },
|
|
336
|
+
{ value: "ffmpeg", label: "FFmpeg", hint: "Audio and video processing" },
|
|
337
|
+
{ value: "imagemagick", label: "ImageMagick", hint: "Image conversion and editing" },
|
|
338
|
+
{ value: "poppler-utils", label: "Poppler tools", hint: "PDF inspection and conversion" },
|
|
339
|
+
{ value: "libreoffice", label: "LibreOffice", hint: "Large; office document conversion" }
|
|
340
|
+
];
|
|
341
|
+
var commonNpmPackages = [
|
|
342
|
+
{ value: "pdfkit@0.19.1", label: "PDFKit", hint: "Generate PDF documents" },
|
|
343
|
+
{ value: "sharp@0.35.3", label: "Sharp", hint: "Resize and transform images" },
|
|
344
|
+
{ value: "exceljs@4.4.0", label: "ExcelJS", hint: "Read and write Excel workbooks" },
|
|
345
|
+
{ value: "docx@9.7.1", label: "docx", hint: "Generate Word documents" },
|
|
346
|
+
{ value: "pptxgenjs@4.0.1", label: "PptxGenJS", hint: "Generate PowerPoint presentations" }
|
|
347
|
+
];
|
|
348
|
+
var commonUvPackages = [
|
|
349
|
+
{ value: "httpx==0.28.1", label: "HTTPX", hint: "Modern HTTP client" },
|
|
350
|
+
{ value: "beautifulsoup4==4.15.0", label: "Beautiful Soup", hint: "HTML and XML parsing" },
|
|
351
|
+
{ value: "scipy==1.18.0", label: "SciPy", hint: "Scientific computing" },
|
|
352
|
+
{ value: "scikit-learn==1.9.0", label: "scikit-learn", hint: "Machine learning utilities" },
|
|
353
|
+
{ value: "polars==1.42.1", label: "Polars", hint: "Fast dataframe processing" }
|
|
354
|
+
];
|
|
355
|
+
var defaultIo = {
|
|
356
|
+
log: console.log,
|
|
357
|
+
warn: console.warn,
|
|
358
|
+
error: console.error,
|
|
359
|
+
stdout: (chunk) => process.stdout.write(chunk),
|
|
360
|
+
stderr: (chunk) => process.stderr.write(chunk)
|
|
361
|
+
};
|
|
362
|
+
async function runCli(argv = process.argv.slice(2), cwd = process.cwd(), io = defaultIo, dependencies = {}) {
|
|
363
|
+
let options;
|
|
364
|
+
try {
|
|
365
|
+
options = parseArgs(argv);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
io.error(errorMessage(error));
|
|
368
|
+
io.log(helpText());
|
|
369
|
+
return 1;
|
|
370
|
+
}
|
|
371
|
+
if (options.help || options.command === void 0) {
|
|
372
|
+
io.log(helpText());
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
if (options.command !== "create-image") {
|
|
376
|
+
io.error(`Unknown command: ${options.command}`);
|
|
377
|
+
io.log(helpText());
|
|
378
|
+
return 1;
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const shouldPrompt = options.name === void 0 || options.runtimes.length === 0 && options.features.length === 0 && options.npm.length === 0 && options.uv.length === 0;
|
|
382
|
+
if (shouldPrompt) {
|
|
383
|
+
const isTTY = dependencies.isTTY ?? (process.stdin.isTTY && process.stdout.isTTY);
|
|
384
|
+
if (!isTTY) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
"Non-interactive create-image requires --name and at least one --runtime or --feature."
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
const prompt = dependencies.prompt ?? promptForImage;
|
|
390
|
+
const result = await prompt(options);
|
|
391
|
+
if (result === void 0) return 130;
|
|
392
|
+
options = applyPromptResult(options, result);
|
|
393
|
+
}
|
|
394
|
+
const input = {
|
|
395
|
+
name: required(options.name, "Image name is required."),
|
|
396
|
+
runtimes: options.runtimes,
|
|
397
|
+
features: options.features,
|
|
398
|
+
packages: {
|
|
399
|
+
apt: options.apt,
|
|
400
|
+
npm: options.npm,
|
|
401
|
+
uv: options.uv
|
|
402
|
+
},
|
|
403
|
+
versions: options.versions
|
|
404
|
+
};
|
|
405
|
+
if (options.tag !== void 0) input.tag = options.tag;
|
|
406
|
+
const spec = resolveSandboxImageSpec(input);
|
|
407
|
+
const packageVersion = dependencies.packageVersion ?? await readPackageVersion();
|
|
408
|
+
const context = renderSandboxImageContext(spec, packageVersion);
|
|
409
|
+
const outputPath = path.resolve(
|
|
410
|
+
cwd,
|
|
411
|
+
options.output ?? path.join(".anvia", "sandbox-images", spec.name)
|
|
412
|
+
);
|
|
413
|
+
const displayPath = relativeDisplayPath(cwd, outputPath);
|
|
414
|
+
const unpinned = unpinnedSandboxImagePackages(spec);
|
|
415
|
+
if (unpinned.length > 0) {
|
|
416
|
+
io.warn(`Unpinned custom packages may change on rebuild: ${unpinned.join(", ")}`);
|
|
417
|
+
}
|
|
418
|
+
if (options.dryRun) {
|
|
419
|
+
printDryRun(context.files, displayPath, spec.tag, io);
|
|
420
|
+
return 0;
|
|
421
|
+
}
|
|
422
|
+
await writeImageContext(outputPath, context.manifest, context.files, options.force);
|
|
423
|
+
io.log(`Created ${displayPath}`);
|
|
424
|
+
if (!options.build) {
|
|
425
|
+
io.log("");
|
|
426
|
+
io.log("Build later:");
|
|
427
|
+
io.log(` ${shellCommand([options.dockerPath, "build", "--tag", spec.tag, displayPath])}`);
|
|
428
|
+
printUsageSnippet(spec.tag, io);
|
|
429
|
+
return 0;
|
|
430
|
+
}
|
|
431
|
+
const buildImage = dependencies.buildImage ?? buildDockerImage;
|
|
432
|
+
await buildImage({
|
|
433
|
+
contextPath: outputPath,
|
|
434
|
+
tag: spec.tag,
|
|
435
|
+
dockerPath: options.dockerPath,
|
|
436
|
+
io
|
|
437
|
+
});
|
|
438
|
+
io.log(`Built ${spec.tag}`);
|
|
439
|
+
printUsageSnippet(spec.tag, io);
|
|
440
|
+
return 0;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
io.error(errorMessage(error));
|
|
443
|
+
return 1;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async function promptForImage(options) {
|
|
447
|
+
assertInteractiveNodeVersion();
|
|
448
|
+
const prompts = await import("@clack/prompts");
|
|
449
|
+
prompts.intro("Create an Anvia sandbox image");
|
|
450
|
+
const nameResult = options.name ?? await prompts.text({
|
|
451
|
+
message: "Image name",
|
|
452
|
+
placeholder: "reports",
|
|
453
|
+
validate: (value) => /^[a-z0-9][a-z0-9-]{0,62}$/.test(value ?? "") ? void 0 : "Use 1-63 lowercase letters, numbers, or hyphens."
|
|
454
|
+
});
|
|
455
|
+
if (prompts.isCancel(nameResult)) return cancelPrompt(prompts);
|
|
456
|
+
const name = String(nameResult);
|
|
457
|
+
let runtimes = [...options.runtimes];
|
|
458
|
+
let features = [...options.features];
|
|
459
|
+
if (runtimes.length === 0 && features.length === 0 && options.npm.length === 0 && options.uv.length === 0) {
|
|
460
|
+
const capabilities = await prompts.multiselect({
|
|
461
|
+
message: "Select runtimes and features",
|
|
462
|
+
required: true,
|
|
463
|
+
options: [
|
|
464
|
+
{ value: "node", label: "Node.js", hint: "Includes npm and pnpm" },
|
|
465
|
+
{ value: "bun", label: "Bun", hint: "Includes bun and bunx" },
|
|
466
|
+
{ value: "python", label: "Python", hint: "Includes uv and uvx" },
|
|
467
|
+
{ value: "artifacts", label: "Reporting and artifacts", hint: "Adds Python automatically" },
|
|
468
|
+
{ value: "playwright", label: "Playwright + Chromium", hint: "Adds Node.js automatically" }
|
|
469
|
+
]
|
|
470
|
+
});
|
|
471
|
+
if (prompts.isCancel(capabilities)) return cancelPrompt(prompts);
|
|
472
|
+
runtimes = capabilities.filter(isRuntime);
|
|
473
|
+
features = capabilities.filter(isFeature);
|
|
474
|
+
}
|
|
475
|
+
const aptResult = await selectCommonPackages(
|
|
476
|
+
prompts,
|
|
477
|
+
"Select common apt tools (optional)",
|
|
478
|
+
commonAptPackages2,
|
|
479
|
+
options.apt
|
|
480
|
+
);
|
|
481
|
+
if (aptResult === void 0) return cancelPrompt(prompts);
|
|
482
|
+
const npmResult = await selectCommonPackages(
|
|
483
|
+
prompts,
|
|
484
|
+
"Select common npm libraries (optional)",
|
|
485
|
+
commonNpmPackages,
|
|
486
|
+
options.npm
|
|
487
|
+
);
|
|
488
|
+
if (npmResult === void 0) return cancelPrompt(prompts);
|
|
489
|
+
const uvResult = await selectCommonPackages(
|
|
490
|
+
prompts,
|
|
491
|
+
"Select common Python libraries with uv (optional)",
|
|
492
|
+
commonUvPackages,
|
|
493
|
+
options.uv
|
|
494
|
+
);
|
|
495
|
+
if (uvResult === void 0) return cancelPrompt(prompts);
|
|
496
|
+
const tagResult = options.tag ?? await prompts.text({
|
|
497
|
+
message: "Docker image tag",
|
|
498
|
+
initialValue: `anvia-sandbox-${name}:latest`
|
|
499
|
+
});
|
|
500
|
+
if (prompts.isCancel(tagResult)) return cancelPrompt(prompts);
|
|
501
|
+
const outputResult = options.output ?? await prompts.text({
|
|
502
|
+
message: "Generated source directory",
|
|
503
|
+
initialValue: path.join(".anvia", "sandbox-images", name)
|
|
504
|
+
});
|
|
505
|
+
if (prompts.isCancel(outputResult)) return cancelPrompt(prompts);
|
|
506
|
+
const buildResult = options.buildExplicit ? options.build : await prompts.confirm({ message: "Build the image now?", initialValue: true });
|
|
507
|
+
if (prompts.isCancel(buildResult)) return cancelPrompt(prompts);
|
|
508
|
+
const confirmed = await prompts.confirm({
|
|
509
|
+
message: "Create this sandbox image?",
|
|
510
|
+
initialValue: true
|
|
511
|
+
});
|
|
512
|
+
if (prompts.isCancel(confirmed) || !confirmed) return cancelPrompt(prompts);
|
|
513
|
+
prompts.outro("Configuration ready");
|
|
514
|
+
return {
|
|
515
|
+
name,
|
|
516
|
+
runtimes,
|
|
517
|
+
features,
|
|
518
|
+
apt: aptResult,
|
|
519
|
+
npm: npmResult,
|
|
520
|
+
uv: uvResult,
|
|
521
|
+
tag: String(tagResult),
|
|
522
|
+
output: String(outputResult),
|
|
523
|
+
build: Boolean(buildResult)
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
async function selectCommonPackages(prompts, message, options, existing) {
|
|
527
|
+
const commonValues = new Set(options.map((option) => option.value));
|
|
528
|
+
const result = await prompts.multiselect({
|
|
529
|
+
message,
|
|
530
|
+
required: false,
|
|
531
|
+
options: [...options],
|
|
532
|
+
initialValues: existing.filter((value) => commonValues.has(value))
|
|
533
|
+
});
|
|
534
|
+
if (prompts.isCancel(result)) return void 0;
|
|
535
|
+
return [.../* @__PURE__ */ new Set([...existing, ...result])];
|
|
536
|
+
}
|
|
537
|
+
function cancelPrompt(prompts) {
|
|
538
|
+
prompts.cancel("Image creation cancelled.");
|
|
539
|
+
return void 0;
|
|
540
|
+
}
|
|
541
|
+
async function buildDockerImage(input) {
|
|
542
|
+
await assertDockerCli(["build", "--tag", input.tag, input.contextPath], {
|
|
543
|
+
dockerPath: input.dockerPath,
|
|
544
|
+
onStdout: input.io.stdout,
|
|
545
|
+
onStderr: input.io.stderr
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
async function writeImageContext(outputPath, manifest, files, force) {
|
|
549
|
+
const outputStat = await safeLstat(outputPath);
|
|
550
|
+
if (outputStat?.isSymbolicLink()) {
|
|
551
|
+
throw new Error(`Refusing to write through symlink: ${outputPath}`);
|
|
552
|
+
}
|
|
553
|
+
if (outputStat !== void 0) {
|
|
554
|
+
if (!outputStat.isDirectory()) throw new Error(`Output path is not a directory: ${outputPath}`);
|
|
555
|
+
const previousManifest = await readGeneratedManifest(outputPath);
|
|
556
|
+
if (previousManifest === void 0) {
|
|
557
|
+
throw new Error(
|
|
558
|
+
`Output directory already exists but was not generated by @anvia/sandbox: ${outputPath}`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
if (!force) throw new Error(`Output directory already exists. Use --force to regenerate it.`);
|
|
562
|
+
for (const filename of previousManifest.generatedFiles) {
|
|
563
|
+
const target = path.join(outputPath, filename);
|
|
564
|
+
const targetStat = await safeLstat(target);
|
|
565
|
+
if (targetStat?.isSymbolicLink()) throw new Error(`Refusing to replace symlink: ${target}`);
|
|
566
|
+
if (targetStat !== void 0) await rm(target);
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
await mkdir(outputPath, { recursive: true });
|
|
570
|
+
}
|
|
571
|
+
for (const [filename, content] of files) {
|
|
572
|
+
if (!generatedFileNames.has(filename))
|
|
573
|
+
throw new Error(`Unexpected generated filename: ${filename}`);
|
|
574
|
+
const target = path.join(outputPath, filename);
|
|
575
|
+
const targetStat = await safeLstat(target);
|
|
576
|
+
if (targetStat?.isSymbolicLink()) throw new Error(`Refusing to replace symlink: ${target}`);
|
|
577
|
+
await writeFile(target, content, "utf8");
|
|
578
|
+
}
|
|
579
|
+
if (!files.has("anvia-sandbox.json") || manifest.generatedFiles.length !== files.size) {
|
|
580
|
+
throw new Error("Generated image context manifest is inconsistent.");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function readGeneratedManifest(outputPath) {
|
|
584
|
+
const manifestPath = path.join(outputPath, "anvia-sandbox.json");
|
|
585
|
+
const stat = await safeLstat(manifestPath);
|
|
586
|
+
if (stat === void 0) return void 0;
|
|
587
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
588
|
+
throw new Error(`Invalid generated manifest: ${manifestPath}`);
|
|
589
|
+
}
|
|
590
|
+
let value;
|
|
591
|
+
try {
|
|
592
|
+
value = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
593
|
+
} catch (error) {
|
|
594
|
+
throw new Error(`Unable to read generated manifest: ${manifestPath}`, { cause: error });
|
|
595
|
+
}
|
|
596
|
+
if (!isGeneratedManifest(value)) {
|
|
597
|
+
throw new Error(
|
|
598
|
+
`Output manifest is not recognized as an @anvia/sandbox manifest: ${manifestPath}`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return value;
|
|
602
|
+
}
|
|
603
|
+
function isGeneratedManifest(value) {
|
|
604
|
+
if (typeof value !== "object" || value === null) return false;
|
|
605
|
+
const record = value;
|
|
606
|
+
const generatedBy = record.generatedBy;
|
|
607
|
+
const files = record.generatedFiles;
|
|
608
|
+
return record.schemaVersion === 1 && generatedBy?.package === "@anvia/sandbox" && Array.isArray(files) && files.every((file) => typeof file === "string" && generatedFileNames.has(file));
|
|
609
|
+
}
|
|
610
|
+
async function safeLstat(target) {
|
|
611
|
+
try {
|
|
612
|
+
return await lstat(target);
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (error.code === "ENOENT") return void 0;
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function printDryRun(files, outputPath, tag, io) {
|
|
619
|
+
io.log(`Would create ${outputPath}`);
|
|
620
|
+
for (const [filename, content] of files) {
|
|
621
|
+
io.log("");
|
|
622
|
+
io.log(`--- ${filename}`);
|
|
623
|
+
io.log(content.trimEnd());
|
|
624
|
+
}
|
|
625
|
+
io.log("");
|
|
626
|
+
io.log(`Would build ${tag}`);
|
|
627
|
+
}
|
|
628
|
+
function printUsageSnippet(tag, io) {
|
|
629
|
+
io.log("");
|
|
630
|
+
io.log("Use with @anvia/sandbox:");
|
|
631
|
+
io.log(" const sandbox = new DockerSandbox({");
|
|
632
|
+
io.log(` image: ${JSON.stringify(tag)},`);
|
|
633
|
+
io.log(' pull: "never",');
|
|
634
|
+
io.log(" });");
|
|
635
|
+
}
|
|
636
|
+
function applyPromptResult(options, result) {
|
|
637
|
+
const next = {
|
|
638
|
+
...options,
|
|
639
|
+
name: result.name,
|
|
640
|
+
runtimes: result.runtimes,
|
|
641
|
+
features: result.features,
|
|
642
|
+
apt: result.apt,
|
|
643
|
+
npm: result.npm,
|
|
644
|
+
uv: result.uv,
|
|
645
|
+
build: result.build,
|
|
646
|
+
buildExplicit: true
|
|
647
|
+
};
|
|
648
|
+
if (result.tag !== void 0) next.tag = result.tag;
|
|
649
|
+
if (result.output !== void 0) next.output = result.output;
|
|
650
|
+
return next;
|
|
651
|
+
}
|
|
652
|
+
function parseArgs(argv) {
|
|
653
|
+
const options = {
|
|
654
|
+
runtimes: [],
|
|
655
|
+
features: [],
|
|
656
|
+
apt: [],
|
|
657
|
+
npm: [],
|
|
658
|
+
uv: [],
|
|
659
|
+
versions: {},
|
|
660
|
+
dockerPath: "docker",
|
|
661
|
+
build: true,
|
|
662
|
+
buildExplicit: false,
|
|
663
|
+
dryRun: false,
|
|
664
|
+
force: false,
|
|
665
|
+
help: false
|
|
666
|
+
};
|
|
667
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
668
|
+
const argument = argv[index] ?? "";
|
|
669
|
+
if (!argument.startsWith("-")) {
|
|
670
|
+
if (options.command !== void 0) throw new Error(`Unexpected argument: ${argument}`);
|
|
671
|
+
options.command = argument;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
const [flag, inlineValue] = splitFlag(argument);
|
|
675
|
+
const value = () => inlineValue ?? required(argv[++index], `${flag} requires a value.`);
|
|
676
|
+
switch (flag) {
|
|
677
|
+
case "-h":
|
|
678
|
+
case "--help":
|
|
679
|
+
options.help = true;
|
|
680
|
+
break;
|
|
681
|
+
case "--name":
|
|
682
|
+
options.name = value();
|
|
683
|
+
break;
|
|
684
|
+
case "--tag":
|
|
685
|
+
options.tag = value();
|
|
686
|
+
break;
|
|
687
|
+
case "--output":
|
|
688
|
+
options.output = value();
|
|
689
|
+
break;
|
|
690
|
+
case "--runtime":
|
|
691
|
+
options.runtimes.push(parseRuntime(value()));
|
|
692
|
+
break;
|
|
693
|
+
case "--feature":
|
|
694
|
+
options.features.push(parseFeature(value()));
|
|
695
|
+
break;
|
|
696
|
+
case "--apt":
|
|
697
|
+
options.apt.push(value());
|
|
698
|
+
break;
|
|
699
|
+
case "--npm":
|
|
700
|
+
options.npm.push(value());
|
|
701
|
+
break;
|
|
702
|
+
case "--uv":
|
|
703
|
+
options.uv.push(value());
|
|
704
|
+
break;
|
|
705
|
+
case "--node-version":
|
|
706
|
+
options.versions.node = value();
|
|
707
|
+
break;
|
|
708
|
+
case "--pnpm-version":
|
|
709
|
+
options.versions.pnpm = value();
|
|
710
|
+
break;
|
|
711
|
+
case "--bun-version":
|
|
712
|
+
options.versions.bun = value();
|
|
713
|
+
break;
|
|
714
|
+
case "--python-version":
|
|
715
|
+
options.versions.python = value();
|
|
716
|
+
break;
|
|
717
|
+
case "--uv-version":
|
|
718
|
+
options.versions.uv = value();
|
|
719
|
+
break;
|
|
720
|
+
case "--playwright-version":
|
|
721
|
+
options.versions.playwright = value();
|
|
722
|
+
break;
|
|
723
|
+
case "--docker-path":
|
|
724
|
+
options.dockerPath = value();
|
|
725
|
+
break;
|
|
726
|
+
case "--no-build":
|
|
727
|
+
rejectInlineValue(flag, inlineValue);
|
|
728
|
+
options.build = false;
|
|
729
|
+
options.buildExplicit = true;
|
|
730
|
+
break;
|
|
731
|
+
case "--dry-run":
|
|
732
|
+
rejectInlineValue(flag, inlineValue);
|
|
733
|
+
options.dryRun = true;
|
|
734
|
+
break;
|
|
735
|
+
case "--force":
|
|
736
|
+
rejectInlineValue(flag, inlineValue);
|
|
737
|
+
options.force = true;
|
|
738
|
+
break;
|
|
739
|
+
default:
|
|
740
|
+
throw new Error(`Unknown option: ${flag}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return options;
|
|
744
|
+
}
|
|
745
|
+
function splitFlag(argument) {
|
|
746
|
+
const separator = argument.indexOf("=");
|
|
747
|
+
return separator === -1 ? [argument, void 0] : [argument.slice(0, separator), argument.slice(separator + 1)];
|
|
748
|
+
}
|
|
749
|
+
function rejectInlineValue(flag, value) {
|
|
750
|
+
if (value !== void 0) throw new Error(`${flag} does not accept a value.`);
|
|
751
|
+
}
|
|
752
|
+
function parseRuntime(value) {
|
|
753
|
+
if (isRuntime(value)) return value;
|
|
754
|
+
throw new Error(`Unknown runtime: ${value}. Expected node, bun, or python.`);
|
|
755
|
+
}
|
|
756
|
+
function parseFeature(value) {
|
|
757
|
+
if (isFeature(value)) return value;
|
|
758
|
+
throw new Error(`Unknown feature: ${value}. Expected artifacts or playwright.`);
|
|
759
|
+
}
|
|
760
|
+
function isRuntime(value) {
|
|
761
|
+
return value === "node" || value === "bun" || value === "python";
|
|
762
|
+
}
|
|
763
|
+
function isFeature(value) {
|
|
764
|
+
return value === "artifacts" || value === "playwright";
|
|
765
|
+
}
|
|
766
|
+
function assertInteractiveNodeVersion() {
|
|
767
|
+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
768
|
+
if (major < 20 || major === 20 && minor < 12) {
|
|
769
|
+
throw new Error("The interactive create-image wizard requires Node.js 20.12 or newer.");
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async function readPackageVersion() {
|
|
773
|
+
const packageJson = JSON.parse(
|
|
774
|
+
await readFile(new URL("../package.json", import.meta.url), "utf8")
|
|
775
|
+
);
|
|
776
|
+
return typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
777
|
+
}
|
|
778
|
+
function relativeDisplayPath(cwd, target) {
|
|
779
|
+
const relative = path.relative(cwd, target);
|
|
780
|
+
return relative && !relative.startsWith("..") ? relative : target;
|
|
781
|
+
}
|
|
782
|
+
function shellCommand(args) {
|
|
783
|
+
return args.map(shellArgument).join(" ");
|
|
784
|
+
}
|
|
785
|
+
function shellArgument(value) {
|
|
786
|
+
if (/^[a-zA-Z0-9_./:@+-]+$/.test(value)) return value;
|
|
787
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
788
|
+
}
|
|
789
|
+
function required(value, message) {
|
|
790
|
+
if (value === void 0 || value === "") throw new Error(message);
|
|
791
|
+
return value;
|
|
792
|
+
}
|
|
793
|
+
function errorMessage(error) {
|
|
794
|
+
return error instanceof Error ? error.message : String(error);
|
|
795
|
+
}
|
|
796
|
+
function helpText() {
|
|
797
|
+
return `Usage:
|
|
798
|
+
pnpm dlx @anvia/sandbox create-image [options]
|
|
799
|
+
|
|
800
|
+
Options:
|
|
801
|
+
--name <slug> Image profile name
|
|
802
|
+
--runtime <node|bun|python> Runtime to include; repeatable
|
|
803
|
+
--feature <artifacts|playwright>
|
|
804
|
+
Curated feature to include; repeatable
|
|
805
|
+
--apt <package> Additional apt package; repeatable
|
|
806
|
+
--npm <package[@version]> Additional npm package; repeatable
|
|
807
|
+
--uv <requirement> Additional Python package installed with uv; repeatable
|
|
808
|
+
--tag <tag> Local image tag
|
|
809
|
+
--output <directory> Generated context directory
|
|
810
|
+
--node-version <version> Override Node.js version
|
|
811
|
+
--pnpm-version <version> Override pnpm version
|
|
812
|
+
--bun-version <version> Override Bun version
|
|
813
|
+
--python-version <version> Override Python version
|
|
814
|
+
--uv-version <version> Override uv version
|
|
815
|
+
--playwright-version <version> Override Playwright and browser image version
|
|
816
|
+
--docker-path <path> Docker CLI path (default: docker)
|
|
817
|
+
--no-build Generate files without building
|
|
818
|
+
--dry-run Print generated files without writing or building
|
|
819
|
+
--force Regenerate a context previously created by this CLI
|
|
820
|
+
-h, --help Show this help
|
|
821
|
+
|
|
822
|
+
Examples:
|
|
823
|
+
pnpm dlx @anvia/sandbox create-image
|
|
824
|
+
pnpm dlx @anvia/sandbox create-image --name reports --feature artifacts
|
|
825
|
+
pnpm dlx @anvia/sandbox create-image --name browser --runtime bun --feature playwright`;
|
|
826
|
+
}
|
|
827
|
+
var invokedPath = process.argv[1] === void 0 ? void 0 : path.resolve(process.argv[1]);
|
|
828
|
+
if (invokedPath !== void 0 && invokedPath === fileURLToPath(import.meta.url)) {
|
|
829
|
+
runCli().then((code) => {
|
|
830
|
+
process.exitCode = code;
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
export {
|
|
834
|
+
runCli
|
|
835
|
+
};
|
|
836
|
+
//# sourceMappingURL=cli.js.map
|