@sandblocks/cli 0.5.0-b.0 → 0.5.0-b.2
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 +280 -162
- package/dist/cli.js.map +5 -4
- package/dist/runtime.js +118 -0
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -11735,7 +11735,7 @@ var init_src = __esm(() => {
|
|
|
11735
11735
|
init_src();
|
|
11736
11736
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11737
11737
|
import { lstat, readFile as readFile8, stat as stat3, writeFile as writeFile5 } from "fs/promises";
|
|
11738
|
-
import
|
|
11738
|
+
import path6 from "path";
|
|
11739
11739
|
|
|
11740
11740
|
// src/hooks.ts
|
|
11741
11741
|
import { chmod, copyFile, mkdir, readFile as readFile3, rm, writeFile } from "fs/promises";
|
|
@@ -11877,11 +11877,125 @@ async function text(file) {
|
|
|
11877
11877
|
}
|
|
11878
11878
|
}
|
|
11879
11879
|
|
|
11880
|
+
// src/runtime.ts
|
|
11881
|
+
import { spawn } from "child_process";
|
|
11882
|
+
import { access, chmod as chmod2, mkdir as mkdir2, readdir, rm as rm2 } from "fs/promises";
|
|
11883
|
+
import path4 from "path";
|
|
11884
|
+
import { pathToFileURL } from "url";
|
|
11885
|
+
var RUNTIME_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;
|
|
11886
|
+
var NODE_ENV_RUNTIME = {
|
|
11887
|
+
production: "prod",
|
|
11888
|
+
development: "dev",
|
|
11889
|
+
test: "local"
|
|
11890
|
+
};
|
|
11891
|
+
async function runRuntimeCommand(args) {
|
|
11892
|
+
const [action, ...rest] = args;
|
|
11893
|
+
if (action !== "prepare")
|
|
11894
|
+
throw new Error("runtime action must be prepare");
|
|
11895
|
+
const options = parse(rest);
|
|
11896
|
+
const root = path4.resolve(options.positionals[0] ?? process.cwd());
|
|
11897
|
+
const runtime = normalizedRuntime(option(options, "runtime") ?? process.env.SANDBLOCKS_RUNTIME ?? "development" ?? "development");
|
|
11898
|
+
const targetRoot = contained(root, option(options, "target") ?? ".locker", "runtime target");
|
|
11899
|
+
const runtimeRoot = path4.join(targetRoot, runtime);
|
|
11900
|
+
const config = contained(root, option(options, "config") ?? "config/dotlocker.config.ts", "Dotlocker config");
|
|
11901
|
+
const dotlocker = path4.resolve(root, option(options, "dotlocker") ?? path4.join("node_modules", ".bin", "dotlocker"));
|
|
11902
|
+
if (!await exists2(config))
|
|
11903
|
+
throw new Error(`Dotlocker config does not exist: ${config}`);
|
|
11904
|
+
if (!await exists2(dotlocker)) {
|
|
11905
|
+
throw new Error("Dotlocker is not installed; add @dotlocker/dotlocker to the workspace");
|
|
11906
|
+
}
|
|
11907
|
+
await rm2(targetRoot, { recursive: true, force: true });
|
|
11908
|
+
await mkdir2(runtimeRoot, { recursive: true, mode: 493 });
|
|
11909
|
+
await run(dotlocker, ["pull", "--config", config, "--runtime", runtime, "--out", runtimeRoot], root);
|
|
11910
|
+
const envFile = path4.join(runtimeRoot, ".env");
|
|
11911
|
+
if (!await exists2(envFile)) {
|
|
11912
|
+
throw new Error(`Dotlocker did not materialize ${path4.relative(root, envFile)}`);
|
|
11913
|
+
}
|
|
11914
|
+
await normalizePermissions(targetRoot);
|
|
11915
|
+
console.log(`runtime ${runtime}
|
|
11916
|
+
environment ${path4.relative(root, envFile)}`);
|
|
11917
|
+
}
|
|
11918
|
+
function run(executable, args, cwd) {
|
|
11919
|
+
return new Promise((resolve, reject) => {
|
|
11920
|
+
const child = spawn(executable, args, { cwd, env: process.env, stdio: "inherit" });
|
|
11921
|
+
child.once("error", reject);
|
|
11922
|
+
child.once("exit", (code, signal) => {
|
|
11923
|
+
if (code === 0)
|
|
11924
|
+
resolve();
|
|
11925
|
+
else
|
|
11926
|
+
reject(new Error(`Dotlocker pull failed (${signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`})`));
|
|
11927
|
+
});
|
|
11928
|
+
});
|
|
11929
|
+
}
|
|
11930
|
+
async function exists2(file) {
|
|
11931
|
+
try {
|
|
11932
|
+
await access(file);
|
|
11933
|
+
return true;
|
|
11934
|
+
} catch {
|
|
11935
|
+
return false;
|
|
11936
|
+
}
|
|
11937
|
+
}
|
|
11938
|
+
function normalizedRuntime(value) {
|
|
11939
|
+
const runtime = NODE_ENV_RUNTIME[value.trim()] ?? value.trim();
|
|
11940
|
+
if (!RUNTIME_PATTERN.test(runtime))
|
|
11941
|
+
throw new Error(`invalid runtime '${value}'`);
|
|
11942
|
+
return runtime;
|
|
11943
|
+
}
|
|
11944
|
+
function contained(root, value, label) {
|
|
11945
|
+
const resolved = path4.resolve(root, value);
|
|
11946
|
+
const relative = path4.relative(root, resolved);
|
|
11947
|
+
if (!relative || !relative.startsWith("..") && !path4.isAbsolute(relative))
|
|
11948
|
+
return resolved;
|
|
11949
|
+
throw new Error(`${label} must remain within the repository`);
|
|
11950
|
+
}
|
|
11951
|
+
async function normalizePermissions(directory) {
|
|
11952
|
+
await chmod2(directory, 493);
|
|
11953
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
11954
|
+
const child = path4.join(directory, entry.name);
|
|
11955
|
+
if (entry.isSymbolicLink())
|
|
11956
|
+
throw new Error("Dotlocker output must not contain symbolic links");
|
|
11957
|
+
if (entry.isDirectory())
|
|
11958
|
+
await normalizePermissions(child);
|
|
11959
|
+
else if (entry.isFile())
|
|
11960
|
+
await chmod2(child, 420);
|
|
11961
|
+
else
|
|
11962
|
+
throw new Error("Dotlocker output contains an unsupported file type");
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11965
|
+
function parse(args) {
|
|
11966
|
+
const options = { positionals: [] };
|
|
11967
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
11968
|
+
const value = args[index] ?? "";
|
|
11969
|
+
if (!value.startsWith("--")) {
|
|
11970
|
+
options.positionals.push(value);
|
|
11971
|
+
continue;
|
|
11972
|
+
}
|
|
11973
|
+
const key = value.slice(2);
|
|
11974
|
+
const next = args[index + 1];
|
|
11975
|
+
if (next && !next.startsWith("--")) {
|
|
11976
|
+
options[key] = next;
|
|
11977
|
+
index += 1;
|
|
11978
|
+
} else
|
|
11979
|
+
options[key] = true;
|
|
11980
|
+
}
|
|
11981
|
+
return options;
|
|
11982
|
+
}
|
|
11983
|
+
function option(options, key) {
|
|
11984
|
+
const value = options[key];
|
|
11985
|
+
return typeof value === "string" ? value : undefined;
|
|
11986
|
+
}
|
|
11987
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path4.resolve(process.argv[1])).href) {
|
|
11988
|
+
runRuntimeCommand(process.argv.slice(2)).catch((error) => {
|
|
11989
|
+
console.error(`sandblocks-runtime: ${error instanceof Error ? error.message : String(error)}`);
|
|
11990
|
+
process.exitCode = 1;
|
|
11991
|
+
});
|
|
11992
|
+
}
|
|
11993
|
+
|
|
11880
11994
|
// src/sandbox.ts
|
|
11881
11995
|
init_src();
|
|
11882
11996
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
11883
|
-
import { chmod as
|
|
11884
|
-
import
|
|
11997
|
+
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile4, rename, rm as rm3 } from "fs/promises";
|
|
11998
|
+
import path5 from "path";
|
|
11885
11999
|
var COMMANDS = new Set([
|
|
11886
12000
|
"up",
|
|
11887
12001
|
"create",
|
|
@@ -11933,7 +12047,7 @@ async function up(args, dependencies) {
|
|
|
11933
12047
|
}
|
|
11934
12048
|
async function create(args, dependencies) {
|
|
11935
12049
|
const options = dependencies.parse(args);
|
|
11936
|
-
const root =
|
|
12050
|
+
const root = path5.resolve(options.positionals[0] ?? process.cwd());
|
|
11937
12051
|
await loadLocalEnvironment(root);
|
|
11938
12052
|
const environment = dependencies.option(options, "environment") ?? "preview";
|
|
11939
12053
|
const file = statePath(root, environment);
|
|
@@ -11942,7 +12056,7 @@ async function create(args, dependencies) {
|
|
|
11942
12056
|
const loaded = await dependencies.load(root, dependencies.option(options, "config") ?? dependencies.option(options, "manifest"));
|
|
11943
12057
|
const preview = selectPreview(loaded, environment);
|
|
11944
12058
|
const projectId = dependencies.option(options, "project") ?? process.env.SANDBLOCKS_PROJECT_ID ?? "default";
|
|
11945
|
-
const repository = loaded.manifest.name ??
|
|
12059
|
+
const repository = loaded.manifest.name ?? path5.basename(root).toLowerCase();
|
|
11946
12060
|
const sandboxId = randomUUID();
|
|
11947
12061
|
const workspaceId = `${sandboxId.replaceAll("-", "").slice(0, 8)}-${repository}`.slice(0, 63);
|
|
11948
12062
|
const { apiUrl, apiKey } = client(options, dependencies.option, projectId);
|
|
@@ -12010,7 +12124,7 @@ async function status2(args, dependencies) {
|
|
|
12010
12124
|
console.log(JSON.stringify({ state: redactedState(context.state), sandbox: sandbox.sandbox, session: session.session }, null, 2));
|
|
12011
12125
|
return;
|
|
12012
12126
|
}
|
|
12013
|
-
console.log(`state ${
|
|
12127
|
+
console.log(`state ${path5.relative(context.root, context.file)}`);
|
|
12014
12128
|
console.log(`sandbox ${context.state.sandboxId} (${sandbox.sandbox.status})`);
|
|
12015
12129
|
console.log(`workspace ${context.state.workspaceId}`);
|
|
12016
12130
|
console.log(`host ${context.state.hostId}`);
|
|
@@ -12023,12 +12137,12 @@ async function deploy(args, dependencies) {
|
|
|
12023
12137
|
}
|
|
12024
12138
|
async function withDeploymentLock(args, dependencies, action) {
|
|
12025
12139
|
const options = dependencies.parse(args);
|
|
12026
|
-
const root =
|
|
12140
|
+
const root = path5.resolve(options.positionals[0] ?? process.cwd());
|
|
12027
12141
|
const environment = dependencies.option(options, "environment") ?? "preview";
|
|
12028
12142
|
const lock = `${statePath(root, environment)}.deploy.lock`;
|
|
12029
|
-
await
|
|
12143
|
+
await mkdir3(path5.dirname(lock), { recursive: true, mode: 448 });
|
|
12030
12144
|
try {
|
|
12031
|
-
await
|
|
12145
|
+
await mkdir3(lock);
|
|
12032
12146
|
} catch (error) {
|
|
12033
12147
|
if (error.code === "EEXIST") {
|
|
12034
12148
|
throw new Error(`sandbox '${environment}' deployment is already running`);
|
|
@@ -12038,7 +12152,7 @@ async function withDeploymentLock(args, dependencies, action) {
|
|
|
12038
12152
|
try {
|
|
12039
12153
|
await action();
|
|
12040
12154
|
} finally {
|
|
12041
|
-
await
|
|
12155
|
+
await rm3(lock, { recursive: true, force: true });
|
|
12042
12156
|
}
|
|
12043
12157
|
}
|
|
12044
12158
|
async function deployUnlocked(args, dependencies) {
|
|
@@ -12271,7 +12385,7 @@ async function down(args, dependencies) {
|
|
|
12271
12385
|
await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/sandboxes/${context.state.sandboxId}`, { method: "DELETE" }).catch(() => {
|
|
12272
12386
|
return;
|
|
12273
12387
|
});
|
|
12274
|
-
await
|
|
12388
|
+
await rm3(context.file, { force: true });
|
|
12275
12389
|
console.log(`sandbox ${context.state.sandboxId} destroyed`);
|
|
12276
12390
|
}
|
|
12277
12391
|
async function resolveAssignedTargetHost(context, fallback) {
|
|
@@ -12406,7 +12520,7 @@ async function dataOperation(context, action, body) {
|
|
|
12406
12520
|
}
|
|
12407
12521
|
async function getContext(args, dependencies) {
|
|
12408
12522
|
const options = dependencies.parse(args);
|
|
12409
|
-
const root =
|
|
12523
|
+
const root = path5.resolve(options.positionals[0] ?? process.cwd());
|
|
12410
12524
|
await loadLocalEnvironment(root);
|
|
12411
12525
|
const environment = dependencies.option(options, "environment") ?? "preview";
|
|
12412
12526
|
const file = statePath(root, environment);
|
|
@@ -12422,9 +12536,9 @@ async function getContext(args, dependencies) {
|
|
|
12422
12536
|
function selectPreview(loaded, id2) {
|
|
12423
12537
|
return resolveEnvironment(loaded.manifest, id2);
|
|
12424
12538
|
}
|
|
12425
|
-
function client(options,
|
|
12426
|
-
const apiUrl = (
|
|
12427
|
-
const apiKey =
|
|
12539
|
+
function client(options, option2, projectId) {
|
|
12540
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
12541
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY ?? "";
|
|
12428
12542
|
if (!apiUrl || !apiKey)
|
|
12429
12543
|
throw new Error("SANDBLOCKS_API_URL and SANDBLOCKS_API_KEY are required");
|
|
12430
12544
|
return { apiUrl, apiKey, projectId };
|
|
@@ -12525,7 +12639,7 @@ async function waitLease(context, operationId) {
|
|
|
12525
12639
|
throw new Error(`sandbox operation ${operationId} timed out`);
|
|
12526
12640
|
}
|
|
12527
12641
|
async function loadLocalEnvironment(root) {
|
|
12528
|
-
const file =
|
|
12642
|
+
const file = path5.join(root, ".sandblocks", "config.env");
|
|
12529
12643
|
let contents;
|
|
12530
12644
|
try {
|
|
12531
12645
|
contents = await readFile4(file, "utf8");
|
|
@@ -12589,15 +12703,15 @@ function resolvedServiceDomain(sandboxId, deploymentId, service, preview) {
|
|
|
12589
12703
|
function statePath(root, environment) {
|
|
12590
12704
|
if (!/^[a-z][a-z0-9-]{0,62}$/.test(environment))
|
|
12591
12705
|
throw new Error("sandbox environment is invalid");
|
|
12592
|
-
return
|
|
12706
|
+
return path5.join(root, ".sandblocks", `sandbox-${environment}.json`);
|
|
12593
12707
|
}
|
|
12594
12708
|
async function writeState(file, state) {
|
|
12595
|
-
await
|
|
12596
|
-
await
|
|
12709
|
+
await mkdir3(path5.dirname(file), { recursive: true, mode: 448 });
|
|
12710
|
+
await chmod3(path5.dirname(file), 448);
|
|
12597
12711
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
12598
12712
|
await Bun.write(temporary, `${JSON.stringify(state, null, 2)}
|
|
12599
12713
|
`, { mode: 384 });
|
|
12600
|
-
await
|
|
12714
|
+
await chmod3(temporary, 384);
|
|
12601
12715
|
await rename(temporary, file);
|
|
12602
12716
|
}
|
|
12603
12717
|
function redactedState(state) {
|
|
@@ -12613,15 +12727,15 @@ function printOutput(operation) {
|
|
|
12613
12727
|
}
|
|
12614
12728
|
|
|
12615
12729
|
// src/sdk.ts
|
|
12616
|
-
import { mkdir as
|
|
12730
|
+
import { mkdir as mkdir5, readFile as readFile7, rm as rm6, writeFile as writeFile4 } from "fs/promises";
|
|
12617
12731
|
import { dirname as dirname2, resolve as resolve7 } from "path";
|
|
12618
12732
|
|
|
12619
12733
|
// ../sdk/dist/index.js
|
|
12620
12734
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
12621
|
-
import { appendFile, cp, mkdir as
|
|
12735
|
+
import { appendFile, cp, mkdir as mkdir4, readFile as readFile5, rm as rm4 } from "fs/promises";
|
|
12622
12736
|
import { dirname, resolve } from "path";
|
|
12623
|
-
import { spawn } from "child_process";
|
|
12624
|
-
import { access, mkdir as mkdir22, writeFile as writeFile2 } from "fs/promises";
|
|
12737
|
+
import { spawn as spawn2 } from "child_process";
|
|
12738
|
+
import { access as access2, mkdir as mkdir22, writeFile as writeFile2 } from "fs/promises";
|
|
12625
12739
|
import { resolve as resolve2 } from "path";
|
|
12626
12740
|
|
|
12627
12741
|
class CommandAgent {
|
|
@@ -12877,7 +12991,7 @@ async function executeProcess(command, options = {}) {
|
|
|
12877
12991
|
const [executable, ...args] = command;
|
|
12878
12992
|
if (!executable)
|
|
12879
12993
|
throw new Error("command executable is required");
|
|
12880
|
-
const child =
|
|
12994
|
+
const child = spawn2(executable, args, {
|
|
12881
12995
|
cwd: options.cwd ?? options.hostCwd,
|
|
12882
12996
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
12883
12997
|
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
@@ -13062,7 +13176,7 @@ class SandblocksSandbox {
|
|
|
13062
13176
|
"--force",
|
|
13063
13177
|
this.git.worktree
|
|
13064
13178
|
]);
|
|
13065
|
-
await
|
|
13179
|
+
await rm4(this.git.worktree, { recursive: true, force: true });
|
|
13066
13180
|
}
|
|
13067
13181
|
}
|
|
13068
13182
|
async[Symbol.asyncDispose]() {
|
|
@@ -13092,7 +13206,7 @@ async function createSandbox(options) {
|
|
|
13092
13206
|
for (const file of options.copyToWorktree ?? []) {
|
|
13093
13207
|
const source = resolve(hostCwd, file);
|
|
13094
13208
|
const destination = resolve(git3.runtimeCwd, file);
|
|
13095
|
-
await
|
|
13209
|
+
await mkdir4(dirname(destination), { recursive: true });
|
|
13096
13210
|
await withTimeout(cp(source, destination, { recursive: true }), options.timeouts?.copyToWorktreeMs ?? 60000, `copying '${file}'`);
|
|
13097
13211
|
}
|
|
13098
13212
|
const id2 = options.id ?? randomUUID2();
|
|
@@ -13134,7 +13248,7 @@ async function connectSandbox(options) {
|
|
|
13134
13248
|
const runtime = await options.provider.reconnect({ id: options.id, cwd });
|
|
13135
13249
|
return new SandblocksSandbox(runtime, { provider: options.provider, cwd, retention: options.retention ?? "always" }, { hostCwd: cwd, runtimeCwd: cwd });
|
|
13136
13250
|
}
|
|
13137
|
-
async function
|
|
13251
|
+
async function run2(options) {
|
|
13138
13252
|
const sandbox = await createSandbox(options);
|
|
13139
13253
|
try {
|
|
13140
13254
|
return await sandbox.run(options);
|
|
@@ -13161,7 +13275,7 @@ async function prepareGit(hostCwd, strategy, timeoutMs = 30000) {
|
|
|
13161
13275
|
}
|
|
13162
13276
|
const branch = strategy.name ?? `sandblocks/${randomUUID2().slice(0, 8)}`;
|
|
13163
13277
|
const worktree = resolve(hostCwd, ".sandblocks", "worktrees", branch.replace(/[^A-Za-z0-9_.-]/g, "-"));
|
|
13164
|
-
await
|
|
13278
|
+
await mkdir4(dirname(worktree), { recursive: true });
|
|
13165
13279
|
await checked(["git", "-C", hostCwd, "worktree", "add", "-B", branch, worktree, "HEAD"], timeoutMs);
|
|
13166
13280
|
return {
|
|
13167
13281
|
hostCwd,
|
|
@@ -13244,7 +13358,7 @@ function createLogger(options, cwd) {
|
|
|
13244
13358
|
process.stdout.write(event.type === "text" ? event.text : event.type === "raw" ? `${event.line}
|
|
13245
13359
|
` : line);
|
|
13246
13360
|
if (config.type !== "stdout" && config.type !== "silent") {
|
|
13247
|
-
await
|
|
13361
|
+
await mkdir4(dirname(file), { recursive: true });
|
|
13248
13362
|
await appendFile(file, line);
|
|
13249
13363
|
}
|
|
13250
13364
|
try {
|
|
@@ -13315,12 +13429,12 @@ SANDBLOCKS_API_KEY=
|
|
|
13315
13429
|
};
|
|
13316
13430
|
const written = [];
|
|
13317
13431
|
for (const [relative, content] of Object.entries(files)) {
|
|
13318
|
-
const
|
|
13319
|
-
await mkdir22(resolve2(
|
|
13320
|
-
if (!options.force && await
|
|
13432
|
+
const path6 = resolve2(root, relative);
|
|
13433
|
+
await mkdir22(resolve2(path6, ".."), { recursive: true });
|
|
13434
|
+
if (!options.force && await access2(path6).then(() => true).catch(() => false))
|
|
13321
13435
|
continue;
|
|
13322
|
-
await writeFile2(
|
|
13323
|
-
written.push(
|
|
13436
|
+
await writeFile2(path6, content, { mode: relative.includes(".env") ? 384 : 420 });
|
|
13437
|
+
written.push(path6);
|
|
13324
13438
|
}
|
|
13325
13439
|
return written;
|
|
13326
13440
|
}
|
|
@@ -13328,7 +13442,7 @@ SANDBLOCKS_API_KEY=
|
|
|
13328
13442
|
// ../sdk/dist/providers/docker.js
|
|
13329
13443
|
import { realpath as realpath3 } from "fs/promises";
|
|
13330
13444
|
import { resolve as resolve3 } from "path";
|
|
13331
|
-
import { spawn as
|
|
13445
|
+
import { spawn as spawn3 } from "child_process";
|
|
13332
13446
|
async function executeProcess2(command, options = {}) {
|
|
13333
13447
|
if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
|
|
13334
13448
|
throw new Error("command must contain safe non-empty arguments");
|
|
@@ -13337,7 +13451,7 @@ async function executeProcess2(command, options = {}) {
|
|
|
13337
13451
|
const [executable, ...args] = command;
|
|
13338
13452
|
if (!executable)
|
|
13339
13453
|
throw new Error("command executable is required");
|
|
13340
|
-
const child =
|
|
13454
|
+
const child = spawn3(executable, args, {
|
|
13341
13455
|
cwd: options.cwd ?? options.hostCwd,
|
|
13342
13456
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
13343
13457
|
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
@@ -13390,12 +13504,12 @@ async function executeProcess2(command, options = {}) {
|
|
|
13390
13504
|
options.signal?.removeEventListener("abort", stop);
|
|
13391
13505
|
}
|
|
13392
13506
|
}
|
|
13393
|
-
function expandHome(
|
|
13394
|
-
if (
|
|
13395
|
-
return process.env.HOME ??
|
|
13396
|
-
if (
|
|
13397
|
-
return `${process.env.HOME ?? "~"}/${
|
|
13398
|
-
return
|
|
13507
|
+
function expandHome(path6) {
|
|
13508
|
+
if (path6 === "~")
|
|
13509
|
+
return process.env.HOME ?? path6;
|
|
13510
|
+
if (path6.startsWith("~/"))
|
|
13511
|
+
return `${process.env.HOME ?? "~"}/${path6.slice(2)}`;
|
|
13512
|
+
return path6;
|
|
13399
13513
|
}
|
|
13400
13514
|
var capabilities = {
|
|
13401
13515
|
bindMounts: true,
|
|
@@ -13574,7 +13688,7 @@ class DockerProvider extends OciProvider {
|
|
|
13574
13688
|
// ../sdk/dist/providers/podman.js
|
|
13575
13689
|
import { realpath as realpath4 } from "fs/promises";
|
|
13576
13690
|
import { resolve as resolve5 } from "path";
|
|
13577
|
-
import { spawn as
|
|
13691
|
+
import { spawn as spawn4 } from "child_process";
|
|
13578
13692
|
async function executeProcess3(command, options = {}) {
|
|
13579
13693
|
if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
|
|
13580
13694
|
throw new Error("command must contain safe non-empty arguments");
|
|
@@ -13583,7 +13697,7 @@ async function executeProcess3(command, options = {}) {
|
|
|
13583
13697
|
const [executable, ...args] = command;
|
|
13584
13698
|
if (!executable)
|
|
13585
13699
|
throw new Error("command executable is required");
|
|
13586
|
-
const child =
|
|
13700
|
+
const child = spawn4(executable, args, {
|
|
13587
13701
|
cwd: options.cwd ?? options.hostCwd,
|
|
13588
13702
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
13589
13703
|
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
@@ -13636,12 +13750,12 @@ async function executeProcess3(command, options = {}) {
|
|
|
13636
13750
|
options.signal?.removeEventListener("abort", stop);
|
|
13637
13751
|
}
|
|
13638
13752
|
}
|
|
13639
|
-
function expandHome2(
|
|
13640
|
-
if (
|
|
13641
|
-
return process.env.HOME ??
|
|
13642
|
-
if (
|
|
13643
|
-
return `${process.env.HOME ?? "~"}/${
|
|
13644
|
-
return
|
|
13753
|
+
function expandHome2(path6) {
|
|
13754
|
+
if (path6 === "~")
|
|
13755
|
+
return process.env.HOME ?? path6;
|
|
13756
|
+
if (path6.startsWith("~/"))
|
|
13757
|
+
return `${process.env.HOME ?? "~"}/${path6.slice(2)}`;
|
|
13758
|
+
return path6;
|
|
13645
13759
|
}
|
|
13646
13760
|
var capabilities2 = {
|
|
13647
13761
|
bindMounts: true,
|
|
@@ -13819,10 +13933,10 @@ class PodmanProvider extends OciProvider2 {
|
|
|
13819
13933
|
|
|
13820
13934
|
// ../sdk/dist/providers/remote.js
|
|
13821
13935
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
13822
|
-
import { mkdtemp, readFile as readFile6, rm as
|
|
13936
|
+
import { mkdtemp, readFile as readFile6, rm as rm5, stat as stat2, writeFile as writeFile3 } from "fs/promises";
|
|
13823
13937
|
import { tmpdir } from "os";
|
|
13824
13938
|
import { join } from "path";
|
|
13825
|
-
import { spawn as
|
|
13939
|
+
import { spawn as spawn5 } from "child_process";
|
|
13826
13940
|
async function executeProcess4(command, options = {}) {
|
|
13827
13941
|
if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
|
|
13828
13942
|
throw new Error("command must contain safe non-empty arguments");
|
|
@@ -13831,7 +13945,7 @@ async function executeProcess4(command, options = {}) {
|
|
|
13831
13945
|
const [executable, ...args] = command;
|
|
13832
13946
|
if (!executable)
|
|
13833
13947
|
throw new Error("command executable is required");
|
|
13834
|
-
const child =
|
|
13948
|
+
const child = spawn5(executable, args, {
|
|
13835
13949
|
cwd: options.cwd ?? options.hostCwd,
|
|
13836
13950
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
13837
13951
|
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
@@ -14097,12 +14211,12 @@ class RemoteSandblocksProvider {
|
|
|
14097
14211
|
if (result.exitCode !== 0)
|
|
14098
14212
|
throw new Error(result.stderr || "remote operation failed");
|
|
14099
14213
|
}
|
|
14100
|
-
async request(
|
|
14214
|
+
async request(path6, init = {}) {
|
|
14101
14215
|
const headers = new Headers(init.headers);
|
|
14102
14216
|
headers.set("x-sandblocks-api-key", this.options.apiKey);
|
|
14103
14217
|
if (init.body)
|
|
14104
14218
|
headers.set("content-type", "application/json");
|
|
14105
|
-
const response = await this.fetchImpl(`${this.baseUrl}${
|
|
14219
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path6}`, { ...init, headers });
|
|
14106
14220
|
const body = await response.json().catch(() => ({}));
|
|
14107
14221
|
if (!response.ok)
|
|
14108
14222
|
throw new Error(body.error ?? `Sandblocks request failed (${response.status})`);
|
|
@@ -14126,7 +14240,7 @@ async function sourceBundle(cwd) {
|
|
|
14126
14240
|
]);
|
|
14127
14241
|
if (listed.exitCode !== 0)
|
|
14128
14242
|
throw new Error("remote source directory must be a Git worktree");
|
|
14129
|
-
const files = listed.stdout.split("\x00").filter((
|
|
14243
|
+
const files = listed.stdout.split("\x00").filter((path6) => path6 && path6 !== ".sandblocks" && !path6.startsWith(".sandblocks/") && path6 !== ".git");
|
|
14130
14244
|
if (!files.length || files.length > 50000)
|
|
14131
14245
|
throw new Error("remote source bundle file count is invalid");
|
|
14132
14246
|
await writeFile3(list, `${files.join("\x00")}\x00`);
|
|
@@ -14137,14 +14251,14 @@ async function sourceBundle(cwd) {
|
|
|
14137
14251
|
throw new Error("remote source bundle exceeds 512 MiB");
|
|
14138
14252
|
return new Uint8Array(await readFile6(file));
|
|
14139
14253
|
} finally {
|
|
14140
|
-
await
|
|
14254
|
+
await rm5(temporary, { recursive: true, force: true });
|
|
14141
14255
|
}
|
|
14142
14256
|
}
|
|
14143
14257
|
|
|
14144
14258
|
// ../sdk/dist/providers/unsafe-host.js
|
|
14145
14259
|
import { cp as cp2, realpath as realpath5 } from "fs/promises";
|
|
14146
14260
|
import { resolve as resolve6 } from "path";
|
|
14147
|
-
import { spawn as
|
|
14261
|
+
import { spawn as spawn6 } from "child_process";
|
|
14148
14262
|
async function executeProcess5(command, options = {}) {
|
|
14149
14263
|
if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
|
|
14150
14264
|
throw new Error("command must contain safe non-empty arguments");
|
|
@@ -14153,7 +14267,7 @@ async function executeProcess5(command, options = {}) {
|
|
|
14153
14267
|
const [executable, ...args] = command;
|
|
14154
14268
|
if (!executable)
|
|
14155
14269
|
throw new Error("command executable is required");
|
|
14156
|
-
const child =
|
|
14270
|
+
const child = spawn6(executable, args, {
|
|
14157
14271
|
cwd: options.cwd ?? options.hostCwd,
|
|
14158
14272
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
14159
14273
|
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
@@ -14266,7 +14380,7 @@ async function runSdkCommand(args, helpers) {
|
|
|
14266
14380
|
const provider = providerFrom(options, helpers, cwd);
|
|
14267
14381
|
const agent = agentFrom(options, helpers);
|
|
14268
14382
|
const outputTag = helpers.option(options, "output-tag");
|
|
14269
|
-
const result = await
|
|
14383
|
+
const result = await run2({
|
|
14270
14384
|
provider,
|
|
14271
14385
|
cwd,
|
|
14272
14386
|
image: helpers.option(options, "image"),
|
|
@@ -14317,7 +14431,7 @@ async function sdkSandbox(args, helpers, defaultCwd) {
|
|
|
14317
14431
|
hooks: hooksFrom(options),
|
|
14318
14432
|
copyToWorktree: multi(options["copy-to-worktree"])
|
|
14319
14433
|
});
|
|
14320
|
-
await
|
|
14434
|
+
await mkdir5(dirname2(stateFile), { recursive: true });
|
|
14321
14435
|
const strategy = branchFrom(options, helpers);
|
|
14322
14436
|
const worktree = strategy.type === "worktree" || strategy.type === "merge-to-head" ? sandbox2.cwd : undefined;
|
|
14323
14437
|
await writeFile4(stateFile, JSON.stringify({
|
|
@@ -14392,7 +14506,7 @@ async function sdkSandbox(args, helpers, defaultCwd) {
|
|
|
14392
14506
|
const child = Bun.spawn(["git", "-C", state.cwd, "worktree", "remove", "--force", state.worktree]);
|
|
14393
14507
|
await child.exited;
|
|
14394
14508
|
}
|
|
14395
|
-
await
|
|
14509
|
+
await rm6(stateFile, { force: true });
|
|
14396
14510
|
console.log(`sandbox ${state.id} destroyed`);
|
|
14397
14511
|
} else
|
|
14398
14512
|
throw new Error("unsupported sdk sandbox command");
|
|
@@ -14504,10 +14618,10 @@ function hooksFrom(options) {
|
|
|
14504
14618
|
};
|
|
14505
14619
|
}
|
|
14506
14620
|
async function withSdkConfig(options, cwd, file) {
|
|
14507
|
-
const
|
|
14508
|
-
if (!await Bun.file(
|
|
14621
|
+
const path6 = resolve7(cwd, file ?? ".sandblocks/config.json");
|
|
14622
|
+
if (!await Bun.file(path6).exists())
|
|
14509
14623
|
return options;
|
|
14510
|
-
const config = JSON.parse(await readFile7(
|
|
14624
|
+
const config = JSON.parse(await readFile7(path6, "utf8"));
|
|
14511
14625
|
const mapped = {};
|
|
14512
14626
|
if (typeof config.provider === "string")
|
|
14513
14627
|
mapped.provider = config.provider;
|
|
@@ -14575,6 +14689,8 @@ var HELP = `Sandblocks CLI
|
|
|
14575
14689
|
sandblocks whoami [directory] [--api-url <url>] [--api-key <key>] [--json]
|
|
14576
14690
|
sandblocks hooks <install|status|uninstall> [directory]
|
|
14577
14691
|
sandblocks hooks run post-commit [directory]
|
|
14692
|
+
sandblocks runtime prepare [directory] [--runtime <id>]
|
|
14693
|
+
[--config <path>] [--target <path>]
|
|
14578
14694
|
sandblocks stack <list|get|settings|settings-set> --project <id> [--stack <id>] [--file <json>] [--json]
|
|
14579
14695
|
sandblocks app <list|get|settings|settings-set> --project <id> [--stack <id>] [--app <id>] [--file <json>] [--json]
|
|
14580
14696
|
sandblocks secret <list|set|delete> --project <id> --stack <id> --app <id>
|
|
@@ -14617,7 +14733,7 @@ Low-level deploy remains available with --workspace, --sandbox, --host,
|
|
|
14617
14733
|
Authentication: --api-key or SANDBLOCKS_API_KEY
|
|
14618
14734
|
API endpoint: --api-url or SANDBLOCKS_API_URL
|
|
14619
14735
|
`;
|
|
14620
|
-
async function
|
|
14736
|
+
async function run3(argv = process.argv.slice(2)) {
|
|
14621
14737
|
await loadCliEnvironment(process.cwd());
|
|
14622
14738
|
const [command = "help", ...args] = argv;
|
|
14623
14739
|
if (command === "configure" || command === "init")
|
|
@@ -14634,8 +14750,8 @@ async function run2(argv = process.argv.slice(2)) {
|
|
|
14634
14750
|
await runHooksCommand(args, {
|
|
14635
14751
|
load,
|
|
14636
14752
|
runSandbox: (hookCommand2, hookArgs) => runStatefulSandboxCommand(hookCommand2, hookArgs, {
|
|
14637
|
-
parse,
|
|
14638
|
-
option,
|
|
14753
|
+
parse: parse2,
|
|
14754
|
+
option: option2,
|
|
14639
14755
|
load,
|
|
14640
14756
|
source: async (root) => {
|
|
14641
14757
|
const bundle = await createSourceTar(root);
|
|
@@ -14643,6 +14759,8 @@ async function run2(argv = process.argv.slice(2)) {
|
|
|
14643
14759
|
}
|
|
14644
14760
|
})
|
|
14645
14761
|
});
|
|
14762
|
+
else if (command === "runtime")
|
|
14763
|
+
await runRuntimeCommand(args);
|
|
14646
14764
|
else if (command === "stack" || command === "app")
|
|
14647
14765
|
await resource(command, args);
|
|
14648
14766
|
else if (command === "secret")
|
|
@@ -14650,7 +14768,7 @@ async function run2(argv = process.argv.slice(2)) {
|
|
|
14650
14768
|
else if (command === "sandbox")
|
|
14651
14769
|
await sandbox(args);
|
|
14652
14770
|
else if (command === "sdk")
|
|
14653
|
-
await runSdkCommand(args, { parse, option });
|
|
14771
|
+
await runSdkCommand(args, { parse: parse2, option: option2 });
|
|
14654
14772
|
else {
|
|
14655
14773
|
console.log(HELP);
|
|
14656
14774
|
if (!["help", "--help", "-h"].includes(command))
|
|
@@ -14658,9 +14776,9 @@ async function run2(argv = process.argv.slice(2)) {
|
|
|
14658
14776
|
}
|
|
14659
14777
|
}
|
|
14660
14778
|
async function configure(args) {
|
|
14661
|
-
const options =
|
|
14662
|
-
const root =
|
|
14663
|
-
const manifestPath =
|
|
14779
|
+
const options = parse2(args);
|
|
14780
|
+
const root = path6.resolve(options.positionals[0] ?? process.cwd());
|
|
14781
|
+
const manifestPath = path6.join(root, "sandblocks.yml");
|
|
14664
14782
|
if (await Bun.file(manifestPath).exists() && !options.force) {
|
|
14665
14783
|
const loaded2 = await load(root, manifestPathOption(options));
|
|
14666
14784
|
console.log(`Sandblocks already configured
|
|
@@ -14668,8 +14786,8 @@ async function configure(args) {
|
|
|
14668
14786
|
digest: ${loaded2.digest}`);
|
|
14669
14787
|
return;
|
|
14670
14788
|
}
|
|
14671
|
-
const packageJson = await json(
|
|
14672
|
-
const name = slug(
|
|
14789
|
+
const packageJson = await json(path6.join(root, "package.json"));
|
|
14790
|
+
const name = slug(option2(options, "name") ?? path6.basename(root));
|
|
14673
14791
|
const checks = ["build", "typecheck", "test"].filter((script) => packageJson?.scripts?.[script]);
|
|
14674
14792
|
const manager = packageJson?.packageManager?.split("@")[0] ?? "npm";
|
|
14675
14793
|
const command = (script) => `[${manager}, ${manager === "npm" ? "run, " : ""}${script}]`;
|
|
@@ -14690,8 +14808,8 @@ async function configure(args) {
|
|
|
14690
14808
|
digest: ${loaded.digest}`);
|
|
14691
14809
|
}
|
|
14692
14810
|
async function validate(args) {
|
|
14693
|
-
const options =
|
|
14694
|
-
const root =
|
|
14811
|
+
const options = parse2(args);
|
|
14812
|
+
const root = path6.resolve(options.positionals[0] ?? process.cwd());
|
|
14695
14813
|
const loaded = await load(root, manifestPathOption(options));
|
|
14696
14814
|
if (options.json) {
|
|
14697
14815
|
console.log(JSON.stringify({ manifest: loaded.entrypoint, digest: loaded.digest, sources: loaded.sources }, null, 2));
|
|
@@ -14701,11 +14819,11 @@ async function validate(args) {
|
|
|
14701
14819
|
digest ${loaded.digest}`);
|
|
14702
14820
|
}
|
|
14703
14821
|
async function register(args) {
|
|
14704
|
-
const options =
|
|
14705
|
-
const root =
|
|
14706
|
-
const projectId =
|
|
14707
|
-
const apiUrl = (
|
|
14708
|
-
const apiKey =
|
|
14822
|
+
const options = parse2(args);
|
|
14823
|
+
const root = path6.resolve(options.positionals[0] ?? process.cwd());
|
|
14824
|
+
const projectId = option2(options, "project");
|
|
14825
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
14826
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
14709
14827
|
if (!projectId)
|
|
14710
14828
|
throw new Error("register requires --project");
|
|
14711
14829
|
if (!apiUrl)
|
|
@@ -14713,7 +14831,7 @@ async function register(args) {
|
|
|
14713
14831
|
if (!apiKey)
|
|
14714
14832
|
throw new Error("register requires --api-key or SANDBLOCKS_API_KEY");
|
|
14715
14833
|
const loaded = await load(root, manifestPathOption(options));
|
|
14716
|
-
const repositoryUrl =
|
|
14834
|
+
const repositoryUrl = option2(options, "repository-url") ?? await git3(root, ["config", "--get", "remote.origin.url"]);
|
|
14717
14835
|
if (!repositoryUrl)
|
|
14718
14836
|
throw new Error("register requires --repository-url or git remote.origin.url");
|
|
14719
14837
|
const branch = await git3(root, ["branch", "--show-current"]) || "main";
|
|
@@ -14721,7 +14839,7 @@ async function register(args) {
|
|
|
14721
14839
|
method: "POST",
|
|
14722
14840
|
headers: { "content-type": "application/json", "x-sandblocks-api-key": apiKey },
|
|
14723
14841
|
body: JSON.stringify({
|
|
14724
|
-
slug: loaded.manifest.name ?? slug(
|
|
14842
|
+
slug: loaded.manifest.name ?? slug(path6.basename(root)),
|
|
14725
14843
|
name: loaded.manifest.name,
|
|
14726
14844
|
url: repositoryUrl,
|
|
14727
14845
|
defaultBranch: branch,
|
|
@@ -14737,24 +14855,24 @@ async function register(args) {
|
|
|
14737
14855
|
console.log(response.status === 409 ? "repository already registered" : "repository registered");
|
|
14738
14856
|
}
|
|
14739
14857
|
async function doctor(args) {
|
|
14740
|
-
const options =
|
|
14741
|
-
const root =
|
|
14858
|
+
const options = parse2(args);
|
|
14859
|
+
const root = path6.resolve(options.positionals[0] ?? process.cwd());
|
|
14742
14860
|
const loaded = await load(root, manifestPathOption(options));
|
|
14743
14861
|
console.log(`manifest ok (${loaded.entrypoint})`);
|
|
14744
|
-
const apiUrl = (
|
|
14862
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
14745
14863
|
if (apiUrl) {
|
|
14746
14864
|
const response = await fetch(`${apiUrl}/v1/health`);
|
|
14747
14865
|
if (!response.ok)
|
|
14748
14866
|
throw new Error(`API health failed (${response.status})`);
|
|
14749
14867
|
console.log(`api ok (${apiUrl})`);
|
|
14750
14868
|
}
|
|
14751
|
-
const gitDetected = await stat3(
|
|
14869
|
+
const gitDetected = await stat3(path6.join(root, ".git")).then(() => true).catch(() => false);
|
|
14752
14870
|
console.log(`git ${gitDetected ? "ok" : "not detected"}`);
|
|
14753
14871
|
}
|
|
14754
14872
|
async function whoami(args) {
|
|
14755
|
-
const options =
|
|
14756
|
-
const apiUrl = (
|
|
14757
|
-
const apiKey =
|
|
14873
|
+
const options = parse2(args);
|
|
14874
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
14875
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
14758
14876
|
if (!apiUrl)
|
|
14759
14877
|
throw new Error("whoami requires --api-url or SANDBLOCKS_API_URL");
|
|
14760
14878
|
if (!apiKey)
|
|
@@ -14777,12 +14895,12 @@ async function whoami(args) {
|
|
|
14777
14895
|
}
|
|
14778
14896
|
async function resource(kind, args) {
|
|
14779
14897
|
const [action = "list", ...rest] = args;
|
|
14780
|
-
const options =
|
|
14781
|
-
const projectId =
|
|
14782
|
-
const stack =
|
|
14783
|
-
const app =
|
|
14784
|
-
const apiUrl = (
|
|
14785
|
-
const apiKey =
|
|
14898
|
+
const options = parse2(rest);
|
|
14899
|
+
const projectId = option2(options, "project");
|
|
14900
|
+
const stack = option2(options, "stack");
|
|
14901
|
+
const app = option2(options, "app");
|
|
14902
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
14903
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
14786
14904
|
if (!projectId)
|
|
14787
14905
|
throw new Error(`${kind} ${action} requires --project`);
|
|
14788
14906
|
if (!apiUrl)
|
|
@@ -14802,10 +14920,10 @@ async function resource(kind, args) {
|
|
|
14802
14920
|
throw new Error(`unsupported ${kind} command`);
|
|
14803
14921
|
let body;
|
|
14804
14922
|
if (action === "settings-set") {
|
|
14805
|
-
const file =
|
|
14923
|
+
const file = option2(options, "file");
|
|
14806
14924
|
if (!file)
|
|
14807
14925
|
throw new Error(`${kind} settings-set requires --file <json>`);
|
|
14808
|
-
const values = JSON.parse(await readFile8(
|
|
14926
|
+
const values = JSON.parse(await readFile8(path6.resolve(file), "utf8"));
|
|
14809
14927
|
const current = await sandblocksRequest(apiUrl, apiKey, requestPath);
|
|
14810
14928
|
body = await sandblocksRequest(apiUrl, apiKey, requestPath, {
|
|
14811
14929
|
method: "PUT",
|
|
@@ -14820,12 +14938,12 @@ async function resource(kind, args) {
|
|
|
14820
14938
|
}
|
|
14821
14939
|
async function secret(args) {
|
|
14822
14940
|
const [action = "list", ...rest] = args;
|
|
14823
|
-
const options =
|
|
14824
|
-
const projectId =
|
|
14825
|
-
const stack =
|
|
14826
|
-
const app =
|
|
14827
|
-
const apiUrl = (
|
|
14828
|
-
const apiKey =
|
|
14941
|
+
const options = parse2(rest);
|
|
14942
|
+
const projectId = option2(options, "project");
|
|
14943
|
+
const stack = option2(options, "stack");
|
|
14944
|
+
const app = option2(options, "app");
|
|
14945
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
14946
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
14829
14947
|
if (!projectId || !stack || !app)
|
|
14830
14948
|
throw new Error("secret commands require --project, --stack, and --app");
|
|
14831
14949
|
if (!apiUrl || !apiKey)
|
|
@@ -14835,9 +14953,9 @@ async function secret(args) {
|
|
|
14835
14953
|
if (action === "list")
|
|
14836
14954
|
body = await sandblocksRequest(apiUrl, apiKey, root);
|
|
14837
14955
|
else if (action === "set") {
|
|
14838
|
-
const environment =
|
|
14839
|
-
const name =
|
|
14840
|
-
const value =
|
|
14956
|
+
const environment = option2(options, "environment");
|
|
14957
|
+
const name = option2(options, "name");
|
|
14958
|
+
const value = option2(options, "value");
|
|
14841
14959
|
if (!environment || !name || value === undefined)
|
|
14842
14960
|
throw new Error("secret set requires --environment, --name, and --value");
|
|
14843
14961
|
body = await sandblocksRequest(apiUrl, apiKey, root, {
|
|
@@ -14845,7 +14963,7 @@ async function secret(args) {
|
|
|
14845
14963
|
body: JSON.stringify({ environment, name, value })
|
|
14846
14964
|
});
|
|
14847
14965
|
} else if (action === "delete") {
|
|
14848
|
-
const id2 =
|
|
14966
|
+
const id2 = option2(options, "id");
|
|
14849
14967
|
if (!id2)
|
|
14850
14968
|
throw new Error("secret delete requires --id");
|
|
14851
14969
|
await sandblocksRequest(apiUrl, apiKey, `${root}/${encodeURIComponent(id2)}`, { method: "DELETE" });
|
|
@@ -14885,8 +15003,8 @@ async function sandbox(args) {
|
|
|
14885
15003
|
const [subcommand, ...rest] = args;
|
|
14886
15004
|
if (isStatefulSandboxCommand(subcommand) && !(subcommand === "deploy" && rest.includes("--workspace"))) {
|
|
14887
15005
|
return runStatefulSandboxCommand(subcommand, rest, {
|
|
14888
|
-
parse,
|
|
14889
|
-
option,
|
|
15006
|
+
parse: parse2,
|
|
15007
|
+
option: option2,
|
|
14890
15008
|
load,
|
|
14891
15009
|
source: async (root2) => {
|
|
14892
15010
|
const bundle2 = await createSourceTar(root2);
|
|
@@ -14902,12 +15020,12 @@ async function sandbox(args) {
|
|
|
14902
15020
|
return sandboxAgent(rest);
|
|
14903
15021
|
if (subcommand !== "import")
|
|
14904
15022
|
throw new Error("unsupported sandbox command");
|
|
14905
|
-
const options =
|
|
14906
|
-
const root =
|
|
14907
|
-
const projectId =
|
|
14908
|
-
const workspaceId =
|
|
14909
|
-
const apiUrl = (
|
|
14910
|
-
const apiKey =
|
|
15023
|
+
const options = parse2(rest);
|
|
15024
|
+
const root = path6.resolve(options.positionals[0] ?? process.cwd());
|
|
15025
|
+
const projectId = option2(options, "project");
|
|
15026
|
+
const workspaceId = option2(options, "workspace");
|
|
15027
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
15028
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
14911
15029
|
if (!projectId)
|
|
14912
15030
|
throw new Error("sandbox import requires --project");
|
|
14913
15031
|
if (!workspaceId)
|
|
@@ -14924,8 +15042,8 @@ async function sandbox(args) {
|
|
|
14924
15042
|
"content-length": String(bundle.byteLength),
|
|
14925
15043
|
"x-sandblocks-api-key": apiKey,
|
|
14926
15044
|
"x-sandblocks-workspace-id": workspaceId,
|
|
14927
|
-
"x-sandblocks-worker-pool":
|
|
14928
|
-
"idempotency-key":
|
|
15045
|
+
"x-sandblocks-worker-pool": option2(options, "pool") ?? "sandbox-development",
|
|
15046
|
+
"idempotency-key": option2(options, "idempotency-key") ?? `local-import:${workspaceId}`
|
|
14929
15047
|
},
|
|
14930
15048
|
body: new Blob([Uint8Array.from(bundle)])
|
|
14931
15049
|
});
|
|
@@ -14945,12 +15063,12 @@ async function sandbox(args) {
|
|
|
14945
15063
|
}
|
|
14946
15064
|
}
|
|
14947
15065
|
async function sandboxAgent(args) {
|
|
14948
|
-
const options =
|
|
15066
|
+
const options = parse2(args);
|
|
14949
15067
|
const action = options.positionals[0] ?? "list";
|
|
14950
|
-
const projectId =
|
|
14951
|
-
const sandboxId =
|
|
14952
|
-
const apiUrl = (
|
|
14953
|
-
const apiKey =
|
|
15068
|
+
const projectId = option2(options, "project");
|
|
15069
|
+
const sandboxId = option2(options, "sandbox");
|
|
15070
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
15071
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
14954
15072
|
if (!projectId || !sandboxId)
|
|
14955
15073
|
throw new Error("sandbox agent requires --project and --sandbox");
|
|
14956
15074
|
if (!apiUrl || !apiKey)
|
|
@@ -14958,25 +15076,25 @@ async function sandboxAgent(args) {
|
|
|
14958
15076
|
const collection = `/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions`;
|
|
14959
15077
|
let result;
|
|
14960
15078
|
if (action === "run") {
|
|
14961
|
-
const promptFile =
|
|
14962
|
-
const prompt =
|
|
15079
|
+
const promptFile = option2(options, "prompt-file");
|
|
15080
|
+
const prompt = option2(options, "prompt") ?? (promptFile ? await readFile8(path6.resolve(promptFile), "utf8") : undefined);
|
|
14963
15081
|
if (!prompt)
|
|
14964
15082
|
throw new Error("sandbox agent run requires --prompt or --prompt-file");
|
|
14965
|
-
const repository =
|
|
14966
|
-
const provider =
|
|
14967
|
-
const model =
|
|
15083
|
+
const repository = option2(options, "repository");
|
|
15084
|
+
const provider = option2(options, "provider") ?? "codex";
|
|
15085
|
+
const model = option2(options, "model");
|
|
14968
15086
|
if (!repository || !model)
|
|
14969
15087
|
throw new Error("sandbox agent run requires --repository and --model");
|
|
14970
15088
|
result = await sandblocksRequest(apiUrl, apiKey, collection, {
|
|
14971
15089
|
method: "POST",
|
|
14972
|
-
headers: { "idempotency-key":
|
|
15090
|
+
headers: { "idempotency-key": option2(options, "idempotency-key") ?? randomUUID4() },
|
|
14973
15091
|
body: JSON.stringify({
|
|
14974
15092
|
repository,
|
|
14975
15093
|
provider,
|
|
14976
15094
|
model,
|
|
14977
15095
|
prompt,
|
|
14978
|
-
credentialEnvironment:
|
|
14979
|
-
timeoutSeconds:
|
|
15096
|
+
credentialEnvironment: option2(options, "credential-env"),
|
|
15097
|
+
timeoutSeconds: option2(options, "timeout-seconds") ? Number(option2(options, "timeout-seconds")) : undefined
|
|
14980
15098
|
})
|
|
14981
15099
|
});
|
|
14982
15100
|
if (options.wait)
|
|
@@ -14984,12 +15102,12 @@ async function sandboxAgent(args) {
|
|
|
14984
15102
|
} else if (action === "list") {
|
|
14985
15103
|
result = await sandblocksRequest(apiUrl, apiKey, collection);
|
|
14986
15104
|
} else if (action === "get" || action === "cancel" || action === "redeploy") {
|
|
14987
|
-
const sessionId =
|
|
15105
|
+
const sessionId = option2(options, "session");
|
|
14988
15106
|
if (!sessionId)
|
|
14989
15107
|
throw new Error(`sandbox agent ${action} requires --session`);
|
|
14990
15108
|
result = await sandblocksRequest(apiUrl, apiKey, `${collection}/${encodeURIComponent(sessionId)}${action === "cancel" ? "/cancel" : action === "redeploy" ? "/redeploy" : ""}`, action === "cancel" || action === "redeploy" ? {
|
|
14991
15109
|
method: "POST",
|
|
14992
|
-
headers: action === "redeploy" ? { "idempotency-key":
|
|
15110
|
+
headers: action === "redeploy" ? { "idempotency-key": option2(options, "idempotency-key") ?? randomUUID4() } : undefined
|
|
14993
15111
|
} : undefined);
|
|
14994
15112
|
} else
|
|
14995
15113
|
throw new Error("sandbox agent action must be run, list, get, cancel, or redeploy");
|
|
@@ -15002,20 +15120,20 @@ async function sandboxAgent(args) {
|
|
|
15002
15120
|
}
|
|
15003
15121
|
}
|
|
15004
15122
|
async function waitForAgentSession(apiUrl, apiKey, projectId, sandboxId, sessionId) {
|
|
15005
|
-
const
|
|
15123
|
+
const path7 = `/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions/${encodeURIComponent(sessionId)}`;
|
|
15006
15124
|
for (;; ) {
|
|
15007
|
-
const result = await sandblocksRequest(apiUrl, apiKey,
|
|
15125
|
+
const result = await sandblocksRequest(apiUrl, apiKey, path7);
|
|
15008
15126
|
if (!["queued", "running"].includes(String(result.session?.state)))
|
|
15009
15127
|
return result.session;
|
|
15010
15128
|
await Bun.sleep(1000);
|
|
15011
15129
|
}
|
|
15012
15130
|
}
|
|
15013
15131
|
async function sandboxPromote(args) {
|
|
15014
|
-
const options =
|
|
15015
|
-
const projectId =
|
|
15016
|
-
const sandboxId =
|
|
15017
|
-
const apiUrl = (
|
|
15018
|
-
const apiKey =
|
|
15132
|
+
const options = parse2(args);
|
|
15133
|
+
const projectId = option2(options, "project");
|
|
15134
|
+
const sandboxId = option2(options, "sandbox");
|
|
15135
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
15136
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
15019
15137
|
if (!projectId)
|
|
15020
15138
|
throw new Error("sandbox promote requires --project");
|
|
15021
15139
|
if (!sandboxId)
|
|
@@ -15057,7 +15175,7 @@ async function createSourceTar(root) {
|
|
|
15057
15175
|
if (name.includes("\x00") || name.includes("\\") || name.startsWith("/") || name.split("/").includes("..")) {
|
|
15058
15176
|
throw new Error(`local source path is unsafe: ${name}`);
|
|
15059
15177
|
}
|
|
15060
|
-
const file =
|
|
15178
|
+
const file = path6.join(root, name);
|
|
15061
15179
|
let info;
|
|
15062
15180
|
try {
|
|
15063
15181
|
info = await lstat(file);
|
|
@@ -15150,25 +15268,25 @@ function tarOctal(header, offset, length, value) {
|
|
|
15150
15268
|
header[offset + length - 1] = 0;
|
|
15151
15269
|
}
|
|
15152
15270
|
async function sandboxDeploy(args) {
|
|
15153
|
-
const options =
|
|
15154
|
-
const root =
|
|
15155
|
-
const projectId =
|
|
15156
|
-
const workspaceId =
|
|
15157
|
-
const sandboxId =
|
|
15158
|
-
const hostId =
|
|
15159
|
-
const targetHost =
|
|
15160
|
-
const specPath =
|
|
15161
|
-
const apiUrl = (
|
|
15162
|
-
const apiKey =
|
|
15271
|
+
const options = parse2(args);
|
|
15272
|
+
const root = path6.resolve(options.positionals[0] ?? process.cwd());
|
|
15273
|
+
const projectId = option2(options, "project");
|
|
15274
|
+
const workspaceId = option2(options, "workspace");
|
|
15275
|
+
const sandboxId = option2(options, "sandbox");
|
|
15276
|
+
const hostId = option2(options, "host");
|
|
15277
|
+
const targetHost = option2(options, "target-host");
|
|
15278
|
+
const specPath = option2(options, "spec");
|
|
15279
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
15280
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
15163
15281
|
if (!projectId || !workspaceId || !sandboxId || !hostId || !targetHost || !specPath) {
|
|
15164
15282
|
throw new Error("sandbox deploy requires --project, --workspace, --sandbox, --host, --target-host, and --spec");
|
|
15165
15283
|
}
|
|
15166
15284
|
if (!apiUrl || !apiKey)
|
|
15167
15285
|
throw new Error("sandbox deploy requires Sandblocks API URL and key");
|
|
15168
|
-
const spec = JSON.parse(await readFile8(
|
|
15286
|
+
const spec = JSON.parse(await readFile8(path6.resolve(root, specPath), "utf8"));
|
|
15169
15287
|
if (!Array.isArray(spec.services) || !spec.services.length)
|
|
15170
15288
|
throw new Error("preview spec requires services");
|
|
15171
|
-
const deploymentId =
|
|
15289
|
+
const deploymentId = option2(options, "deployment") ?? randomUUID4();
|
|
15172
15290
|
const submitted = await sandblocksRequest(apiUrl, apiKey, `/v1/projects/${encodeURIComponent(projectId)}/operations`, {
|
|
15173
15291
|
method: "POST",
|
|
15174
15292
|
headers: { "idempotency-key": `preview:${deploymentId}:deploy` },
|
|
@@ -15249,7 +15367,7 @@ async function waitForOperation(apiUrl, apiKey, id2) {
|
|
|
15249
15367
|
async function load(root, entrypoint) {
|
|
15250
15368
|
return loadSandblocksManifest({ repositoryRoot: root, ...entrypoint ? { entrypoint } : {} });
|
|
15251
15369
|
}
|
|
15252
|
-
function
|
|
15370
|
+
function parse2(args) {
|
|
15253
15371
|
const output = { positionals: [] };
|
|
15254
15372
|
for (let index = 0;index < args.length; index++) {
|
|
15255
15373
|
const value = args[index];
|
|
@@ -15287,11 +15405,11 @@ function parse(args) {
|
|
|
15287
15405
|
}
|
|
15288
15406
|
return output;
|
|
15289
15407
|
}
|
|
15290
|
-
function
|
|
15408
|
+
function option2(options, key) {
|
|
15291
15409
|
return typeof options[key] === "string" ? options[key] : undefined;
|
|
15292
15410
|
}
|
|
15293
15411
|
function manifestPathOption(options) {
|
|
15294
|
-
return
|
|
15412
|
+
return option2(options, "config") ?? option2(options, "manifest");
|
|
15295
15413
|
}
|
|
15296
15414
|
function slug(value) {
|
|
15297
15415
|
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -15314,7 +15432,7 @@ async function git3(cwd, args) {
|
|
|
15314
15432
|
async function loadCliEnvironment(root) {
|
|
15315
15433
|
let contents;
|
|
15316
15434
|
try {
|
|
15317
|
-
contents = await readFile8(
|
|
15435
|
+
contents = await readFile8(path6.join(root, ".sandblocks", "config.env"), "utf8");
|
|
15318
15436
|
} catch {
|
|
15319
15437
|
return;
|
|
15320
15438
|
}
|
|
@@ -15330,15 +15448,15 @@ async function loadCliEnvironment(root) {
|
|
|
15330
15448
|
}
|
|
15331
15449
|
if (import.meta.main) {
|
|
15332
15450
|
try {
|
|
15333
|
-
await
|
|
15451
|
+
await run3();
|
|
15334
15452
|
} catch (error) {
|
|
15335
15453
|
console.error(`sandblocks: ${error instanceof Error ? error.message : String(error)}`);
|
|
15336
15454
|
process.exitCode = 1;
|
|
15337
15455
|
}
|
|
15338
15456
|
}
|
|
15339
15457
|
export {
|
|
15340
|
-
|
|
15341
|
-
parse
|
|
15458
|
+
run3 as run,
|
|
15459
|
+
parse2 as parse
|
|
15342
15460
|
};
|
|
15343
15461
|
|
|
15344
|
-
//# debugId=
|
|
15462
|
+
//# debugId=96A687238FDBFB8A64756E2164756E21
|