@sandblocks/cli 0.5.0-b.5 → 0.5.0-b.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2242 -1669
- package/dist/cli.js.map +12 -12
- package/dist/runtime-bin.js +2 -2
- package/package.json +2 -8
package/dist/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ var __export = (target, all) => {
|
|
|
18
18
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
19
19
|
var __require = import.meta.require;
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// packages/libs/manifest/src/project-graph.ts
|
|
22
22
|
function compileProjectGraph(manifest) {
|
|
23
23
|
return {
|
|
24
24
|
version: 2,
|
|
@@ -89,7 +89,7 @@ function normalizeCommand(command) {
|
|
|
89
89
|
return typeof command === "string" ? ["sh", "-lc", command] : [...command];
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
//
|
|
92
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
93
93
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
94
94
|
const t = typeof data;
|
|
95
95
|
switch (t) {
|
|
@@ -220,7 +220,7 @@ var init_util = __esm(() => {
|
|
|
220
220
|
]);
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
-
//
|
|
223
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
224
224
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
225
225
|
const json = JSON.stringify(obj, null, 2);
|
|
226
226
|
return json.replace(/"([^"]+)":/g, "$1:");
|
|
@@ -341,7 +341,7 @@ var init_ZodError = __esm(() => {
|
|
|
341
341
|
};
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
-
//
|
|
344
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
345
345
|
var errorMap = (issue, _ctx) => {
|
|
346
346
|
let message;
|
|
347
347
|
switch (issue.code) {
|
|
@@ -448,7 +448,7 @@ var init_en = __esm(() => {
|
|
|
448
448
|
en_default = errorMap;
|
|
449
449
|
});
|
|
450
450
|
|
|
451
|
-
//
|
|
451
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
452
452
|
function setErrorMap(map) {
|
|
453
453
|
overrideErrorMap = map;
|
|
454
454
|
}
|
|
@@ -461,7 +461,7 @@ var init_errors = __esm(() => {
|
|
|
461
461
|
overrideErrorMap = en_default;
|
|
462
462
|
});
|
|
463
463
|
|
|
464
|
-
//
|
|
464
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
465
465
|
function addIssueToContext(ctx, issueData) {
|
|
466
466
|
const overrideMap = getErrorMap();
|
|
467
467
|
const issue = makeIssue({
|
|
@@ -566,10 +566,10 @@ var init_parseUtil = __esm(() => {
|
|
|
566
566
|
});
|
|
567
567
|
});
|
|
568
568
|
|
|
569
|
-
//
|
|
569
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
|
|
570
570
|
var init_typeAliases = () => {};
|
|
571
571
|
|
|
572
|
-
//
|
|
572
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
573
573
|
var errorUtil;
|
|
574
574
|
var init_errorUtil = __esm(() => {
|
|
575
575
|
(function(errorUtil2) {
|
|
@@ -578,7 +578,7 @@ var init_errorUtil = __esm(() => {
|
|
|
578
578
|
})(errorUtil || (errorUtil = {}));
|
|
579
579
|
});
|
|
580
580
|
|
|
581
|
-
//
|
|
581
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
582
582
|
class ParseInputLazyPath {
|
|
583
583
|
constructor(parent, value, path, key) {
|
|
584
584
|
this._cachedPath = [];
|
|
@@ -3929,7 +3929,7 @@ var init_types = __esm(() => {
|
|
|
3929
3929
|
NEVER = INVALID;
|
|
3930
3930
|
});
|
|
3931
3931
|
|
|
3932
|
-
//
|
|
3932
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
3933
3933
|
var exports_external = {};
|
|
3934
3934
|
__export(exports_external, {
|
|
3935
3935
|
void: () => voidType,
|
|
@@ -4049,13 +4049,13 @@ var init_external = __esm(() => {
|
|
|
4049
4049
|
init_ZodError();
|
|
4050
4050
|
});
|
|
4051
4051
|
|
|
4052
|
-
//
|
|
4052
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
|
|
4053
4053
|
var init_zod = __esm(() => {
|
|
4054
4054
|
init_external();
|
|
4055
4055
|
init_external();
|
|
4056
4056
|
});
|
|
4057
4057
|
|
|
4058
|
-
//
|
|
4058
|
+
// packages/libs/manifest/src/schema.ts
|
|
4059
4059
|
function keyedCollection(schema) {
|
|
4060
4060
|
return exports_external.record(id, schema).transform((values) => Object.entries(values).map(([entryId, value]) => ({ id: entryId, ...value })));
|
|
4061
4061
|
}
|
|
@@ -4280,7 +4280,7 @@ var init_schema = __esm(() => {
|
|
|
4280
4280
|
SandblocksManifestV2Schema = CompleteSandblocksManifestV2Schema.superRefine(validateReferences);
|
|
4281
4281
|
});
|
|
4282
4282
|
|
|
4283
|
-
//
|
|
4283
|
+
// packages/libs/manifest/src/resolve.ts
|
|
4284
4284
|
function resolveProjectGraph(manifest) {
|
|
4285
4285
|
return compileProjectGraph(manifest);
|
|
4286
4286
|
}
|
|
@@ -4381,9 +4381,16 @@ function executionService(service) {
|
|
|
4381
4381
|
}
|
|
4382
4382
|
var init_resolve = () => {};
|
|
4383
4383
|
|
|
4384
|
-
//
|
|
4384
|
+
// packages/libs/manifest/src/images.ts
|
|
4385
4385
|
import { readFile, realpath, stat } from "fs/promises";
|
|
4386
4386
|
import path from "path";
|
|
4387
|
+
function resolvePrebuiltImage(family, registry = process.env.SANDBLOCKS_PREBUILT_IMAGE_REGISTRY) {
|
|
4388
|
+
const image = SANDBLOCKS_PREBUILT_IMAGES[family];
|
|
4389
|
+
const configured = registry?.trim().replace(/\/$/, "");
|
|
4390
|
+
if (!configured)
|
|
4391
|
+
return image;
|
|
4392
|
+
return `${configured}/${image.slice(image.lastIndexOf("/") + 1)}`;
|
|
4393
|
+
}
|
|
4387
4394
|
async function detectPrebuiltWorkspaceImage(repositoryRoot, workspaceRoot = ".") {
|
|
4388
4395
|
const root = await realpath(repositoryRoot);
|
|
4389
4396
|
const workspace = await containedDirectory(root, workspaceRoot);
|
|
@@ -4414,7 +4421,7 @@ async function detectPrebuiltWorkspaceImage(repositoryRoot, workspaceRoot = ".")
|
|
|
4414
4421
|
return selection("node", "default Node.js environment");
|
|
4415
4422
|
}
|
|
4416
4423
|
function selection(family, evidence) {
|
|
4417
|
-
return { family, image:
|
|
4424
|
+
return { family, image: resolvePrebuiltImage(family), evidence };
|
|
4418
4425
|
}
|
|
4419
4426
|
async function containedDirectory(repositoryRoot, relative) {
|
|
4420
4427
|
const candidate = path.resolve(repositoryRoot, relative);
|
|
@@ -4480,14 +4487,14 @@ function relativeEvidence(repositoryRoot, directory, file) {
|
|
|
4480
4487
|
var SANDBLOCKS_PREBUILT_IMAGES, LOCKFILES;
|
|
4481
4488
|
var init_images = __esm(() => {
|
|
4482
4489
|
SANDBLOCKS_PREBUILT_IMAGES = {
|
|
4483
|
-
bun: "
|
|
4484
|
-
pnpm: "
|
|
4485
|
-
npm: "
|
|
4486
|
-
yarn: "
|
|
4487
|
-
corepack: "
|
|
4488
|
-
node: "
|
|
4489
|
-
"node-native": "
|
|
4490
|
-
playwright: "
|
|
4490
|
+
bun: "100.114.99.85:5000/sandblocks/executor-bun:1.0.0",
|
|
4491
|
+
pnpm: "100.114.99.85:5000/sandblocks/executor-pnpm:1.2.0",
|
|
4492
|
+
npm: "100.114.99.85:5000/sandblocks/executor-npm:1.0.0",
|
|
4493
|
+
yarn: "100.114.99.85:5000/sandblocks/executor-yarn:1.0.0",
|
|
4494
|
+
corepack: "100.114.99.85:5000/sandblocks/executor-corepack:1.0.0",
|
|
4495
|
+
node: "100.114.99.85:5000/sandblocks/executor-node:1.0.0",
|
|
4496
|
+
"node-native": "100.114.99.85:5000/sandblocks/executor-node-native:1.0.0",
|
|
4497
|
+
playwright: "100.114.99.85:5000/sandblocks/executor-playwright:1.0.0"
|
|
4491
4498
|
};
|
|
4492
4499
|
LOCKFILES = [
|
|
4493
4500
|
{ file: "bun.lock", family: "bun" },
|
|
@@ -4499,7 +4506,7 @@ var init_images = __esm(() => {
|
|
|
4499
4506
|
];
|
|
4500
4507
|
});
|
|
4501
4508
|
|
|
4502
|
-
//
|
|
4509
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
|
|
4503
4510
|
var require_identity = __commonJS((exports) => {
|
|
4504
4511
|
var ALIAS = Symbol.for("yaml.alias");
|
|
4505
4512
|
var DOC = Symbol.for("yaml.document");
|
|
@@ -4553,7 +4560,7 @@ var require_identity = __commonJS((exports) => {
|
|
|
4553
4560
|
exports.isSeq = isSeq;
|
|
4554
4561
|
});
|
|
4555
4562
|
|
|
4556
|
-
//
|
|
4563
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/visit.js
|
|
4557
4564
|
var require_visit = __commonJS((exports) => {
|
|
4558
4565
|
var identity = require_identity();
|
|
4559
4566
|
var BREAK = Symbol("break visit");
|
|
@@ -4708,7 +4715,7 @@ var require_visit = __commonJS((exports) => {
|
|
|
4708
4715
|
exports.visitAsync = visitAsync;
|
|
4709
4716
|
});
|
|
4710
4717
|
|
|
4711
|
-
//
|
|
4718
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/doc/directives.js
|
|
4712
4719
|
var require_directives = __commonJS((exports) => {
|
|
4713
4720
|
var identity = require_identity();
|
|
4714
4721
|
var visit = require_visit();
|
|
@@ -4860,7 +4867,7 @@ var require_directives = __commonJS((exports) => {
|
|
|
4860
4867
|
exports.Directives = Directives;
|
|
4861
4868
|
});
|
|
4862
4869
|
|
|
4863
|
-
//
|
|
4870
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/doc/anchors.js
|
|
4864
4871
|
var require_anchors = __commonJS((exports) => {
|
|
4865
4872
|
var identity = require_identity();
|
|
4866
4873
|
var visit = require_visit();
|
|
@@ -4922,7 +4929,7 @@ var require_anchors = __commonJS((exports) => {
|
|
|
4922
4929
|
exports.findNewAnchor = findNewAnchor;
|
|
4923
4930
|
});
|
|
4924
4931
|
|
|
4925
|
-
//
|
|
4932
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/doc/applyReviver.js
|
|
4926
4933
|
var require_applyReviver = __commonJS((exports) => {
|
|
4927
4934
|
function applyReviver(reviver, obj, key, val) {
|
|
4928
4935
|
if (val && typeof val === "object") {
|
|
@@ -4969,7 +4976,7 @@ var require_applyReviver = __commonJS((exports) => {
|
|
|
4969
4976
|
exports.applyReviver = applyReviver;
|
|
4970
4977
|
});
|
|
4971
4978
|
|
|
4972
|
-
//
|
|
4979
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/toJS.js
|
|
4973
4980
|
var require_toJS = __commonJS((exports) => {
|
|
4974
4981
|
var identity = require_identity();
|
|
4975
4982
|
function toJS(value, arg, ctx) {
|
|
@@ -4996,7 +5003,7 @@ var require_toJS = __commonJS((exports) => {
|
|
|
4996
5003
|
exports.toJS = toJS;
|
|
4997
5004
|
});
|
|
4998
5005
|
|
|
4999
|
-
//
|
|
5006
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/Node.js
|
|
5000
5007
|
var require_Node = __commonJS((exports) => {
|
|
5001
5008
|
var applyReviver = require_applyReviver();
|
|
5002
5009
|
var identity = require_identity();
|
|
@@ -5033,7 +5040,7 @@ var require_Node = __commonJS((exports) => {
|
|
|
5033
5040
|
exports.NodeBase = NodeBase;
|
|
5034
5041
|
});
|
|
5035
5042
|
|
|
5036
|
-
//
|
|
5043
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/Alias.js
|
|
5037
5044
|
var require_Alias = __commonJS((exports) => {
|
|
5038
5045
|
var anchors = require_anchors();
|
|
5039
5046
|
var visit = require_visit();
|
|
@@ -5143,7 +5150,7 @@ var require_Alias = __commonJS((exports) => {
|
|
|
5143
5150
|
exports.Alias = Alias;
|
|
5144
5151
|
});
|
|
5145
5152
|
|
|
5146
|
-
//
|
|
5153
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/Scalar.js
|
|
5147
5154
|
var require_Scalar = __commonJS((exports) => {
|
|
5148
5155
|
var identity = require_identity();
|
|
5149
5156
|
var Node = require_Node();
|
|
@@ -5171,7 +5178,7 @@ var require_Scalar = __commonJS((exports) => {
|
|
|
5171
5178
|
exports.isScalarValue = isScalarValue;
|
|
5172
5179
|
});
|
|
5173
5180
|
|
|
5174
|
-
//
|
|
5181
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/doc/createNode.js
|
|
5175
5182
|
var require_createNode = __commonJS((exports) => {
|
|
5176
5183
|
var Alias = require_Alias();
|
|
5177
5184
|
var identity = require_identity();
|
|
@@ -5243,7 +5250,7 @@ var require_createNode = __commonJS((exports) => {
|
|
|
5243
5250
|
exports.createNode = createNode;
|
|
5244
5251
|
});
|
|
5245
5252
|
|
|
5246
|
-
//
|
|
5253
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/Collection.js
|
|
5247
5254
|
var require_Collection = __commonJS((exports) => {
|
|
5248
5255
|
var createNode = require_createNode();
|
|
5249
5256
|
var identity = require_identity();
|
|
@@ -5358,7 +5365,7 @@ var require_Collection = __commonJS((exports) => {
|
|
|
5358
5365
|
exports.isEmptyPath = isEmptyPath;
|
|
5359
5366
|
});
|
|
5360
5367
|
|
|
5361
|
-
//
|
|
5368
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/stringify/stringifyComment.js
|
|
5362
5369
|
var require_stringifyComment = __commonJS((exports) => {
|
|
5363
5370
|
var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#");
|
|
5364
5371
|
function indentComment(comment, indent) {
|
|
@@ -5375,7 +5382,7 @@ var require_stringifyComment = __commonJS((exports) => {
|
|
|
5375
5382
|
exports.stringifyComment = stringifyComment;
|
|
5376
5383
|
});
|
|
5377
5384
|
|
|
5378
|
-
//
|
|
5385
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/stringify/foldFlowLines.js
|
|
5379
5386
|
var require_foldFlowLines = __commonJS((exports) => {
|
|
5380
5387
|
var FOLD_FLOW = "flow";
|
|
5381
5388
|
var FOLD_BLOCK = "block";
|
|
@@ -5512,7 +5519,7 @@ ${indent}${text.slice(fold + 1, end2)}`;
|
|
|
5512
5519
|
exports.foldFlowLines = foldFlowLines;
|
|
5513
5520
|
});
|
|
5514
5521
|
|
|
5515
|
-
//
|
|
5522
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/stringify/stringifyString.js
|
|
5516
5523
|
var require_stringifyString = __commonJS((exports) => {
|
|
5517
5524
|
var Scalar = require_Scalar();
|
|
5518
5525
|
var foldFlowLines = require_foldFlowLines();
|
|
@@ -5810,7 +5817,7 @@ ${indent}`);
|
|
|
5810
5817
|
exports.stringifyString = stringifyString;
|
|
5811
5818
|
});
|
|
5812
5819
|
|
|
5813
|
-
//
|
|
5820
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/stringify/stringify.js
|
|
5814
5821
|
var require_stringify = __commonJS((exports) => {
|
|
5815
5822
|
var anchors = require_anchors();
|
|
5816
5823
|
var identity = require_identity();
|
|
@@ -5931,7 +5938,7 @@ ${ctx.indent}${str}`;
|
|
|
5931
5938
|
exports.stringify = stringify;
|
|
5932
5939
|
});
|
|
5933
5940
|
|
|
5934
|
-
//
|
|
5941
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/stringify/stringifyPair.js
|
|
5935
5942
|
var require_stringifyPair = __commonJS((exports) => {
|
|
5936
5943
|
var identity = require_identity();
|
|
5937
5944
|
var Scalar = require_Scalar();
|
|
@@ -6067,7 +6074,7 @@ ${ctx.indent}`;
|
|
|
6067
6074
|
exports.stringifyPair = stringifyPair;
|
|
6068
6075
|
});
|
|
6069
6076
|
|
|
6070
|
-
//
|
|
6077
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/log.js
|
|
6071
6078
|
var require_log = __commonJS((exports) => {
|
|
6072
6079
|
var node_process = __require("process");
|
|
6073
6080
|
function debug(logLevel, ...messages) {
|
|
@@ -6086,7 +6093,7 @@ var require_log = __commonJS((exports) => {
|
|
|
6086
6093
|
exports.warn = warn;
|
|
6087
6094
|
});
|
|
6088
6095
|
|
|
6089
|
-
//
|
|
6096
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/merge.js
|
|
6090
6097
|
var require_merge = __commonJS((exports) => {
|
|
6091
6098
|
var identity = require_identity();
|
|
6092
6099
|
var Scalar = require_Scalar();
|
|
@@ -6143,7 +6150,7 @@ var require_merge = __commonJS((exports) => {
|
|
|
6143
6150
|
exports.merge = merge;
|
|
6144
6151
|
});
|
|
6145
6152
|
|
|
6146
|
-
//
|
|
6153
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/addPairToJSMap.js
|
|
6147
6154
|
var require_addPairToJSMap = __commonJS((exports) => {
|
|
6148
6155
|
var log = require_log();
|
|
6149
6156
|
var merge = require_merge();
|
|
@@ -6204,7 +6211,7 @@ var require_addPairToJSMap = __commonJS((exports) => {
|
|
|
6204
6211
|
exports.addPairToJSMap = addPairToJSMap;
|
|
6205
6212
|
});
|
|
6206
6213
|
|
|
6207
|
-
//
|
|
6214
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/Pair.js
|
|
6208
6215
|
var require_Pair = __commonJS((exports) => {
|
|
6209
6216
|
var createNode = require_createNode();
|
|
6210
6217
|
var stringifyPair = require_stringifyPair();
|
|
@@ -6242,7 +6249,7 @@ var require_Pair = __commonJS((exports) => {
|
|
|
6242
6249
|
exports.createPair = createPair;
|
|
6243
6250
|
});
|
|
6244
6251
|
|
|
6245
|
-
//
|
|
6252
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/stringify/stringifyCollection.js
|
|
6246
6253
|
var require_stringifyCollection = __commonJS((exports) => {
|
|
6247
6254
|
var identity = require_identity();
|
|
6248
6255
|
var stringify = require_stringify();
|
|
@@ -6394,7 +6401,7 @@ ${indent}${end}`;
|
|
|
6394
6401
|
exports.stringifyCollection = stringifyCollection;
|
|
6395
6402
|
});
|
|
6396
6403
|
|
|
6397
|
-
//
|
|
6404
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/YAMLMap.js
|
|
6398
6405
|
var require_YAMLMap = __commonJS((exports) => {
|
|
6399
6406
|
var stringifyCollection = require_stringifyCollection();
|
|
6400
6407
|
var addPairToJSMap = require_addPairToJSMap();
|
|
@@ -6521,7 +6528,7 @@ var require_YAMLMap = __commonJS((exports) => {
|
|
|
6521
6528
|
exports.findPair = findPair;
|
|
6522
6529
|
});
|
|
6523
6530
|
|
|
6524
|
-
//
|
|
6531
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/common/map.js
|
|
6525
6532
|
var require_map = __commonJS((exports) => {
|
|
6526
6533
|
var identity = require_identity();
|
|
6527
6534
|
var YAMLMap = require_YAMLMap();
|
|
@@ -6540,7 +6547,7 @@ var require_map = __commonJS((exports) => {
|
|
|
6540
6547
|
exports.map = map;
|
|
6541
6548
|
});
|
|
6542
6549
|
|
|
6543
|
-
//
|
|
6550
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/YAMLSeq.js
|
|
6544
6551
|
var require_YAMLSeq = __commonJS((exports) => {
|
|
6545
6552
|
var createNode = require_createNode();
|
|
6546
6553
|
var stringifyCollection = require_stringifyCollection();
|
|
@@ -6633,7 +6640,7 @@ var require_YAMLSeq = __commonJS((exports) => {
|
|
|
6633
6640
|
exports.YAMLSeq = YAMLSeq;
|
|
6634
6641
|
});
|
|
6635
6642
|
|
|
6636
|
-
//
|
|
6643
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/common/seq.js
|
|
6637
6644
|
var require_seq = __commonJS((exports) => {
|
|
6638
6645
|
var identity = require_identity();
|
|
6639
6646
|
var YAMLSeq = require_YAMLSeq();
|
|
@@ -6652,7 +6659,7 @@ var require_seq = __commonJS((exports) => {
|
|
|
6652
6659
|
exports.seq = seq;
|
|
6653
6660
|
});
|
|
6654
6661
|
|
|
6655
|
-
//
|
|
6662
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/common/string.js
|
|
6656
6663
|
var require_string = __commonJS((exports) => {
|
|
6657
6664
|
var stringifyString = require_stringifyString();
|
|
6658
6665
|
var string = {
|
|
@@ -6668,7 +6675,7 @@ var require_string = __commonJS((exports) => {
|
|
|
6668
6675
|
exports.string = string;
|
|
6669
6676
|
});
|
|
6670
6677
|
|
|
6671
|
-
//
|
|
6678
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/common/null.js
|
|
6672
6679
|
var require_null = __commonJS((exports) => {
|
|
6673
6680
|
var Scalar = require_Scalar();
|
|
6674
6681
|
var nullTag = {
|
|
@@ -6683,7 +6690,7 @@ var require_null = __commonJS((exports) => {
|
|
|
6683
6690
|
exports.nullTag = nullTag;
|
|
6684
6691
|
});
|
|
6685
6692
|
|
|
6686
|
-
//
|
|
6693
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/core/bool.js
|
|
6687
6694
|
var require_bool = __commonJS((exports) => {
|
|
6688
6695
|
var Scalar = require_Scalar();
|
|
6689
6696
|
var boolTag = {
|
|
@@ -6704,7 +6711,7 @@ var require_bool = __commonJS((exports) => {
|
|
|
6704
6711
|
exports.boolTag = boolTag;
|
|
6705
6712
|
});
|
|
6706
6713
|
|
|
6707
|
-
//
|
|
6714
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/stringify/stringifyNumber.js
|
|
6708
6715
|
var require_stringifyNumber = __commonJS((exports) => {
|
|
6709
6716
|
function stringifyNumber({ format, minFractionDigits, tag, value }) {
|
|
6710
6717
|
if (typeof value === "bigint")
|
|
@@ -6728,7 +6735,7 @@ var require_stringifyNumber = __commonJS((exports) => {
|
|
|
6728
6735
|
exports.stringifyNumber = stringifyNumber;
|
|
6729
6736
|
});
|
|
6730
6737
|
|
|
6731
|
-
//
|
|
6738
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/core/float.js
|
|
6732
6739
|
var require_float = __commonJS((exports) => {
|
|
6733
6740
|
var Scalar = require_Scalar();
|
|
6734
6741
|
var stringifyNumber = require_stringifyNumber();
|
|
@@ -6771,7 +6778,7 @@ var require_float = __commonJS((exports) => {
|
|
|
6771
6778
|
exports.floatNaN = floatNaN;
|
|
6772
6779
|
});
|
|
6773
6780
|
|
|
6774
|
-
//
|
|
6781
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/core/int.js
|
|
6775
6782
|
var require_int = __commonJS((exports) => {
|
|
6776
6783
|
var stringifyNumber = require_stringifyNumber();
|
|
6777
6784
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
@@ -6813,7 +6820,7 @@ var require_int = __commonJS((exports) => {
|
|
|
6813
6820
|
exports.intOct = intOct;
|
|
6814
6821
|
});
|
|
6815
6822
|
|
|
6816
|
-
//
|
|
6823
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/core/schema.js
|
|
6817
6824
|
var require_schema = __commonJS((exports) => {
|
|
6818
6825
|
var map = require_map();
|
|
6819
6826
|
var _null = require_null();
|
|
@@ -6838,7 +6845,7 @@ var require_schema = __commonJS((exports) => {
|
|
|
6838
6845
|
exports.schema = schema;
|
|
6839
6846
|
});
|
|
6840
6847
|
|
|
6841
|
-
//
|
|
6848
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/json/schema.js
|
|
6842
6849
|
var require_schema2 = __commonJS((exports) => {
|
|
6843
6850
|
var Scalar = require_Scalar();
|
|
6844
6851
|
var map = require_map();
|
|
@@ -6902,7 +6909,7 @@ var require_schema2 = __commonJS((exports) => {
|
|
|
6902
6909
|
exports.schema = schema;
|
|
6903
6910
|
});
|
|
6904
6911
|
|
|
6905
|
-
//
|
|
6912
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/binary.js
|
|
6906
6913
|
var require_binary = __commonJS((exports) => {
|
|
6907
6914
|
var node_buffer = __require("buffer");
|
|
6908
6915
|
var Scalar = require_Scalar();
|
|
@@ -6957,7 +6964,7 @@ var require_binary = __commonJS((exports) => {
|
|
|
6957
6964
|
exports.binary = binary;
|
|
6958
6965
|
});
|
|
6959
6966
|
|
|
6960
|
-
//
|
|
6967
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
|
|
6961
6968
|
var require_pairs = __commonJS((exports) => {
|
|
6962
6969
|
var identity = require_identity();
|
|
6963
6970
|
var Pair = require_Pair();
|
|
@@ -7032,7 +7039,7 @@ ${cn.comment}` : item.comment;
|
|
|
7032
7039
|
exports.resolvePairs = resolvePairs;
|
|
7033
7040
|
});
|
|
7034
7041
|
|
|
7035
|
-
//
|
|
7042
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/omap.js
|
|
7036
7043
|
var require_omap = __commonJS((exports) => {
|
|
7037
7044
|
var identity = require_identity();
|
|
7038
7045
|
var toJS = require_toJS();
|
|
@@ -7104,7 +7111,7 @@ var require_omap = __commonJS((exports) => {
|
|
|
7104
7111
|
exports.omap = omap;
|
|
7105
7112
|
});
|
|
7106
7113
|
|
|
7107
|
-
//
|
|
7114
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/bool.js
|
|
7108
7115
|
var require_bool2 = __commonJS((exports) => {
|
|
7109
7116
|
var Scalar = require_Scalar();
|
|
7110
7117
|
function boolStringify({ value, source }, ctx) {
|
|
@@ -7133,7 +7140,7 @@ var require_bool2 = __commonJS((exports) => {
|
|
|
7133
7140
|
exports.trueTag = trueTag;
|
|
7134
7141
|
});
|
|
7135
7142
|
|
|
7136
|
-
//
|
|
7143
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/float.js
|
|
7137
7144
|
var require_float2 = __commonJS((exports) => {
|
|
7138
7145
|
var Scalar = require_Scalar();
|
|
7139
7146
|
var stringifyNumber = require_stringifyNumber();
|
|
@@ -7179,7 +7186,7 @@ var require_float2 = __commonJS((exports) => {
|
|
|
7179
7186
|
exports.floatNaN = floatNaN;
|
|
7180
7187
|
});
|
|
7181
7188
|
|
|
7182
|
-
//
|
|
7189
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/int.js
|
|
7183
7190
|
var require_int2 = __commonJS((exports) => {
|
|
7184
7191
|
var stringifyNumber = require_stringifyNumber();
|
|
7185
7192
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
@@ -7255,7 +7262,7 @@ var require_int2 = __commonJS((exports) => {
|
|
|
7255
7262
|
exports.intOct = intOct;
|
|
7256
7263
|
});
|
|
7257
7264
|
|
|
7258
|
-
//
|
|
7265
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/set.js
|
|
7259
7266
|
var require_set = __commonJS((exports) => {
|
|
7260
7267
|
var identity = require_identity();
|
|
7261
7268
|
var Pair = require_Pair();
|
|
@@ -7338,7 +7345,7 @@ var require_set = __commonJS((exports) => {
|
|
|
7338
7345
|
exports.set = set;
|
|
7339
7346
|
});
|
|
7340
7347
|
|
|
7341
|
-
//
|
|
7348
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
|
|
7342
7349
|
var require_timestamp = __commonJS((exports) => {
|
|
7343
7350
|
var stringifyNumber = require_stringifyNumber();
|
|
7344
7351
|
function parseSexagesimal(str, asBigInt) {
|
|
@@ -7420,7 +7427,7 @@ var require_timestamp = __commonJS((exports) => {
|
|
|
7420
7427
|
exports.timestamp = timestamp;
|
|
7421
7428
|
});
|
|
7422
7429
|
|
|
7423
|
-
//
|
|
7430
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/yaml-1.1/schema.js
|
|
7424
7431
|
var require_schema3 = __commonJS((exports) => {
|
|
7425
7432
|
var map = require_map();
|
|
7426
7433
|
var _null = require_null();
|
|
@@ -7461,7 +7468,7 @@ var require_schema3 = __commonJS((exports) => {
|
|
|
7461
7468
|
exports.schema = schema;
|
|
7462
7469
|
});
|
|
7463
7470
|
|
|
7464
|
-
//
|
|
7471
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/tags.js
|
|
7465
7472
|
var require_tags = __commonJS((exports) => {
|
|
7466
7473
|
var map = require_map();
|
|
7467
7474
|
var _null = require_null();
|
|
@@ -7552,7 +7559,7 @@ var require_tags = __commonJS((exports) => {
|
|
|
7552
7559
|
exports.getTags = getTags;
|
|
7553
7560
|
});
|
|
7554
7561
|
|
|
7555
|
-
//
|
|
7562
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/schema/Schema.js
|
|
7556
7563
|
var require_Schema = __commonJS((exports) => {
|
|
7557
7564
|
var identity = require_identity();
|
|
7558
7565
|
var map = require_map();
|
|
@@ -7582,7 +7589,7 @@ var require_Schema = __commonJS((exports) => {
|
|
|
7582
7589
|
exports.Schema = Schema;
|
|
7583
7590
|
});
|
|
7584
7591
|
|
|
7585
|
-
//
|
|
7592
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/stringify/stringifyDocument.js
|
|
7586
7593
|
var require_stringifyDocument = __commonJS((exports) => {
|
|
7587
7594
|
var identity = require_identity();
|
|
7588
7595
|
var stringify = require_stringify();
|
|
@@ -7662,7 +7669,7 @@ var require_stringifyDocument = __commonJS((exports) => {
|
|
|
7662
7669
|
exports.stringifyDocument = stringifyDocument;
|
|
7663
7670
|
});
|
|
7664
7671
|
|
|
7665
|
-
//
|
|
7672
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/doc/Document.js
|
|
7666
7673
|
var require_Document = __commonJS((exports) => {
|
|
7667
7674
|
var Alias = require_Alias();
|
|
7668
7675
|
var Collection = require_Collection();
|
|
@@ -7897,7 +7904,7 @@ var require_Document = __commonJS((exports) => {
|
|
|
7897
7904
|
exports.Document = Document;
|
|
7898
7905
|
});
|
|
7899
7906
|
|
|
7900
|
-
//
|
|
7907
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/errors.js
|
|
7901
7908
|
var require_errors = __commonJS((exports) => {
|
|
7902
7909
|
class YAMLError extends Error {
|
|
7903
7910
|
constructor(name, pos, code, message) {
|
|
@@ -7962,7 +7969,7 @@ ${pointer}
|
|
|
7962
7969
|
exports.prettifyError = prettifyError;
|
|
7963
7970
|
});
|
|
7964
7971
|
|
|
7965
|
-
//
|
|
7972
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/resolve-props.js
|
|
7966
7973
|
var require_resolve_props = __commonJS((exports) => {
|
|
7967
7974
|
function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
|
|
7968
7975
|
let spaceBefore = false;
|
|
@@ -8092,7 +8099,7 @@ var require_resolve_props = __commonJS((exports) => {
|
|
|
8092
8099
|
exports.resolveProps = resolveProps;
|
|
8093
8100
|
});
|
|
8094
8101
|
|
|
8095
|
-
//
|
|
8102
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/util-contains-newline.js
|
|
8096
8103
|
var require_util_contains_newline = __commonJS((exports) => {
|
|
8097
8104
|
function containsNewline(key) {
|
|
8098
8105
|
if (!key)
|
|
@@ -8132,7 +8139,7 @@ var require_util_contains_newline = __commonJS((exports) => {
|
|
|
8132
8139
|
exports.containsNewline = containsNewline;
|
|
8133
8140
|
});
|
|
8134
8141
|
|
|
8135
|
-
//
|
|
8142
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/util-flow-indent-check.js
|
|
8136
8143
|
var require_util_flow_indent_check = __commonJS((exports) => {
|
|
8137
8144
|
var utilContainsNewline = require_util_contains_newline();
|
|
8138
8145
|
function flowIndentCheck(indent, fc, onError) {
|
|
@@ -8147,7 +8154,7 @@ var require_util_flow_indent_check = __commonJS((exports) => {
|
|
|
8147
8154
|
exports.flowIndentCheck = flowIndentCheck;
|
|
8148
8155
|
});
|
|
8149
8156
|
|
|
8150
|
-
//
|
|
8157
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/util-map-includes.js
|
|
8151
8158
|
var require_util_map_includes = __commonJS((exports) => {
|
|
8152
8159
|
var identity = require_identity();
|
|
8153
8160
|
function mapIncludes(ctx, items, search) {
|
|
@@ -8160,7 +8167,7 @@ var require_util_map_includes = __commonJS((exports) => {
|
|
|
8160
8167
|
exports.mapIncludes = mapIncludes;
|
|
8161
8168
|
});
|
|
8162
8169
|
|
|
8163
|
-
//
|
|
8170
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/resolve-block-map.js
|
|
8164
8171
|
var require_resolve_block_map = __commonJS((exports) => {
|
|
8165
8172
|
var Pair = require_Pair();
|
|
8166
8173
|
var YAMLMap = require_YAMLMap();
|
|
@@ -8267,7 +8274,7 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
8267
8274
|
exports.resolveBlockMap = resolveBlockMap;
|
|
8268
8275
|
});
|
|
8269
8276
|
|
|
8270
|
-
//
|
|
8277
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/resolve-block-seq.js
|
|
8271
8278
|
var require_resolve_block_seq = __commonJS((exports) => {
|
|
8272
8279
|
var YAMLSeq = require_YAMLSeq();
|
|
8273
8280
|
var resolveProps = require_resolve_props();
|
|
@@ -8315,7 +8322,7 @@ var require_resolve_block_seq = __commonJS((exports) => {
|
|
|
8315
8322
|
exports.resolveBlockSeq = resolveBlockSeq;
|
|
8316
8323
|
});
|
|
8317
8324
|
|
|
8318
|
-
//
|
|
8325
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/resolve-end.js
|
|
8319
8326
|
var require_resolve_end = __commonJS((exports) => {
|
|
8320
8327
|
function resolveEnd(end, offset, reqSpace, onError) {
|
|
8321
8328
|
let comment = "";
|
|
@@ -8355,7 +8362,7 @@ var require_resolve_end = __commonJS((exports) => {
|
|
|
8355
8362
|
exports.resolveEnd = resolveEnd;
|
|
8356
8363
|
});
|
|
8357
8364
|
|
|
8358
|
-
//
|
|
8365
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/resolve-flow-collection.js
|
|
8359
8366
|
var require_resolve_flow_collection = __commonJS((exports) => {
|
|
8360
8367
|
var identity = require_identity();
|
|
8361
8368
|
var Pair = require_Pair();
|
|
@@ -8546,7 +8553,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
8546
8553
|
exports.resolveFlowCollection = resolveFlowCollection;
|
|
8547
8554
|
});
|
|
8548
8555
|
|
|
8549
|
-
//
|
|
8556
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/compose-collection.js
|
|
8550
8557
|
var require_compose_collection = __commonJS((exports) => {
|
|
8551
8558
|
var identity = require_identity();
|
|
8552
8559
|
var Scalar = require_Scalar();
|
|
@@ -8608,7 +8615,7 @@ var require_compose_collection = __commonJS((exports) => {
|
|
|
8608
8615
|
exports.composeCollection = composeCollection;
|
|
8609
8616
|
});
|
|
8610
8617
|
|
|
8611
|
-
//
|
|
8618
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/resolve-block-scalar.js
|
|
8612
8619
|
var require_resolve_block_scalar = __commonJS((exports) => {
|
|
8613
8620
|
var Scalar = require_Scalar();
|
|
8614
8621
|
function resolveBlockScalar(ctx, scalar, onError) {
|
|
@@ -8801,7 +8808,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
|
|
|
8801
8808
|
exports.resolveBlockScalar = resolveBlockScalar;
|
|
8802
8809
|
});
|
|
8803
8810
|
|
|
8804
|
-
//
|
|
8811
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/resolve-flow-scalar.js
|
|
8805
8812
|
var require_resolve_flow_scalar = __commonJS((exports) => {
|
|
8806
8813
|
var Scalar = require_Scalar();
|
|
8807
8814
|
var resolveEnd = require_resolve_end();
|
|
@@ -9018,7 +9025,7 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
|
|
|
9018
9025
|
exports.resolveFlowScalar = resolveFlowScalar;
|
|
9019
9026
|
});
|
|
9020
9027
|
|
|
9021
|
-
//
|
|
9028
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/compose-scalar.js
|
|
9022
9029
|
var require_compose_scalar = __commonJS((exports) => {
|
|
9023
9030
|
var identity = require_identity();
|
|
9024
9031
|
var Scalar = require_Scalar();
|
|
@@ -9096,7 +9103,7 @@ var require_compose_scalar = __commonJS((exports) => {
|
|
|
9096
9103
|
exports.composeScalar = composeScalar;
|
|
9097
9104
|
});
|
|
9098
9105
|
|
|
9099
|
-
//
|
|
9106
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/util-empty-scalar-position.js
|
|
9100
9107
|
var require_util_empty_scalar_position = __commonJS((exports) => {
|
|
9101
9108
|
function emptyScalarPosition(offset, before, pos) {
|
|
9102
9109
|
if (before) {
|
|
@@ -9123,7 +9130,7 @@ var require_util_empty_scalar_position = __commonJS((exports) => {
|
|
|
9123
9130
|
exports.emptyScalarPosition = emptyScalarPosition;
|
|
9124
9131
|
});
|
|
9125
9132
|
|
|
9126
|
-
//
|
|
9133
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/compose-node.js
|
|
9127
9134
|
var require_compose_node = __commonJS((exports) => {
|
|
9128
9135
|
var Alias = require_Alias();
|
|
9129
9136
|
var identity = require_identity();
|
|
@@ -9226,7 +9233,7 @@ var require_compose_node = __commonJS((exports) => {
|
|
|
9226
9233
|
exports.composeNode = composeNode;
|
|
9227
9234
|
});
|
|
9228
9235
|
|
|
9229
|
-
//
|
|
9236
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/compose-doc.js
|
|
9230
9237
|
var require_compose_doc = __commonJS((exports) => {
|
|
9231
9238
|
var Document = require_Document();
|
|
9232
9239
|
var composeNode = require_compose_node();
|
|
@@ -9266,7 +9273,7 @@ var require_compose_doc = __commonJS((exports) => {
|
|
|
9266
9273
|
exports.composeDoc = composeDoc;
|
|
9267
9274
|
});
|
|
9268
9275
|
|
|
9269
|
-
//
|
|
9276
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/compose/composer.js
|
|
9270
9277
|
var require_composer = __commonJS((exports) => {
|
|
9271
9278
|
var node_process = __require("process");
|
|
9272
9279
|
var directives = require_directives();
|
|
@@ -9457,7 +9464,7 @@ ${end.comment}` : end.comment;
|
|
|
9457
9464
|
exports.Composer = Composer;
|
|
9458
9465
|
});
|
|
9459
9466
|
|
|
9460
|
-
//
|
|
9467
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/parse/cst-scalar.js
|
|
9461
9468
|
var require_cst_scalar = __commonJS((exports) => {
|
|
9462
9469
|
var resolveBlockScalar = require_resolve_block_scalar();
|
|
9463
9470
|
var resolveFlowScalar = require_resolve_flow_scalar();
|
|
@@ -9647,7 +9654,7 @@ var require_cst_scalar = __commonJS((exports) => {
|
|
|
9647
9654
|
exports.setScalarValue = setScalarValue;
|
|
9648
9655
|
});
|
|
9649
9656
|
|
|
9650
|
-
//
|
|
9657
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/parse/cst-stringify.js
|
|
9651
9658
|
var require_cst_stringify = __commonJS((exports) => {
|
|
9652
9659
|
var stringify = (cst) => ("type" in cst) ? stringifyToken(cst) : stringifyItem(cst);
|
|
9653
9660
|
function stringifyToken(token) {
|
|
@@ -9705,7 +9712,7 @@ var require_cst_stringify = __commonJS((exports) => {
|
|
|
9705
9712
|
exports.stringify = stringify;
|
|
9706
9713
|
});
|
|
9707
9714
|
|
|
9708
|
-
//
|
|
9715
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/parse/cst-visit.js
|
|
9709
9716
|
var require_cst_visit = __commonJS((exports) => {
|
|
9710
9717
|
var BREAK = Symbol("break visit");
|
|
9711
9718
|
var SKIP = Symbol("skip children");
|
|
@@ -9764,7 +9771,7 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
9764
9771
|
exports.visit = visit;
|
|
9765
9772
|
});
|
|
9766
9773
|
|
|
9767
|
-
//
|
|
9774
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/parse/cst.js
|
|
9768
9775
|
var require_cst = __commonJS((exports) => {
|
|
9769
9776
|
var cstScalar = require_cst_scalar();
|
|
9770
9777
|
var cstStringify = require_cst_stringify();
|
|
@@ -9865,7 +9872,7 @@ var require_cst = __commonJS((exports) => {
|
|
|
9865
9872
|
exports.tokenType = tokenType;
|
|
9866
9873
|
});
|
|
9867
9874
|
|
|
9868
|
-
//
|
|
9875
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/parse/lexer.js
|
|
9869
9876
|
var require_lexer = __commonJS((exports) => {
|
|
9870
9877
|
var cst = require_cst();
|
|
9871
9878
|
function isEmpty(ch) {
|
|
@@ -10462,7 +10469,7 @@ var require_lexer = __commonJS((exports) => {
|
|
|
10462
10469
|
exports.Lexer = Lexer;
|
|
10463
10470
|
});
|
|
10464
10471
|
|
|
10465
|
-
//
|
|
10472
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/parse/line-counter.js
|
|
10466
10473
|
var require_line_counter = __commonJS((exports) => {
|
|
10467
10474
|
class LineCounter {
|
|
10468
10475
|
constructor() {
|
|
@@ -10490,7 +10497,7 @@ var require_line_counter = __commonJS((exports) => {
|
|
|
10490
10497
|
exports.LineCounter = LineCounter;
|
|
10491
10498
|
});
|
|
10492
10499
|
|
|
10493
|
-
//
|
|
10500
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/parse/parser.js
|
|
10494
10501
|
var require_parser = __commonJS((exports) => {
|
|
10495
10502
|
var node_process = __require("process");
|
|
10496
10503
|
var cst = require_cst();
|
|
@@ -11346,7 +11353,7 @@ var require_parser = __commonJS((exports) => {
|
|
|
11346
11353
|
exports.Parser = Parser;
|
|
11347
11354
|
});
|
|
11348
11355
|
|
|
11349
|
-
//
|
|
11356
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/public-api.js
|
|
11350
11357
|
var require_public_api = __commonJS((exports) => {
|
|
11351
11358
|
var composer = require_composer();
|
|
11352
11359
|
var Document = require_Document();
|
|
@@ -11440,7 +11447,7 @@ var require_public_api = __commonJS((exports) => {
|
|
|
11440
11447
|
exports.stringify = stringify;
|
|
11441
11448
|
});
|
|
11442
11449
|
|
|
11443
|
-
//
|
|
11450
|
+
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/index.js
|
|
11444
11451
|
var composer, Document, Schema, errors2, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync;
|
|
11445
11452
|
var init_dist = __esm(() => {
|
|
11446
11453
|
composer = require_composer();
|
|
@@ -11489,7 +11496,7 @@ var init_dist = __esm(() => {
|
|
|
11489
11496
|
$visitAsync = visit.visitAsync;
|
|
11490
11497
|
});
|
|
11491
11498
|
|
|
11492
|
-
//
|
|
11499
|
+
// packages/libs/manifest/src/loader.ts
|
|
11493
11500
|
import { createHash } from "crypto";
|
|
11494
11501
|
import { readFile as readFile2, realpath as realpath2 } from "fs/promises";
|
|
11495
11502
|
import path2 from "path";
|
|
@@ -11676,12 +11683,13 @@ var init_loader = __esm(() => {
|
|
|
11676
11683
|
};
|
|
11677
11684
|
});
|
|
11678
11685
|
|
|
11679
|
-
//
|
|
11686
|
+
// packages/libs/manifest/src/index.ts
|
|
11680
11687
|
var exports_src = {};
|
|
11681
11688
|
__export(exports_src, {
|
|
11682
11689
|
sandblocksManifestDigest: () => sandblocksManifestDigest,
|
|
11683
11690
|
resolveServiceSteps: () => resolveServiceSteps,
|
|
11684
11691
|
resolveProjectGraph: () => resolveProjectGraph,
|
|
11692
|
+
resolvePrebuiltImage: () => resolvePrebuiltImage,
|
|
11685
11693
|
resolveEnvironment: () => resolveEnvironment,
|
|
11686
11694
|
parseSandblocksManifest: () => parseSandblocksManifest,
|
|
11687
11695
|
normalizeCommand: () => normalizeCommand,
|
|
@@ -11709,1708 +11717,2153 @@ var init_src = __esm(() => {
|
|
|
11709
11717
|
init_loader();
|
|
11710
11718
|
});
|
|
11711
11719
|
|
|
11712
|
-
// src/cli.ts
|
|
11720
|
+
// packages/libs/cli/src/cli.ts
|
|
11713
11721
|
init_src();
|
|
11714
11722
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11715
|
-
import { lstat, readFile as readFile8, stat as stat3, writeFile as writeFile5 } from "fs/promises";
|
|
11723
|
+
import { lstat, readFile as readFile8, readdir as readdir2, stat as stat3, writeFile as writeFile5 } from "fs/promises";
|
|
11716
11724
|
import path6 from "path";
|
|
11717
11725
|
|
|
11718
|
-
//
|
|
11719
|
-
import {
|
|
11720
|
-
import
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11726
|
+
// packages/libs/sdk/dist/index.js
|
|
11727
|
+
import { randomUUID } from "crypto";
|
|
11728
|
+
import { appendFile, cp, mkdir, readFile as readFile3, rm } from "fs/promises";
|
|
11729
|
+
import { dirname, resolve } from "path";
|
|
11730
|
+
import { spawn } from "child_process";
|
|
11731
|
+
import { access, mkdir as mkdir2, writeFile } from "fs/promises";
|
|
11732
|
+
import { resolve as resolve2 } from "path";
|
|
11733
|
+
|
|
11734
|
+
class CommandAgent {
|
|
11735
|
+
options;
|
|
11736
|
+
id;
|
|
11737
|
+
constructor(options) {
|
|
11738
|
+
this.options = options;
|
|
11739
|
+
this.id = options.id ?? "command";
|
|
11730
11740
|
}
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11741
|
+
async run(context) {
|
|
11742
|
+
const startedAt = new Date().toISOString();
|
|
11743
|
+
const base = typeof this.options.command === "function" ? this.options.command(context) : [...this.options.command];
|
|
11744
|
+
const promptMode = this.options.prompt ?? "stdin";
|
|
11745
|
+
const command = promptMode === "argument" ? [...base, context.prompt] : base;
|
|
11746
|
+
const idle = new AbortController;
|
|
11747
|
+
const completed = new AbortController;
|
|
11748
|
+
let idleTimer;
|
|
11749
|
+
let completionTimer;
|
|
11750
|
+
const resetIdle = () => {
|
|
11751
|
+
if (idleTimer)
|
|
11752
|
+
clearTimeout(idleTimer);
|
|
11753
|
+
idleTimer = setTimeout(() => idle.abort(new Error(`agent idle timeout after ${context.idleTimeoutSeconds}s`)), context.idleTimeoutSeconds * 1000);
|
|
11754
|
+
};
|
|
11755
|
+
resetIdle();
|
|
11756
|
+
let output = "";
|
|
11757
|
+
let stdout = "";
|
|
11758
|
+
let stderr = "";
|
|
11759
|
+
let lineBuffer = "";
|
|
11760
|
+
let completionSignal;
|
|
11761
|
+
const consume = async (chunk, stream) => {
|
|
11762
|
+
resetIdle();
|
|
11763
|
+
output += chunk;
|
|
11764
|
+
if (stream === "stdout")
|
|
11765
|
+
stdout += chunk;
|
|
11766
|
+
else
|
|
11767
|
+
stderr += chunk;
|
|
11768
|
+
lineBuffer += chunk;
|
|
11769
|
+
const lines = lineBuffer.split(/\r?\n/);
|
|
11770
|
+
lineBuffer = lines.pop() ?? "";
|
|
11771
|
+
for (const line of lines)
|
|
11772
|
+
await this.emitLine(line, stream, context);
|
|
11773
|
+
completionSignal ??= context.completionSignal.find((signal) => output.includes(signal));
|
|
11774
|
+
if (completionSignal && !completionTimer) {
|
|
11775
|
+
completionTimer = setTimeout(() => completed.abort(new Error("agent did not exit after its completion signal")), context.completionTimeoutSeconds * 1000);
|
|
11776
|
+
}
|
|
11777
|
+
};
|
|
11778
|
+
try {
|
|
11779
|
+
const signals = [context.signal, idle.signal, completed.signal].filter(Boolean);
|
|
11780
|
+
try {
|
|
11781
|
+
const result = await context.runtime.exec(command, {
|
|
11782
|
+
env: this.options.env,
|
|
11783
|
+
stdin: promptMode === "stdin" ? context.prompt : undefined,
|
|
11784
|
+
signal: signals.length > 1 ? AbortSignal.any(signals) : signals[0],
|
|
11785
|
+
onStdout: (chunk) => consume(chunk, "stdout"),
|
|
11786
|
+
onStderr: (chunk) => consume(chunk, "stderr")
|
|
11787
|
+
});
|
|
11788
|
+
stdout = result.stdout;
|
|
11789
|
+
stderr = result.stderr;
|
|
11790
|
+
return this.result(context, startedAt, result.exitCode, stdout, stderr, completionSignal);
|
|
11791
|
+
} catch (error) {
|
|
11792
|
+
if (!completionSignal)
|
|
11793
|
+
throw error;
|
|
11794
|
+
return this.result(context, startedAt, 0, stdout, stderr, completionSignal);
|
|
11795
|
+
}
|
|
11796
|
+
} finally {
|
|
11797
|
+
if (idleTimer)
|
|
11798
|
+
clearTimeout(idleTimer);
|
|
11799
|
+
if (completionTimer)
|
|
11800
|
+
clearTimeout(completionTimer);
|
|
11801
|
+
if (lineBuffer)
|
|
11802
|
+
await this.emitLine(lineBuffer, "stdout", context);
|
|
11803
|
+
}
|
|
11749
11804
|
}
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
if (await exists(backupPath))
|
|
11763
|
-
console.log(`chaining ${backupPath}`);
|
|
11764
|
-
}
|
|
11765
|
-
async function uninstall(root) {
|
|
11766
|
-
const hooksDir = await gitPath(root, "hooks");
|
|
11767
|
-
const hookPath = path3.join(hooksDir, "post-commit");
|
|
11768
|
-
const backupPath = path3.join(hooksDir, backupName);
|
|
11769
|
-
const current = await text(hookPath);
|
|
11770
|
-
if (!current?.includes(marker))
|
|
11771
|
-
throw new Error("Sandblocks post-commit hook is not installed");
|
|
11772
|
-
if (await exists(backupPath)) {
|
|
11773
|
-
await copyFile(backupPath, hookPath);
|
|
11774
|
-
await chmod(hookPath, 493);
|
|
11775
|
-
await rm(backupPath);
|
|
11776
|
-
console.log(`restored ${hookPath}`);
|
|
11777
|
-
} else {
|
|
11778
|
-
await rm(hookPath);
|
|
11779
|
-
console.log(`removed ${hookPath}`);
|
|
11805
|
+
result(context, startedAt, exitCode, stdout, stderr, completionSignal) {
|
|
11806
|
+
const sessionId = this.options.sessionId?.(stdout, stderr);
|
|
11807
|
+
return {
|
|
11808
|
+
iteration: context.iteration,
|
|
11809
|
+
exitCode,
|
|
11810
|
+
stdout,
|
|
11811
|
+
stderr,
|
|
11812
|
+
...sessionId ? { sessionId } : {},
|
|
11813
|
+
...completionSignal ? { completionSignal } : {},
|
|
11814
|
+
startedAt,
|
|
11815
|
+
finishedAt: new Date().toISOString()
|
|
11816
|
+
};
|
|
11780
11817
|
}
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
11790
|
-
|
|
11791
|
-
|
|
11818
|
+
async emitLine(line, stream, context) {
|
|
11819
|
+
const timestamp = new Date().toISOString();
|
|
11820
|
+
const parsed = this.options.parseEvent?.(line, { iteration: context.iteration, timestamp });
|
|
11821
|
+
const events = parsed ? Array.isArray(parsed) ? parsed : [parsed] : [
|
|
11822
|
+
{
|
|
11823
|
+
type: "raw",
|
|
11824
|
+
line: stream === "stderr" ? `[stderr] ${line}` : line,
|
|
11825
|
+
iteration: context.iteration,
|
|
11826
|
+
timestamp
|
|
11827
|
+
}
|
|
11828
|
+
];
|
|
11829
|
+
for (const event of events)
|
|
11830
|
+
await context.emit(event);
|
|
11792
11831
|
}
|
|
11793
11832
|
}
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
const logDir = path3.join(root, ".sandblocks", "logs");
|
|
11812
|
-
await mkdir(logDir, { recursive: true });
|
|
11813
|
-
const log = path3.join(logDir, `post-commit-${Date.now()}.log`);
|
|
11814
|
-
const executable = process.argv[1];
|
|
11815
|
-
if (!executable)
|
|
11816
|
-
throw new Error("unable to resolve Sandblocks CLI executable");
|
|
11817
|
-
const proc = Bun.spawn([process.execPath, executable, "sandbox", command, ...commandArgs], {
|
|
11818
|
-
cwd: root,
|
|
11819
|
-
stdout: Bun.file(log),
|
|
11820
|
-
stderr: Bun.file(log),
|
|
11821
|
-
env: { ...process.env, SANDBLOCKS_SKIP_POST_COMMIT: "1" }
|
|
11833
|
+
|
|
11834
|
+
class ClaudeCodeAgent extends CommandAgent {
|
|
11835
|
+
constructor(options) {
|
|
11836
|
+
super({
|
|
11837
|
+
id: "claude-code",
|
|
11838
|
+
env: credentialEnvironment(options.env, options.credential),
|
|
11839
|
+
prompt: "argument",
|
|
11840
|
+
command: (context) => [
|
|
11841
|
+
options.binary ?? "claude",
|
|
11842
|
+
"--print",
|
|
11843
|
+
"--model",
|
|
11844
|
+
options.model,
|
|
11845
|
+
...options.effort ? ["--effort", options.effort] : [],
|
|
11846
|
+
...options.extraArgs ?? []
|
|
11847
|
+
],
|
|
11848
|
+
sessionId: extractSessionId,
|
|
11849
|
+
parseEvent: parseJsonAgentEvent
|
|
11822
11850
|
});
|
|
11823
|
-
proc.unref();
|
|
11824
|
-
console.log(`sandblocks ${command} started for ${policy.environment} (${log})`);
|
|
11825
|
-
return;
|
|
11826
11851
|
}
|
|
11827
|
-
await dependencies.runSandbox(command, commandArgs);
|
|
11828
|
-
}
|
|
11829
|
-
async function loadPolicy(root) {
|
|
11830
|
-
const { loadSandblocksManifest: loadSandblocksManifest2 } = await Promise.resolve().then(() => (init_src(), exports_src));
|
|
11831
|
-
return (await loadSandblocksManifest2({ repositoryRoot: root })).manifest.hooks.postCommit;
|
|
11832
|
-
}
|
|
11833
|
-
async function gitPath(root, name) {
|
|
11834
|
-
return path3.resolve(root, await git(root, ["rev-parse", "--git-path", name]));
|
|
11835
|
-
}
|
|
11836
|
-
async function git(root, args) {
|
|
11837
|
-
const proc = Bun.spawn(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
|
|
11838
|
-
const [stdout, stderr, code] = await Promise.all([
|
|
11839
|
-
new Response(proc.stdout).text(),
|
|
11840
|
-
new Response(proc.stderr).text(),
|
|
11841
|
-
proc.exited
|
|
11842
|
-
]);
|
|
11843
|
-
if (code !== 0)
|
|
11844
|
-
throw new Error(stderr.trim() || `git ${args.join(" ")} failed`);
|
|
11845
|
-
return stdout.trim();
|
|
11846
|
-
}
|
|
11847
|
-
async function exists(file) {
|
|
11848
|
-
return Bun.file(file).exists();
|
|
11849
11852
|
}
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11853
|
+
|
|
11854
|
+
class CodexAgent extends CommandAgent {
|
|
11855
|
+
constructor(options) {
|
|
11856
|
+
super({
|
|
11857
|
+
id: "codex",
|
|
11858
|
+
env: credentialEnvironment(options.env, options.credential),
|
|
11859
|
+
prompt: "argument",
|
|
11860
|
+
command: (context) => [
|
|
11861
|
+
options.binary ?? "codex",
|
|
11862
|
+
"exec",
|
|
11863
|
+
"--model",
|
|
11864
|
+
options.model,
|
|
11865
|
+
...options.reasoningEffort ? ["--config", `model_reasoning_effort=${options.reasoningEffort}`] : [],
|
|
11866
|
+
...options.extraArgs ?? []
|
|
11867
|
+
],
|
|
11868
|
+
sessionId: extractSessionId,
|
|
11869
|
+
parseEvent: parseJsonAgentEvent
|
|
11870
|
+
});
|
|
11855
11871
|
}
|
|
11856
11872
|
}
|
|
11857
11873
|
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
11870
|
-
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
const runtime = normalizedRuntime(option(options, "runtime") ?? process.env.SANDBLOCKS_RUNTIME ?? "development" ?? "development");
|
|
11875
|
-
const targetRoot = contained(root, option(options, "target") ?? ".locker", "runtime target");
|
|
11876
|
-
const runtimeRoot = path4.join(targetRoot, runtime);
|
|
11877
|
-
const config = contained(root, option(options, "config") ?? "config/dotlocker.config.ts", "Dotlocker config");
|
|
11878
|
-
const dotlocker = path4.resolve(root, option(options, "dotlocker") ?? path4.join("node_modules", ".bin", "dotlocker"));
|
|
11879
|
-
if (!await exists2(config))
|
|
11880
|
-
throw new Error(`Dotlocker config does not exist: ${config}`);
|
|
11881
|
-
if (!await exists2(dotlocker)) {
|
|
11882
|
-
throw new Error("Dotlocker is not installed; add @dotlocker/dotlocker to the workspace");
|
|
11883
|
-
}
|
|
11884
|
-
await rm2(targetRoot, { recursive: true, force: true });
|
|
11885
|
-
await mkdir2(runtimeRoot, { recursive: true, mode: 493 });
|
|
11886
|
-
await run(dotlocker, ["pull", "--config", config, "--runtime", runtime, "--out", runtimeRoot], root);
|
|
11887
|
-
const envFile = path4.join(runtimeRoot, ".env");
|
|
11888
|
-
if (!await exists2(envFile)) {
|
|
11889
|
-
throw new Error(`Dotlocker did not materialize ${path4.relative(root, envFile)}`);
|
|
11890
|
-
}
|
|
11891
|
-
await normalizePermissions(targetRoot);
|
|
11892
|
-
console.log(`runtime ${runtime}
|
|
11893
|
-
environment ${path4.relative(root, envFile)}`);
|
|
11894
|
-
}
|
|
11895
|
-
function run(executable, args, cwd) {
|
|
11896
|
-
return new Promise((resolve, reject) => {
|
|
11897
|
-
const child = spawn(executable, args, { cwd, env: process.env, stdio: "inherit" });
|
|
11898
|
-
child.once("error", reject);
|
|
11899
|
-
child.once("exit", (code, signal) => {
|
|
11900
|
-
if (code === 0)
|
|
11901
|
-
resolve();
|
|
11902
|
-
else
|
|
11903
|
-
reject(new Error(`Dotlocker pull failed (${signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`})`));
|
|
11874
|
+
class PiAgent extends CommandAgent {
|
|
11875
|
+
constructor(options) {
|
|
11876
|
+
super({
|
|
11877
|
+
id: "pi",
|
|
11878
|
+
env: credentialEnvironment(options.env, options.credential),
|
|
11879
|
+
prompt: "argument",
|
|
11880
|
+
command: (context) => [
|
|
11881
|
+
options.binary ?? "pi",
|
|
11882
|
+
"--print",
|
|
11883
|
+
"--model",
|
|
11884
|
+
options.model,
|
|
11885
|
+
...options.skills?.flatMap((skill) => ["--skill", skill]) ?? [],
|
|
11886
|
+
...options.extraArgs ?? []
|
|
11887
|
+
],
|
|
11888
|
+
sessionId: extractSessionId,
|
|
11889
|
+
parseEvent: parseJsonAgentEvent
|
|
11904
11890
|
});
|
|
11905
|
-
}
|
|
11891
|
+
}
|
|
11906
11892
|
}
|
|
11907
|
-
|
|
11893
|
+
function parseJsonAgentEvent(line, context) {
|
|
11894
|
+
let value;
|
|
11908
11895
|
try {
|
|
11909
|
-
|
|
11910
|
-
return true;
|
|
11896
|
+
value = JSON.parse(line);
|
|
11911
11897
|
} catch {
|
|
11912
|
-
return
|
|
11898
|
+
return { type: "raw", line, ...context };
|
|
11899
|
+
}
|
|
11900
|
+
const type = String(value.type ?? "");
|
|
11901
|
+
if (/tool/i.test(type)) {
|
|
11902
|
+
return {
|
|
11903
|
+
type: "toolCall",
|
|
11904
|
+
name: String(value.name ?? value.tool ?? value.tool_name ?? "tool"),
|
|
11905
|
+
...value.input !== undefined ? { input: value.input } : {},
|
|
11906
|
+
...context
|
|
11907
|
+
};
|
|
11913
11908
|
}
|
|
11909
|
+
const text = value.text ?? value.message ?? value.content;
|
|
11910
|
+
return typeof text === "string" ? { type: "text", text, ...context } : { type: "raw", line, ...context };
|
|
11914
11911
|
}
|
|
11915
|
-
function
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
11912
|
+
function credentialEnvironment(env, credential) {
|
|
11913
|
+
if (!credential)
|
|
11914
|
+
return env;
|
|
11915
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(credential.environment))
|
|
11916
|
+
throw new Error("credential environment name is invalid");
|
|
11917
|
+
const value = process.env[credential.environment];
|
|
11918
|
+
if (value === undefined)
|
|
11919
|
+
return env;
|
|
11920
|
+
const target = credential.target ?? credential.environment;
|
|
11921
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(target))
|
|
11922
|
+
throw new Error("credential target name is invalid");
|
|
11923
|
+
return { ...env, [target]: value };
|
|
11920
11924
|
}
|
|
11921
|
-
function
|
|
11922
|
-
const
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
return resolved;
|
|
11926
|
-
throw new Error(`${label} must remain within the repository`);
|
|
11925
|
+
function extractSessionId(stdout, stderr) {
|
|
11926
|
+
const match = `${stdout}
|
|
11927
|
+
${stderr}`.match(/(?:session[_ -]?id|session)[:=\s]+([A-Za-z0-9._-]{8,})/i);
|
|
11928
|
+
return match?.[1];
|
|
11927
11929
|
}
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
if (entry.isDirectory())
|
|
11935
|
-
await normalizePermissions(child);
|
|
11936
|
-
else if (entry.isFile())
|
|
11937
|
-
await chmod2(child, 420);
|
|
11938
|
-
else
|
|
11939
|
-
throw new Error("Dotlocker output contains an unsupported file type");
|
|
11930
|
+
var Output = {
|
|
11931
|
+
string(input) {
|
|
11932
|
+
return { kind: "string", tag: validTag(input.tag) };
|
|
11933
|
+
},
|
|
11934
|
+
object(input) {
|
|
11935
|
+
return { kind: "object", tag: validTag(input.tag), schema: input.schema };
|
|
11940
11936
|
}
|
|
11941
|
-
}
|
|
11942
|
-
function
|
|
11943
|
-
const
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
}
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
|
|
11937
|
+
};
|
|
11938
|
+
async function extractOutput(spec, text) {
|
|
11939
|
+
const pattern = new RegExp(`<${escapeRegex(spec.tag)}>([\\s\\S]*?)<\\/${escapeRegex(spec.tag)}>`);
|
|
11940
|
+
const match = text.match(pattern);
|
|
11941
|
+
if (!match)
|
|
11942
|
+
throw new Error(`structured output tag <${spec.tag}> was not found`);
|
|
11943
|
+
const content = match[1]?.trim();
|
|
11944
|
+
if (content === undefined)
|
|
11945
|
+
throw new Error(`structured output tag <${spec.tag}> is empty`);
|
|
11946
|
+
if (spec.kind === "string")
|
|
11947
|
+
return content;
|
|
11948
|
+
let value;
|
|
11949
|
+
try {
|
|
11950
|
+
value = JSON.parse(content);
|
|
11951
|
+
} catch {
|
|
11952
|
+
throw new Error(`structured output <${spec.tag}> is not valid JSON`);
|
|
11957
11953
|
}
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
"
|
|
11974
|
-
"deploy",
|
|
11975
|
-
"redeploy",
|
|
11976
|
-
"renew",
|
|
11977
|
-
"exec",
|
|
11978
|
-
"git",
|
|
11979
|
-
"down",
|
|
11980
|
-
"destroy"
|
|
11981
|
-
]);
|
|
11982
|
-
function isStatefulSandboxCommand(command) {
|
|
11983
|
-
return Boolean(command && COMMANDS.has(command));
|
|
11954
|
+
const schema = spec.schema;
|
|
11955
|
+
if (schema["~standard"]) {
|
|
11956
|
+
const result = await schema["~standard"].validate(value);
|
|
11957
|
+
if (result.issues?.length)
|
|
11958
|
+
throw new Error(`structured output validation failed: ${JSON.stringify(result.issues)}`);
|
|
11959
|
+
return result.value;
|
|
11960
|
+
}
|
|
11961
|
+
if (schema.safeParse) {
|
|
11962
|
+
const result = schema.safeParse(value);
|
|
11963
|
+
if (!result.success)
|
|
11964
|
+
throw new Error(`structured output validation failed: ${String(result.error)}`);
|
|
11965
|
+
return result.data;
|
|
11966
|
+
}
|
|
11967
|
+
if (schema.parse)
|
|
11968
|
+
return schema.parse(value);
|
|
11969
|
+
throw new Error("structured output schema is unsupported");
|
|
11984
11970
|
}
|
|
11985
|
-
|
|
11986
|
-
if (
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
return create(args, dependencies);
|
|
11990
|
-
if (command === "status")
|
|
11991
|
-
return status2(args, dependencies);
|
|
11992
|
-
if (command === "deploy")
|
|
11993
|
-
return deploy(args, dependencies);
|
|
11994
|
-
if (command === "redeploy")
|
|
11995
|
-
return redeploy(args, dependencies);
|
|
11996
|
-
if (command === "renew")
|
|
11997
|
-
return renew(args, dependencies);
|
|
11998
|
-
if (command === "exec")
|
|
11999
|
-
return exec(args, dependencies);
|
|
12000
|
-
if (command === "git")
|
|
12001
|
-
return git2(args, dependencies);
|
|
12002
|
-
return down(args, dependencies);
|
|
11971
|
+
function validTag(tag) {
|
|
11972
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(tag))
|
|
11973
|
+
throw new Error("output tag is invalid");
|
|
11974
|
+
return tag;
|
|
12003
11975
|
}
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
await create(args, dependencies);
|
|
12007
|
-
try {
|
|
12008
|
-
await deployUnlocked(args, dependencies);
|
|
12009
|
-
} catch (error) {
|
|
12010
|
-
if (process.env.SANDBLOCKS_RETAIN_FAILED !== "true") {
|
|
12011
|
-
await down(args, dependencies).catch(() => {
|
|
12012
|
-
return;
|
|
12013
|
-
});
|
|
12014
|
-
}
|
|
12015
|
-
throw error;
|
|
12016
|
-
}
|
|
12017
|
-
});
|
|
11976
|
+
function escapeRegex(value) {
|
|
11977
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12018
11978
|
}
|
|
12019
|
-
async function
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
const
|
|
12024
|
-
const
|
|
12025
|
-
if (
|
|
12026
|
-
throw new Error(
|
|
12027
|
-
const
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
const sandboxId = randomUUID();
|
|
12032
|
-
const workspaceId = `${sandboxId.replaceAll("-", "").slice(0, 8)}-${repository}`.slice(0, 63);
|
|
12033
|
-
const { apiUrl, apiKey } = client(options, dependencies.option, projectId);
|
|
12034
|
-
await request(apiUrl, apiKey, `/v1/projects/${encodeURIComponent(projectId)}/sandboxes`, {
|
|
12035
|
-
method: "POST",
|
|
12036
|
-
body: JSON.stringify({ id: sandboxId, goal: preview.goal, status: "active", repositories: [repository] })
|
|
11979
|
+
async function executeProcess(command, options = {}) {
|
|
11980
|
+
if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
|
|
11981
|
+
throw new Error("command must contain safe non-empty arguments");
|
|
11982
|
+
}
|
|
11983
|
+
const startedAt = new Date().toISOString();
|
|
11984
|
+
const [executable, ...args] = command;
|
|
11985
|
+
if (!executable)
|
|
11986
|
+
throw new Error("command executable is required");
|
|
11987
|
+
const child = spawn(executable, args, {
|
|
11988
|
+
cwd: options.cwd ?? options.hostCwd,
|
|
11989
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
11990
|
+
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
12037
11991
|
});
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
});
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
projectId,
|
|
12050
|
-
sandboxId,
|
|
12051
|
-
workspaceId,
|
|
12052
|
-
pool: dependencies.option(options, "pool") ?? process.env.SANDBLOCKS_WORKER_POOL ?? "sandbox-development",
|
|
12053
|
-
apiUrl,
|
|
12054
|
-
apiKey,
|
|
12055
|
-
idempotencyKey: `${sandboxId}:workspace-import`,
|
|
12056
|
-
source: dependencies.source,
|
|
12057
|
-
..."image" in loaded.manifest.workspace && loaded.manifest.workspace.image ? { workspaceImage: loaded.manifest.workspace.image } : {}
|
|
11992
|
+
if (options.stdin !== undefined)
|
|
11993
|
+
child.stdin?.end(options.stdin);
|
|
11994
|
+
let timedOut = false;
|
|
11995
|
+
let stdout = "";
|
|
11996
|
+
let stderr = "";
|
|
11997
|
+
let callbackQueue = Promise.resolve();
|
|
11998
|
+
child.stdout?.on("data", (chunk) => {
|
|
11999
|
+
const text = chunk.toString();
|
|
12000
|
+
stdout = `${stdout}${text}`.slice(-4 * 1024 * 1024);
|
|
12001
|
+
callbackQueue = callbackQueue.then(() => options.onStdout?.(text)).then(() => {
|
|
12002
|
+
return;
|
|
12058
12003
|
});
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
environment,
|
|
12065
|
-
projectId,
|
|
12066
|
-
sandboxId,
|
|
12067
|
-
workspaceId,
|
|
12068
|
-
repository,
|
|
12069
|
-
leaseId: lease.lease.id,
|
|
12070
|
-
leaseToken: lease.token,
|
|
12071
|
-
hostId,
|
|
12072
|
-
sequence: 0,
|
|
12073
|
-
createdAt: new Date().toISOString()
|
|
12074
|
-
};
|
|
12075
|
-
await writeState(file, state);
|
|
12076
|
-
console.log(`sandbox ${sandboxId}`);
|
|
12077
|
-
console.log(`workspace ${workspaceId}`);
|
|
12078
|
-
console.log(`host ${hostId}`);
|
|
12079
|
-
} catch (error) {
|
|
12080
|
-
await request(apiUrl, apiKey, `/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${sandboxId}`, {
|
|
12081
|
-
method: "DELETE"
|
|
12082
|
-
}).catch(() => {
|
|
12004
|
+
});
|
|
12005
|
+
child.stderr?.on("data", (chunk) => {
|
|
12006
|
+
const text = chunk.toString();
|
|
12007
|
+
stderr = `${stderr}${text}`.slice(-4 * 1024 * 1024);
|
|
12008
|
+
callbackQueue = callbackQueue.then(() => options.onStderr?.(text)).then(() => {
|
|
12083
12009
|
return;
|
|
12084
12010
|
});
|
|
12085
|
-
|
|
12011
|
+
});
|
|
12012
|
+
const stop = () => child.kill("SIGTERM");
|
|
12013
|
+
options.signal?.addEventListener("abort", stop, { once: true });
|
|
12014
|
+
const timer = options.timeoutMs ? setTimeout(() => {
|
|
12015
|
+
timedOut = true;
|
|
12016
|
+
child.kill("SIGKILL");
|
|
12017
|
+
}, options.timeoutMs) : undefined;
|
|
12018
|
+
try {
|
|
12019
|
+
const exitCode = await new Promise((resolve3, reject) => {
|
|
12020
|
+
child.once("error", reject);
|
|
12021
|
+
child.once("close", (code, signal) => resolve3(code ?? (signal ? 128 : 1)));
|
|
12022
|
+
});
|
|
12023
|
+
await callbackQueue;
|
|
12024
|
+
if (options.signal?.aborted)
|
|
12025
|
+
throw options.signal.reason ?? new Error("operation aborted");
|
|
12026
|
+
return {
|
|
12027
|
+
exitCode: timedOut ? 124 : exitCode,
|
|
12028
|
+
stdout,
|
|
12029
|
+
stderr,
|
|
12030
|
+
timedOut,
|
|
12031
|
+
startedAt,
|
|
12032
|
+
finishedAt: new Date().toISOString()
|
|
12033
|
+
};
|
|
12034
|
+
} finally {
|
|
12035
|
+
if (timer)
|
|
12036
|
+
clearTimeout(timer);
|
|
12037
|
+
options.signal?.removeEventListener("abort", stop);
|
|
12086
12038
|
}
|
|
12087
12039
|
}
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12040
|
+
class SandblocksSandbox {
|
|
12041
|
+
runtime;
|
|
12042
|
+
options;
|
|
12043
|
+
git;
|
|
12044
|
+
id;
|
|
12045
|
+
provider;
|
|
12046
|
+
cwd;
|
|
12047
|
+
failed = false;
|
|
12048
|
+
disposed = false;
|
|
12049
|
+
eventQueue = [];
|
|
12050
|
+
eventWaiters = [];
|
|
12051
|
+
constructor(runtime, options, git) {
|
|
12052
|
+
this.runtime = runtime;
|
|
12053
|
+
this.options = options;
|
|
12054
|
+
this.git = git;
|
|
12055
|
+
this.id = runtime.id;
|
|
12056
|
+
this.provider = runtime.provider;
|
|
12057
|
+
this.cwd = runtime.cwd;
|
|
12097
12058
|
}
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
console.log(`workspace ${context.state.workspaceId}`);
|
|
12101
|
-
console.log(`host ${context.state.hostId}`);
|
|
12102
|
-
console.log(`lease expires ${session.session.expiresAt}`);
|
|
12103
|
-
for (const route of context.state.previewUrls ?? [])
|
|
12104
|
-
console.log(`${route.service ?? "service"} ${route.url}`);
|
|
12105
|
-
}
|
|
12106
|
-
async function deploy(args, dependencies) {
|
|
12107
|
-
await withDeploymentLock(args, dependencies, () => deployUnlocked(args, dependencies));
|
|
12108
|
-
}
|
|
12109
|
-
async function withDeploymentLock(args, dependencies, action) {
|
|
12110
|
-
const options = dependencies.parse(args);
|
|
12111
|
-
const root = path5.resolve(options.positionals[0] ?? process.cwd());
|
|
12112
|
-
const environment = dependencies.option(options, "environment") ?? "preview";
|
|
12113
|
-
const lock = `${statePath(root, environment)}.deploy.lock`;
|
|
12114
|
-
await mkdir3(path5.dirname(lock), { recursive: true, mode: 448 });
|
|
12115
|
-
try {
|
|
12116
|
-
await mkdir3(lock);
|
|
12117
|
-
} catch (error) {
|
|
12118
|
-
if (error.code === "EEXIST") {
|
|
12119
|
-
throw new Error(`sandbox '${environment}' deployment is already running`);
|
|
12120
|
-
}
|
|
12121
|
-
throw error;
|
|
12059
|
+
exec(command, options = {}) {
|
|
12060
|
+
return this.runtime.exec(command, options);
|
|
12122
12061
|
}
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12062
|
+
upload(source, destination) {
|
|
12063
|
+
if (!this.runtime.upload)
|
|
12064
|
+
throw new Error(`${this.provider} provider does not support upload`);
|
|
12065
|
+
return this.runtime.upload(source, destination);
|
|
12127
12066
|
}
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
throw new Error("preview is already deployed; use sandbox redeploy");
|
|
12133
|
-
const loaded = await dependencies.load(context.root, dependencies.option(context.options, "config") ?? dependencies.option(context.options, "manifest"));
|
|
12134
|
-
const stack = loaded.manifest.name ?? context.state.repository;
|
|
12135
|
-
const preview = await applyManagedRoutingPolicy(context, stack, selectPreview(loaded, context.state.environment));
|
|
12136
|
-
const targetHost = await resolveAssignedTargetHost(context, process.env.SANDBLOCKS_SANDBOX_TARGET_HOST ?? preview.targetHost);
|
|
12137
|
-
if (!targetHost)
|
|
12138
|
-
throw new Error("assigned host targetHost label or preview targetHost is required");
|
|
12139
|
-
const deploymentId = randomUUID();
|
|
12140
|
-
const environmentSnapshotId = await createEnvironmentSnapshot(context);
|
|
12141
|
-
const serviceUrls = expectedPreviewServiceUrls(context.state.sandboxId, deploymentId, context.state.repository, preview);
|
|
12142
|
-
const serviceSteps = resolveServiceSteps(loaded.manifest, context.state.environment);
|
|
12143
|
-
if (serviceSteps.length) {
|
|
12144
|
-
const stepOperation = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/operations`, {
|
|
12145
|
-
method: "POST",
|
|
12146
|
-
headers: {
|
|
12147
|
-
"idempotency-key": `preview:${deploymentId}:steps`
|
|
12148
|
-
},
|
|
12149
|
-
body: JSON.stringify({
|
|
12150
|
-
kind: "workspace.step.run",
|
|
12151
|
-
stack: context.state.repository,
|
|
12152
|
-
environmentId: context.state.environment,
|
|
12153
|
-
environmentSnapshotId,
|
|
12154
|
-
payload: {
|
|
12155
|
-
workspaceId: context.state.workspaceId,
|
|
12156
|
-
sandboxId: context.state.sandboxId,
|
|
12157
|
-
revisionId: deploymentId,
|
|
12158
|
-
runtimeType: preview.deploymentType,
|
|
12159
|
-
serviceUrls,
|
|
12160
|
-
steps: serviceSteps,
|
|
12161
|
-
placement: { hostId: context.state.hostId }
|
|
12162
|
-
},
|
|
12163
|
-
maxAttempts: 1
|
|
12164
|
-
})
|
|
12165
|
-
});
|
|
12166
|
-
await wait(context.apiUrl, context.apiKey, stepOperation.operation.id, 60 * 60000);
|
|
12067
|
+
download(source, destination) {
|
|
12068
|
+
if (!this.runtime.download)
|
|
12069
|
+
throw new Error(`${this.provider} provider does not support download`);
|
|
12070
|
+
return this.runtime.download(source, destination);
|
|
12167
12071
|
}
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
headers: {
|
|
12171
|
-
"idempotency-key": `preview:${deploymentId}:deploy`
|
|
12172
|
-
},
|
|
12173
|
-
body: JSON.stringify({
|
|
12174
|
-
kind: "workspace.service.deploy",
|
|
12175
|
-
stack: context.state.repository,
|
|
12176
|
-
environmentId: context.state.environment,
|
|
12177
|
-
environmentSnapshotId,
|
|
12178
|
-
payload: {
|
|
12179
|
-
workspaceId: context.state.workspaceId,
|
|
12180
|
-
deploymentId,
|
|
12181
|
-
sandboxId: context.state.sandboxId,
|
|
12182
|
-
runtimeType: preview.deploymentType,
|
|
12183
|
-
stack,
|
|
12184
|
-
workspace: preview.workspace,
|
|
12185
|
-
targetHost,
|
|
12186
|
-
serviceUrls,
|
|
12187
|
-
services: preview.services,
|
|
12188
|
-
placement: { hostId: context.state.hostId }
|
|
12189
|
-
},
|
|
12190
|
-
maxAttempts: 1
|
|
12191
|
-
})
|
|
12192
|
-
});
|
|
12193
|
-
const operation = await wait(context.apiUrl, context.apiKey, submitted.operation.id, 30 * 60000);
|
|
12194
|
-
context.state.deploymentId = deploymentId;
|
|
12195
|
-
await writeState(context.file, context.state);
|
|
12196
|
-
const routes = operation.result?.serviceRoutes ?? [];
|
|
12197
|
-
const images = operation.result?.images ?? [];
|
|
12198
|
-
if (typeof preview.ssoEnabled === "boolean") {
|
|
12199
|
-
await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/routing-policies/${encodeURIComponent(stack)}/${encodeURIComponent(preview.deploymentType)}`, { method: "PUT", body: JSON.stringify({ ssoEnabled: preview.ssoEnabled }) });
|
|
12072
|
+
stop(reason) {
|
|
12073
|
+
return this.runtime.stop(reason);
|
|
12200
12074
|
}
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
const
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
`SANDBLOCKS_SERVICE_${route.service.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_URL`,
|
|
12210
|
-
route.url
|
|
12211
|
-
]
|
|
12212
|
-
] : []));
|
|
12213
|
-
const check = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/operations`, {
|
|
12214
|
-
method: "POST",
|
|
12215
|
-
headers: {
|
|
12216
|
-
"idempotency-key": `preview:${deploymentId}:checks`
|
|
12217
|
-
},
|
|
12218
|
-
body: JSON.stringify({
|
|
12219
|
-
kind: "workspace.check.run",
|
|
12220
|
-
stack: context.state.repository,
|
|
12221
|
-
environmentId: context.state.environment,
|
|
12222
|
-
environmentSnapshotId,
|
|
12223
|
-
payload: {
|
|
12224
|
-
workspaceId: context.state.workspaceId,
|
|
12225
|
-
deploymentId,
|
|
12226
|
-
sandboxId: context.state.sandboxId,
|
|
12227
|
-
runtimeType: preview.deploymentType,
|
|
12228
|
-
checks: preview.steps,
|
|
12229
|
-
serviceUrls: serviceUrls2,
|
|
12230
|
-
placement: { hostId: context.state.hostId }
|
|
12231
|
-
},
|
|
12232
|
-
maxAttempts: 1
|
|
12233
|
-
})
|
|
12234
|
-
});
|
|
12235
|
-
const checked = await wait(context.apiUrl, context.apiKey, check.operation.id, 60 * 60000);
|
|
12236
|
-
checkEvidence = checked.result?.checks ?? [];
|
|
12237
|
-
if (checked.result?.passed !== true) {
|
|
12238
|
-
throw new Error(`one or more post-deploy checks failed: ${JSON.stringify(checkEvidence)}`);
|
|
12075
|
+
async* events(options = {}) {
|
|
12076
|
+
while (!this.disposed || this.eventQueue.length) {
|
|
12077
|
+
if (options.signal?.aborted)
|
|
12078
|
+
return;
|
|
12079
|
+
const queued = this.eventQueue.shift();
|
|
12080
|
+
if (queued) {
|
|
12081
|
+
yield queued;
|
|
12082
|
+
continue;
|
|
12239
12083
|
}
|
|
12240
|
-
|
|
12084
|
+
const event = await new Promise((resolve22) => {
|
|
12085
|
+
const abort = () => resolve22(undefined);
|
|
12086
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
12087
|
+
this.eventWaiters.push((value) => {
|
|
12088
|
+
options.signal?.removeEventListener("abort", abort);
|
|
12089
|
+
resolve22(value);
|
|
12090
|
+
});
|
|
12091
|
+
});
|
|
12092
|
+
if (!event)
|
|
12093
|
+
return;
|
|
12094
|
+
yield event;
|
|
12241
12095
|
}
|
|
12242
|
-
} catch (error) {
|
|
12243
|
-
await recordSandbox(context, loaded, preview, routes, [...operation.result?.healthEvidence ?? [], ...checkEvidence], images, "failed").catch(() => {
|
|
12244
|
-
return;
|
|
12245
|
-
});
|
|
12246
|
-
await releaseEnvironmentSnapshot(context, environmentSnapshotId).catch(() => {
|
|
12247
|
-
return;
|
|
12248
|
-
});
|
|
12249
|
-
throw error;
|
|
12250
12096
|
}
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12097
|
+
async run(options) {
|
|
12098
|
+
if (options.output && (options.maxIterations ?? 1) !== 1)
|
|
12099
|
+
throw new Error("structured output requires maxIterations === 1");
|
|
12100
|
+
const startedAt = new Date().toISOString();
|
|
12101
|
+
const prompt = await resolvePrompt(options);
|
|
12102
|
+
const signals = array(options.completionSignal ?? "<promise>COMPLETE</promise>");
|
|
12103
|
+
const logger = createLogger(options.logging, this.git.hostCwd);
|
|
12104
|
+
const baseline = await gitHead(this.git.runtimeCwd);
|
|
12105
|
+
const iterations = [];
|
|
12106
|
+
try {
|
|
12107
|
+
await runSandboxHooks(this.runtime, options.hooks?.sandbox?.beforeAgent);
|
|
12108
|
+
for (let iteration = 1;iteration <= (options.maxIterations ?? 1); iteration += 1) {
|
|
12109
|
+
const result = await options.agent.run({
|
|
12110
|
+
runtime: this.runtime,
|
|
12111
|
+
prompt,
|
|
12112
|
+
iteration,
|
|
12113
|
+
signal: options.signal,
|
|
12114
|
+
idleTimeoutSeconds: options.idleTimeoutSeconds ?? 600,
|
|
12115
|
+
completionTimeoutSeconds: options.completionTimeoutSeconds ?? 60,
|
|
12116
|
+
completionSignal: signals,
|
|
12117
|
+
emit: async (event) => {
|
|
12118
|
+
this.publish(event);
|
|
12119
|
+
await logger(event);
|
|
12120
|
+
}
|
|
12121
|
+
});
|
|
12122
|
+
iterations.push(result);
|
|
12123
|
+
if (result.exitCode !== 0)
|
|
12124
|
+
throw new Error(result.stderr || `${options.agent.id} exited ${result.exitCode}`);
|
|
12125
|
+
if (result.completionSignal)
|
|
12126
|
+
break;
|
|
12127
|
+
}
|
|
12128
|
+
await runSandboxHooks(this.runtime, options.hooks?.sandbox?.afterAgent);
|
|
12129
|
+
await this.runtime.syncToHost?.();
|
|
12130
|
+
const commits = await collectCommits(this.git.runtimeCwd, baseline, this.options.timeouts?.commitCollectionMs);
|
|
12131
|
+
if (this.git.mergeToHead && this.git.branch)
|
|
12132
|
+
await mergeBranch(this.git, this.options.timeouts?.mergeToHostMs);
|
|
12133
|
+
const allOutput = iterations.map((item) => item.stdout).join(`
|
|
12134
|
+
`);
|
|
12135
|
+
const completionSignal = iterations.find((item) => item.completionSignal)?.completionSignal;
|
|
12136
|
+
return {
|
|
12137
|
+
sandboxId: this.id,
|
|
12138
|
+
provider: this.provider,
|
|
12139
|
+
iterations,
|
|
12140
|
+
commits,
|
|
12141
|
+
...this.git.branch ? { branch: this.git.branch } : {},
|
|
12142
|
+
...completionSignal ? { completionSignal } : {},
|
|
12143
|
+
...options.output ? { output: await extractOutput(options.output, allOutput) } : {},
|
|
12144
|
+
startedAt,
|
|
12145
|
+
finishedAt: new Date().toISOString()
|
|
12146
|
+
};
|
|
12147
|
+
} catch (error) {
|
|
12148
|
+
this.failed = true;
|
|
12149
|
+
throw error;
|
|
12297
12150
|
}
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12151
|
+
}
|
|
12152
|
+
async destroy() {
|
|
12153
|
+
if (this.disposed)
|
|
12154
|
+
return;
|
|
12155
|
+
this.disposed = true;
|
|
12156
|
+
await runHostHooks(this.options.hooks?.host?.beforeDestroy, this.git.hostCwd);
|
|
12157
|
+
const retention = this.options.retention ?? "destroy";
|
|
12158
|
+
if (retention === "destroy" || retention === "on-failure" && !this.failed)
|
|
12159
|
+
await this.runtime.destroy();
|
|
12160
|
+
for (const waiter of this.eventWaiters.splice(0))
|
|
12161
|
+
waiter(undefined);
|
|
12162
|
+
if (this.git.worktree && retention !== "always") {
|
|
12163
|
+
await executeProcess([
|
|
12164
|
+
"git",
|
|
12165
|
+
"-C",
|
|
12166
|
+
this.git.hostCwd,
|
|
12167
|
+
"worktree",
|
|
12168
|
+
"remove",
|
|
12169
|
+
"--force",
|
|
12170
|
+
this.git.worktree
|
|
12171
|
+
]);
|
|
12172
|
+
await rm(this.git.worktree, { recursive: true, force: true });
|
|
12173
|
+
}
|
|
12174
|
+
}
|
|
12175
|
+
async[Symbol.asyncDispose]() {
|
|
12176
|
+
await this.destroy();
|
|
12177
|
+
}
|
|
12178
|
+
publish(event) {
|
|
12179
|
+
const waiter = this.eventWaiters.shift();
|
|
12180
|
+
if (waiter)
|
|
12181
|
+
waiter(event);
|
|
12182
|
+
else
|
|
12183
|
+
this.eventQueue.push(event);
|
|
12302
12184
|
}
|
|
12303
12185
|
}
|
|
12304
|
-
async function
|
|
12305
|
-
const
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
const
|
|
12312
|
-
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
|
|
12317
|
-
|
|
12186
|
+
async function createSandbox(options) {
|
|
12187
|
+
const provider = options.provider ?? options.sandbox;
|
|
12188
|
+
if (!provider)
|
|
12189
|
+
throw new Error("sandbox provider is required");
|
|
12190
|
+
assertProviderCapabilities(provider, options);
|
|
12191
|
+
const hostCwd = resolve(options.cwd ?? process.cwd());
|
|
12192
|
+
await runHostHooks(options.hooks?.host?.beforeCreate, hostCwd);
|
|
12193
|
+
const branchStrategy = options.branch ?? options.branchStrategy ?? { type: "head" };
|
|
12194
|
+
if (options.copyToWorktree?.length && branchStrategy.type === "head") {
|
|
12195
|
+
throw new Error("copyToWorktree is not supported with the head branch strategy");
|
|
12196
|
+
}
|
|
12197
|
+
const git = await prepareGit(hostCwd, branchStrategy, options.timeouts?.gitSetupMs);
|
|
12198
|
+
await runHostHooks(options.hooks?.host?.onWorktreeReady, git.runtimeCwd);
|
|
12199
|
+
for (const file of options.copyToWorktree ?? []) {
|
|
12200
|
+
const source = resolve(hostCwd, file);
|
|
12201
|
+
const destination = resolve(git.runtimeCwd, file);
|
|
12202
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
12203
|
+
await withTimeout(cp(source, destination, { recursive: true }), options.timeouts?.copyToWorktreeMs ?? 60000, `copying '${file}'`);
|
|
12204
|
+
}
|
|
12205
|
+
const id2 = options.id ?? randomUUID();
|
|
12206
|
+
const runtime = await provider.create({
|
|
12207
|
+
id: id2,
|
|
12208
|
+
cwd: git.runtimeCwd,
|
|
12209
|
+
image: options.image,
|
|
12210
|
+
env: options.env,
|
|
12211
|
+
mounts: options.mounts,
|
|
12212
|
+
network: options.network,
|
|
12213
|
+
groups: options.groups,
|
|
12214
|
+
devices: options.devices,
|
|
12215
|
+
cpus: options.cpus,
|
|
12216
|
+
memoryMb: options.memoryMb,
|
|
12217
|
+
pids: options.pids,
|
|
12218
|
+
tmpfsMb: options.tmpfsMb,
|
|
12219
|
+
signal: options.signal,
|
|
12220
|
+
idempotencyKey: options.idempotencyKey,
|
|
12221
|
+
metadata: options.metadata
|
|
12318
12222
|
});
|
|
12319
|
-
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
await
|
|
12223
|
+
const sandbox = new SandblocksSandbox(runtime, options, git);
|
|
12224
|
+
try {
|
|
12225
|
+
await runHostHooks(options.hooks?.host?.onSandboxReady, hostCwd);
|
|
12226
|
+
await runHostHooks(options.hooks?.host?.afterCreate, hostCwd);
|
|
12227
|
+
await runSandboxHooks(runtime, options.hooks?.sandbox?.onSandboxReady);
|
|
12228
|
+
await runSandboxHooks(runtime, options.hooks?.sandbox?.afterCreate);
|
|
12229
|
+
return sandbox;
|
|
12230
|
+
} catch (error) {
|
|
12231
|
+
await sandbox.destroy().catch(() => {
|
|
12324
12232
|
return;
|
|
12325
12233
|
});
|
|
12234
|
+
throw error;
|
|
12326
12235
|
}
|
|
12327
|
-
return lease;
|
|
12328
12236
|
}
|
|
12329
|
-
async function
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
const
|
|
12334
|
-
|
|
12335
|
-
const operation = await dataOperation(context, "exec", { executable: command[0], args: command.slice(1) });
|
|
12336
|
-
printOutput(operation);
|
|
12237
|
+
async function connectSandbox(options) {
|
|
12238
|
+
if (!options.provider.reconnect)
|
|
12239
|
+
throw new Error(`${options.provider.kind} provider does not support reconnect`);
|
|
12240
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
12241
|
+
const runtime = await options.provider.reconnect({ id: options.id, cwd });
|
|
12242
|
+
return new SandblocksSandbox(runtime, { provider: options.provider, cwd, retention: options.retention ?? "always" }, { hostCwd: cwd, runtimeCwd: cwd });
|
|
12337
12243
|
}
|
|
12338
|
-
async function
|
|
12339
|
-
const
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
throw new Error("sandbox git action is required");
|
|
12346
|
-
const rest = args.slice(actionIndex + 1);
|
|
12347
|
-
const operation = await dataOperation(context, "git", action === "checkout" ? { action, branch: rest[0] } : { action, ...rest.length ? { paths: rest } : {} });
|
|
12348
|
-
printOutput(operation);
|
|
12244
|
+
async function run(options) {
|
|
12245
|
+
const sandbox = await createSandbox(options);
|
|
12246
|
+
try {
|
|
12247
|
+
return await sandbox.run(options);
|
|
12248
|
+
} finally {
|
|
12249
|
+
await sandbox.destroy();
|
|
12250
|
+
}
|
|
12349
12251
|
}
|
|
12350
|
-
async function
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
})
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
});
|
|
12359
|
-
await rm3(context.file, { force: true });
|
|
12360
|
-
console.log(`sandbox ${context.state.sandboxId} destroyed`);
|
|
12252
|
+
async function resolvePrompt(options) {
|
|
12253
|
+
if (Boolean(options.prompt) === Boolean(options.promptFile))
|
|
12254
|
+
throw new Error("provide exactly one of prompt or promptFile");
|
|
12255
|
+
const promptFile = options.promptFile;
|
|
12256
|
+
let prompt = options.prompt ?? await readFile3(resolve(process.cwd(), promptFile ?? ""), "utf8");
|
|
12257
|
+
for (const [key, value] of Object.entries(options.promptArgs ?? {}))
|
|
12258
|
+
prompt = prompt.replaceAll(`{{${key}}}`, value);
|
|
12259
|
+
return prompt;
|
|
12361
12260
|
}
|
|
12362
|
-
async function
|
|
12363
|
-
if (
|
|
12364
|
-
return
|
|
12365
|
-
const
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12261
|
+
async function prepareGit(hostCwd, strategy, timeoutMs = 30000) {
|
|
12262
|
+
if (strategy.type === "head")
|
|
12263
|
+
return { hostCwd, runtimeCwd: hostCwd };
|
|
12264
|
+
const originalHead = await gitHead(hostCwd);
|
|
12265
|
+
if (strategy.type === "branch") {
|
|
12266
|
+
await checked(["git", "-C", hostCwd, "switch", "-C", strategy.name], timeoutMs);
|
|
12267
|
+
return { hostCwd, runtimeCwd: hostCwd, branch: strategy.name, originalHead };
|
|
12268
|
+
}
|
|
12269
|
+
const branch = strategy.name ?? `sandblocks/${randomUUID().slice(0, 8)}`;
|
|
12270
|
+
const worktree = resolve(hostCwd, ".sandblocks", "worktrees", branch.replace(/[^A-Za-z0-9_.-]/g, "-"));
|
|
12271
|
+
await mkdir(dirname(worktree), { recursive: true });
|
|
12272
|
+
await checked(["git", "-C", hostCwd, "worktree", "add", "-B", branch, worktree, "HEAD"], timeoutMs);
|
|
12273
|
+
return {
|
|
12274
|
+
hostCwd,
|
|
12275
|
+
runtimeCwd: worktree,
|
|
12276
|
+
branch,
|
|
12277
|
+
worktree,
|
|
12278
|
+
mergeToHead: strategy.type === "merge-to-head",
|
|
12279
|
+
originalHead
|
|
12280
|
+
};
|
|
12369
12281
|
}
|
|
12370
|
-
async function
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
const body = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/sandboxes`, {
|
|
12375
|
-
method: "POST",
|
|
12376
|
-
body: JSON.stringify({
|
|
12377
|
-
id: context.state.sandboxId,
|
|
12378
|
-
goal: preview.goal,
|
|
12379
|
-
status: status3,
|
|
12380
|
-
stack: loaded.manifest.name ?? context.state.repository,
|
|
12381
|
-
deploymentType: preview.deploymentType,
|
|
12382
|
-
revisionId: deploymentId,
|
|
12383
|
-
generation: Date.now(),
|
|
12384
|
-
baseDomain: preview.baseDomain,
|
|
12385
|
-
domains: await managedServiceDomains(context, loaded.manifest.name ?? context.state.repository, preview, deploymentId),
|
|
12386
|
-
stableDomains: preview.stableDomains,
|
|
12387
|
-
serviceRoutes,
|
|
12388
|
-
healthEvidence,
|
|
12389
|
-
images,
|
|
12390
|
-
volumes: [
|
|
12391
|
-
{
|
|
12392
|
-
name: `${context.state.repository}-workspace`,
|
|
12393
|
-
target: "/workspace",
|
|
12394
|
-
access: "read-write"
|
|
12395
|
-
}
|
|
12396
|
-
],
|
|
12397
|
-
repositories: [context.state.repository],
|
|
12398
|
-
executor: "sandblocks"
|
|
12399
|
-
})
|
|
12400
|
-
});
|
|
12401
|
-
return body.sandbox;
|
|
12282
|
+
async function mergeBranch(git, timeoutMs = 60000) {
|
|
12283
|
+
if (!git.branch)
|
|
12284
|
+
throw new Error("merge branch is unavailable");
|
|
12285
|
+
await checked(["git", "-C", git.hostCwd, "merge", "--no-edit", git.branch], timeoutMs);
|
|
12402
12286
|
}
|
|
12403
|
-
async function
|
|
12404
|
-
const
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12287
|
+
async function gitHead(cwd) {
|
|
12288
|
+
const result = await executeProcess(["git", "-C", cwd, "rev-parse", "HEAD"]);
|
|
12289
|
+
return result.exitCode === 0 ? result.stdout.trim() : "";
|
|
12290
|
+
}
|
|
12291
|
+
async function collectCommits(cwd, baseline, timeoutMs = 30000) {
|
|
12292
|
+
if (!baseline)
|
|
12293
|
+
return [];
|
|
12294
|
+
const result = await executeProcess(["git", "-C", cwd, "log", "--format=%H%x00%s", `${baseline}..HEAD`], {
|
|
12295
|
+
timeoutMs
|
|
12296
|
+
});
|
|
12297
|
+
if (result.exitCode !== 0 || !result.stdout.trim())
|
|
12298
|
+
return [];
|
|
12299
|
+
return result.stdout.trim().split(`
|
|
12300
|
+
`).map((line) => {
|
|
12301
|
+
const [sha, message] = line.split("\x00");
|
|
12302
|
+
if (!sha)
|
|
12303
|
+
throw new Error("Git returned an invalid commit record");
|
|
12304
|
+
return { sha, ...message ? { message } : {} };
|
|
12411
12305
|
});
|
|
12412
|
-
const id2 = body.snapshot?.id;
|
|
12413
|
-
if (typeof id2 !== "string" || !/^[a-f0-9-]{36}$/.test(id2)) {
|
|
12414
|
-
throw new Error("managed environment snapshot response is invalid");
|
|
12415
|
-
}
|
|
12416
|
-
return id2;
|
|
12417
12306
|
}
|
|
12418
|
-
async function
|
|
12419
|
-
await
|
|
12307
|
+
async function checked(command, timeoutMs) {
|
|
12308
|
+
const result = await executeProcess(command, { timeoutMs });
|
|
12309
|
+
if (result.exitCode !== 0)
|
|
12310
|
+
throw new Error(`${command[0]} failed: ${result.stderr || result.stdout}`);
|
|
12420
12311
|
}
|
|
12421
|
-
async function
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12312
|
+
async function runHostHooks(hooks, cwd) {
|
|
12313
|
+
for (const hook of hooks ?? []) {
|
|
12314
|
+
const command = hookCommand(hook);
|
|
12315
|
+
const result = await executeProcess(command, {
|
|
12316
|
+
cwd: resolve(cwd, hook.cwd ?? "."),
|
|
12317
|
+
env: hook.env,
|
|
12318
|
+
timeoutMs: hook.timeoutMs
|
|
12319
|
+
});
|
|
12320
|
+
if (result.exitCode !== 0)
|
|
12321
|
+
throw new Error(`host hook failed: ${result.stderr || result.stdout}`);
|
|
12322
|
+
}
|
|
12428
12323
|
}
|
|
12429
|
-
async function
|
|
12430
|
-
const
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
}
|
|
12439
|
-
await wait(context.apiUrl, context.apiKey, body.operation.id);
|
|
12324
|
+
async function runSandboxHooks(runtime, hooks) {
|
|
12325
|
+
for (const hook of hooks ?? []) {
|
|
12326
|
+
const result = await runtime.exec(hookCommand(hook), {
|
|
12327
|
+
cwd: hook.cwd,
|
|
12328
|
+
env: hook.env,
|
|
12329
|
+
timeoutMs: hook.timeoutMs
|
|
12330
|
+
});
|
|
12331
|
+
if (result.exitCode !== 0)
|
|
12332
|
+
throw new Error(`sandbox hook failed: ${result.stderr || result.stdout}`);
|
|
12333
|
+
}
|
|
12440
12334
|
}
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
body: JSON.stringify({ idempotencyKey })
|
|
12450
|
-
});
|
|
12451
|
-
const body = await response.json().catch(() => ({}));
|
|
12452
|
-
if (response.ok)
|
|
12453
|
-
await wait(context.apiUrl, context.apiKey, body.operation.id);
|
|
12454
|
-
else if (![404, 409].includes(response.status))
|
|
12455
|
-
throw new Error(String(body.error ?? "workspace destroy failed"));
|
|
12335
|
+
function hookCommand(hook) {
|
|
12336
|
+
if (Boolean(hook.command) === Boolean(hook.shell))
|
|
12337
|
+
throw new Error("hook requires exactly one of command or shell");
|
|
12338
|
+
if (hook.command)
|
|
12339
|
+
return hook.command;
|
|
12340
|
+
if (!hook.shell)
|
|
12341
|
+
throw new Error("hook shell command is required");
|
|
12342
|
+
return ["sh", "-lc", hook.shell];
|
|
12456
12343
|
}
|
|
12457
|
-
|
|
12458
|
-
const
|
|
12459
|
-
const
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
"
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
const submitted = await response.json().catch(() => ({}));
|
|
12474
|
-
if (!response.ok)
|
|
12475
|
-
throw new Error(String(submitted?.error ?? `sandbox import failed (${response.status})`));
|
|
12476
|
-
return {
|
|
12477
|
-
files: source.files,
|
|
12478
|
-
bytes: source.bundle.byteLength,
|
|
12479
|
-
operation: await wait(input.apiUrl, input.apiKey, submitted.operation.id)
|
|
12344
|
+
function createLogger(options, cwd) {
|
|
12345
|
+
const config = options ?? { type: "file" };
|
|
12346
|
+
const file = resolve(cwd, config.path ?? `.sandblocks/logs/${Date.now()}.log`);
|
|
12347
|
+
return async (event) => {
|
|
12348
|
+
const line = `${JSON.stringify(event)}
|
|
12349
|
+
`;
|
|
12350
|
+
if (config.type === "stdout")
|
|
12351
|
+
process.stdout.write(event.type === "text" ? event.text : event.type === "raw" ? `${event.line}
|
|
12352
|
+
` : line);
|
|
12353
|
+
if (config.type !== "stdout" && config.type !== "silent") {
|
|
12354
|
+
await mkdir(dirname(file), { recursive: true });
|
|
12355
|
+
await appendFile(file, line);
|
|
12356
|
+
}
|
|
12357
|
+
try {
|
|
12358
|
+
await config.onAgentStreamEvent?.(event);
|
|
12359
|
+
} catch {}
|
|
12480
12360
|
};
|
|
12481
12361
|
}
|
|
12482
|
-
async function
|
|
12483
|
-
|
|
12484
|
-
await writeState(context.file, context.state);
|
|
12485
|
-
const submitted = await leaseRequest(context, `/v1/sandboxes/${context.state.sandboxId}/workspaces/${context.state.repository}/${action}`, {
|
|
12486
|
-
method: "POST",
|
|
12487
|
-
headers: { "idempotency-key": `cli:${context.state.sandboxId}:${context.state.sequence}:${action}` },
|
|
12488
|
-
body: JSON.stringify(body)
|
|
12489
|
-
});
|
|
12490
|
-
return waitLease(context, submitted.operation.id);
|
|
12491
|
-
}
|
|
12492
|
-
async function getContext(args, dependencies) {
|
|
12493
|
-
const options = dependencies.parse(args);
|
|
12494
|
-
const root = path5.resolve(options.positionals[0] ?? process.cwd());
|
|
12495
|
-
await loadLocalEnvironment(root);
|
|
12496
|
-
const environment = dependencies.option(options, "environment") ?? "preview";
|
|
12497
|
-
const file = statePath(root, environment);
|
|
12498
|
-
let state;
|
|
12362
|
+
async function withTimeout(promise, timeoutMs, label) {
|
|
12363
|
+
let timer;
|
|
12499
12364
|
try {
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12365
|
+
return await Promise.race([
|
|
12366
|
+
promise,
|
|
12367
|
+
new Promise((_, reject) => {
|
|
12368
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
12369
|
+
})
|
|
12370
|
+
]);
|
|
12371
|
+
} finally {
|
|
12372
|
+
if (timer)
|
|
12373
|
+
clearTimeout(timer);
|
|
12503
12374
|
}
|
|
12504
|
-
const configured = client(options, dependencies.option, state.projectId);
|
|
12505
|
-
return { root, file, options, state, ...configured };
|
|
12506
|
-
}
|
|
12507
|
-
function selectPreview(loaded, id2) {
|
|
12508
|
-
return resolveEnvironment(loaded.manifest, id2);
|
|
12509
|
-
}
|
|
12510
|
-
function client(options, option2, projectId) {
|
|
12511
|
-
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
12512
|
-
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY ?? "";
|
|
12513
|
-
if (!apiUrl || !apiKey)
|
|
12514
|
-
throw new Error("SANDBLOCKS_API_URL and SANDBLOCKS_API_KEY are required");
|
|
12515
|
-
return { apiUrl, apiKey, projectId };
|
|
12516
12375
|
}
|
|
12517
|
-
|
|
12518
|
-
const
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12376
|
+
function assertProviderCapabilities(provider, options) {
|
|
12377
|
+
const capabilities = provider.capabilities;
|
|
12378
|
+
if (options.mounts?.length && !capabilities.bindMounts)
|
|
12379
|
+
throw new Error(`${provider.kind} provider does not support bind mounts`);
|
|
12380
|
+
if (options.network && !capabilities.networks)
|
|
12381
|
+
throw new Error(`${provider.kind} provider does not support network selection`);
|
|
12382
|
+
if (options.devices?.length && !capabilities.devices)
|
|
12383
|
+
throw new Error(`${provider.kind} provider does not support devices`);
|
|
12384
|
+
if ((options.retention === "always" || options.retention === "on-failure") && !capabilities.persistent)
|
|
12385
|
+
throw new Error(`${provider.kind} provider does not support retained sandboxes`);
|
|
12527
12386
|
}
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
headers.set("authorization", `Bearer ${context.state.leaseToken}`);
|
|
12531
|
-
if (init.body)
|
|
12532
|
-
headers.set("content-type", "application/json");
|
|
12533
|
-
const response = await fetchWithRetry(`${context.apiUrl}${pathname}`, { ...init, headers });
|
|
12534
|
-
const body = await response.json().catch(() => ({}));
|
|
12535
|
-
if (!response.ok)
|
|
12536
|
-
throw new Error(String(body.error ?? `Sandblocks lease request failed (${response.status})`));
|
|
12537
|
-
return body;
|
|
12387
|
+
function array(value) {
|
|
12388
|
+
return Array.isArray(value) ? value : [value];
|
|
12538
12389
|
}
|
|
12539
|
-
async function
|
|
12540
|
-
const
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12390
|
+
async function initSdk(options = {}) {
|
|
12391
|
+
const cwd = resolve2(options.cwd ?? process.cwd());
|
|
12392
|
+
const root = resolve2(cwd, ".sandblocks");
|
|
12393
|
+
const files = {
|
|
12394
|
+
"main.ts": `import { ClaudeCodeAgent, createSandbox } from "@sandblocks/sdk";
|
|
12395
|
+
import { DockerProvider } from "@sandblocks/sdk/providers/docker";
|
|
12396
|
+
|
|
12397
|
+
await using sandbox = await createSandbox({
|
|
12398
|
+
provider: new DockerProvider({ image: "node:22-bookworm" }),
|
|
12399
|
+
cwd: process.cwd(),
|
|
12400
|
+
});
|
|
12401
|
+
|
|
12402
|
+
const result = await sandbox.run({
|
|
12403
|
+
agent: new ClaudeCodeAgent({ model: "claude-sonnet-4-6" }),
|
|
12404
|
+
promptFile: ".sandblocks/prompts/implement.md",
|
|
12405
|
+
logging: { type: "stdout" },
|
|
12406
|
+
});
|
|
12407
|
+
|
|
12408
|
+
console.log(result);
|
|
12409
|
+
`,
|
|
12410
|
+
"prompts/implement.md": `Implement the requested change, run the relevant checks, and commit the result.
|
|
12411
|
+
|
|
12412
|
+
<promise>COMPLETE</promise>
|
|
12413
|
+
`,
|
|
12414
|
+
".env.example": `# Prefer Sandblocks managed secrets for remote providers.
|
|
12415
|
+
CLAUDE_CODE_OAUTH_TOKEN=
|
|
12416
|
+
# ANTHROPIC_API_KEY=
|
|
12417
|
+
SANDBLOCKS_API_URL=
|
|
12418
|
+
SANDBLOCKS_API_KEY=
|
|
12419
|
+
`,
|
|
12420
|
+
"config.json": `${JSON.stringify({ provider: "docker", image: "node:22-bookworm", agent: "claude-code", model: "claude-sonnet-4-6", branchStrategy: "head" }, null, 2)}
|
|
12421
|
+
`
|
|
12422
|
+
};
|
|
12423
|
+
const written = [];
|
|
12424
|
+
for (const [relative, content] of Object.entries(files)) {
|
|
12425
|
+
const path3 = resolve2(root, relative);
|
|
12426
|
+
await mkdir2(resolve2(path3, ".."), { recursive: true });
|
|
12427
|
+
if (!options.force && await access(path3).then(() => true).catch(() => false))
|
|
12428
|
+
continue;
|
|
12429
|
+
await writeFile(path3, content, { mode: relative.includes(".env") ? 384 : 420 });
|
|
12430
|
+
written.push(path3);
|
|
12431
|
+
}
|
|
12432
|
+
return written;
|
|
12564
12433
|
}
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12434
|
+
|
|
12435
|
+
class SandblocksClient {
|
|
12436
|
+
options;
|
|
12437
|
+
baseUrl;
|
|
12438
|
+
fetchImpl;
|
|
12439
|
+
constructor(options) {
|
|
12440
|
+
this.options = options;
|
|
12441
|
+
if (!options.apiKey.trim())
|
|
12442
|
+
throw new Error("Sandblocks API key is required");
|
|
12443
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
12444
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
12445
|
+
}
|
|
12446
|
+
listStacks(projectId) {
|
|
12447
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/stacks`);
|
|
12448
|
+
}
|
|
12449
|
+
getStack(projectId, stack) {
|
|
12450
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/stacks/${encodeURIComponent(stack)}`);
|
|
12451
|
+
}
|
|
12452
|
+
listApps(projectId, stack) {
|
|
12453
|
+
const path3 = stack ? `/v1/projects/${encodeURIComponent(projectId)}/stacks/${encodeURIComponent(stack)}/apps` : `/v1/projects/${encodeURIComponent(projectId)}/apps`;
|
|
12454
|
+
return this.request(path3);
|
|
12455
|
+
}
|
|
12456
|
+
getApp(projectId, stack, app) {
|
|
12457
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/stacks/${encodeURIComponent(stack)}/apps/${encodeURIComponent(app)}`);
|
|
12458
|
+
}
|
|
12459
|
+
getResourceSettings(projectId, stack, app) {
|
|
12460
|
+
return this.request(this.resourcePath(projectId, stack, app, "settings"));
|
|
12461
|
+
}
|
|
12462
|
+
saveResourceSettings(input) {
|
|
12463
|
+
return this.request(this.resourcePath(input.projectId, input.stack, input.app, "settings"), {
|
|
12464
|
+
method: "PUT",
|
|
12465
|
+
body: JSON.stringify({ values: input.values, expectedRevision: input.expectedRevision })
|
|
12466
|
+
});
|
|
12467
|
+
}
|
|
12468
|
+
listDeploymentSecrets(projectId, stack, app) {
|
|
12469
|
+
return this.request(this.secretPath(projectId, stack, app));
|
|
12470
|
+
}
|
|
12471
|
+
saveDeploymentSecret(input) {
|
|
12472
|
+
return this.request(this.secretPath(input.projectId, input.stack, input.app), {
|
|
12473
|
+
method: "POST",
|
|
12474
|
+
body: JSON.stringify({ environment: input.environment, name: input.name, value: input.value })
|
|
12475
|
+
});
|
|
12476
|
+
}
|
|
12477
|
+
deleteDeploymentSecret(projectId, secretId, stack, app) {
|
|
12478
|
+
return this.request(`${this.secretPath(projectId, stack, app)}/${encodeURIComponent(secretId)}`, {
|
|
12479
|
+
method: "DELETE"
|
|
12480
|
+
});
|
|
12481
|
+
}
|
|
12482
|
+
listSandboxes(projectId) {
|
|
12483
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes`);
|
|
12484
|
+
}
|
|
12485
|
+
listAgentSessions(projectId, sandboxId) {
|
|
12486
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions`);
|
|
12487
|
+
}
|
|
12488
|
+
createAgentSession(input) {
|
|
12489
|
+
return this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/sandboxes/${encodeURIComponent(input.sandboxId)}/agent-sessions`, {
|
|
12490
|
+
method: "POST",
|
|
12491
|
+
headers: { "idempotency-key": input.idempotencyKey },
|
|
12492
|
+
body: JSON.stringify({
|
|
12493
|
+
repository: input.repository,
|
|
12494
|
+
provider: input.provider,
|
|
12495
|
+
model: input.model,
|
|
12496
|
+
prompt: input.prompt,
|
|
12497
|
+
credentialEnvironment: input.credentialEnvironment,
|
|
12498
|
+
timeoutSeconds: input.timeoutSeconds
|
|
12499
|
+
})
|
|
12500
|
+
});
|
|
12501
|
+
}
|
|
12502
|
+
getAgentSession(projectId, sandboxId, sessionId) {
|
|
12503
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions/${encodeURIComponent(sessionId)}`);
|
|
12504
|
+
}
|
|
12505
|
+
redeployAgentSession(projectId, sandboxId, sessionId, idempotencyKey) {
|
|
12506
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions/${encodeURIComponent(sessionId)}/redeploy`, { method: "POST", headers: { "idempotency-key": idempotencyKey } });
|
|
12507
|
+
}
|
|
12508
|
+
cancelAgentSession(projectId, sandboxId, sessionId) {
|
|
12509
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions/${encodeURIComponent(sessionId)}/cancel`, { method: "POST" });
|
|
12510
|
+
}
|
|
12511
|
+
listHosts(projectId) {
|
|
12512
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/hosts`);
|
|
12513
|
+
}
|
|
12514
|
+
recordSandbox(projectId, sandbox) {
|
|
12515
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes`, {
|
|
12516
|
+
method: "POST",
|
|
12517
|
+
body: JSON.stringify(sandbox)
|
|
12518
|
+
});
|
|
12519
|
+
}
|
|
12520
|
+
deployServices(input) {
|
|
12521
|
+
return this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/deployments`, {
|
|
12522
|
+
method: "POST",
|
|
12523
|
+
headers: { "idempotency-key": input.idempotencyKey },
|
|
12524
|
+
body: JSON.stringify(input)
|
|
12525
|
+
});
|
|
12526
|
+
}
|
|
12527
|
+
destroyServices(input) {
|
|
12528
|
+
return this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/deployments/${encodeURIComponent(input.deploymentId)}`, {
|
|
12529
|
+
method: "DELETE",
|
|
12530
|
+
headers: { "idempotency-key": input.idempotencyKey }
|
|
12531
|
+
});
|
|
12532
|
+
}
|
|
12533
|
+
issueSandboxLease(input) {
|
|
12534
|
+
return this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/sandboxes/${encodeURIComponent(input.sandboxId)}/leases`, {
|
|
12535
|
+
method: "POST",
|
|
12536
|
+
body: JSON.stringify({ subject: input.subject, scopes: input.scopes, ttlSeconds: input.ttlSeconds })
|
|
12537
|
+
});
|
|
12538
|
+
}
|
|
12539
|
+
listSandboxLeases(projectId, sandboxId) {
|
|
12540
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/leases`);
|
|
12541
|
+
}
|
|
12542
|
+
revokeSandboxLease(projectId, sandboxId, leaseId) {
|
|
12543
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/leases/${encodeURIComponent(leaseId)}`, { method: "DELETE" });
|
|
12544
|
+
}
|
|
12545
|
+
listRoutingAliases(projectId) {
|
|
12546
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/routing-aliases`);
|
|
12547
|
+
}
|
|
12548
|
+
listRoutingHistory(projectId) {
|
|
12549
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/routing-aliases/history`);
|
|
12550
|
+
}
|
|
12551
|
+
promoteRoutingAliases(projectId, sandboxId) {
|
|
12552
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/routing-aliases/promote`, {
|
|
12553
|
+
method: "POST",
|
|
12554
|
+
body: JSON.stringify({ sandboxId })
|
|
12555
|
+
});
|
|
12556
|
+
}
|
|
12557
|
+
getRoutingPolicy(projectId, stack, deploymentType) {
|
|
12558
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/routing-policies/${encodeURIComponent(stack)}/${encodeURIComponent(deploymentType)}`);
|
|
12559
|
+
}
|
|
12560
|
+
setRoutingPolicy(projectId, stack, deploymentType, policy) {
|
|
12561
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/routing-policies/${encodeURIComponent(stack)}/${encodeURIComponent(deploymentType)}`, {
|
|
12562
|
+
method: "PUT",
|
|
12563
|
+
body: JSON.stringify(policy)
|
|
12564
|
+
});
|
|
12565
|
+
}
|
|
12566
|
+
rollbackRoutingAliases(projectId, sandboxId) {
|
|
12567
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/routing-aliases/rollback`, {
|
|
12568
|
+
method: "POST",
|
|
12569
|
+
body: JSON.stringify({ sandboxId })
|
|
12570
|
+
});
|
|
12571
|
+
}
|
|
12572
|
+
getSandbox(projectId, sandboxId) {
|
|
12573
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}`);
|
|
12574
|
+
}
|
|
12575
|
+
createWorkspace(input) {
|
|
12576
|
+
return this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/workspaces`, {
|
|
12577
|
+
method: "POST",
|
|
12578
|
+
headers: {
|
|
12579
|
+
"idempotency-key": input.idempotencyKey,
|
|
12580
|
+
...input.gitCredential ? { "x-sandblocks-git-credential": input.gitCredential } : {}
|
|
12581
|
+
},
|
|
12582
|
+
body: JSON.stringify({
|
|
12583
|
+
workspaceId: input.workspaceId,
|
|
12584
|
+
repositoryUrl: input.repositoryUrl,
|
|
12585
|
+
revision: input.revision,
|
|
12586
|
+
placement: input.placement
|
|
12587
|
+
})
|
|
12588
|
+
});
|
|
12589
|
+
}
|
|
12590
|
+
executeWorkspace(input) {
|
|
12591
|
+
return this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/workspaces/${encodeURIComponent(input.workspaceId)}/exec`, {
|
|
12592
|
+
method: "POST",
|
|
12593
|
+
headers: { "idempotency-key": input.idempotencyKey },
|
|
12594
|
+
body: JSON.stringify({ executable: input.executable, args: input.args })
|
|
12595
|
+
});
|
|
12596
|
+
}
|
|
12597
|
+
destroyWorkspace(input) {
|
|
12598
|
+
return this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/workspaces/${encodeURIComponent(input.workspaceId)}`, { method: "DELETE", headers: { "idempotency-key": input.idempotencyKey } });
|
|
12599
|
+
}
|
|
12600
|
+
async uploadPrebuiltArtifact(input) {
|
|
12601
|
+
if (!input.bundle.byteLength)
|
|
12602
|
+
throw new Error("Prebuilt artifact bundle cannot be empty");
|
|
12603
|
+
const headers = new Headers({
|
|
12604
|
+
"x-sandblocks-api-key": this.options.apiKey,
|
|
12605
|
+
"content-type": "application/x-tar",
|
|
12606
|
+
"content-length": String(input.bundle.byteLength),
|
|
12607
|
+
"x-sandblocks-workspace-id": input.workspaceId,
|
|
12608
|
+
"x-sandblocks-worker-pool": input.pool ?? "build-fleet",
|
|
12609
|
+
"idempotency-key": `prebuilt:${input.deploymentId}:import`
|
|
12610
|
+
});
|
|
12611
|
+
if (input.workspaceImage)
|
|
12612
|
+
headers.set("x-sandblocks-workspace-image", input.workspaceImage);
|
|
12613
|
+
const response = await this.fetchImpl(`${this.baseUrl}/v1/projects/${encodeURIComponent(input.projectId)}/workspaces/import`, { method: "POST", headers, body: new Blob([Uint8Array.from(input.bundle)]) });
|
|
12614
|
+
const imported = await response.json().catch(() => ({}));
|
|
12615
|
+
if (!response.ok || !imported.operation)
|
|
12616
|
+
throw new Error(imported.error ?? `Prebuilt artifact import failed (${response.status})`);
|
|
12617
|
+
await this.waitForOperation(imported.operation.id, { timeoutMs: input.timeoutMs ?? 30 * 60000 });
|
|
12618
|
+
const published = await this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/operations`, {
|
|
12619
|
+
method: "POST",
|
|
12620
|
+
headers: { "idempotency-key": `prebuilt:${input.deploymentId}:publish` },
|
|
12621
|
+
body: JSON.stringify({
|
|
12622
|
+
kind: "workspace.artifact.publish",
|
|
12623
|
+
payload: {
|
|
12624
|
+
workspaceId: input.workspaceId,
|
|
12625
|
+
deploymentId: input.deploymentId,
|
|
12626
|
+
stack: input.stack,
|
|
12627
|
+
placement: { requiredWorkerRole: "build", pool: input.pool ?? "build-fleet" }
|
|
12628
|
+
},
|
|
12629
|
+
maxAttempts: 3
|
|
12630
|
+
})
|
|
12631
|
+
});
|
|
12632
|
+
const operation = await this.waitForOperation(published.operation.id, {
|
|
12633
|
+
timeoutMs: input.timeoutMs ?? 30 * 60000
|
|
12634
|
+
});
|
|
12635
|
+
const image = String(operation.result?.image ?? "");
|
|
12636
|
+
if (!/@sha256:[a-f0-9]{64}$/.test(image))
|
|
12637
|
+
throw new Error("Published artifact digest is invalid");
|
|
12638
|
+
return { image, workspaceId: input.workspaceId, deploymentId: input.deploymentId, bytes: input.bundle.byteLength };
|
|
12639
|
+
}
|
|
12640
|
+
async deployPrebuiltArtifact(input) {
|
|
12641
|
+
const { operation } = await this.request(`/v1/projects/${encodeURIComponent(input.projectId)}/operations`, {
|
|
12642
|
+
method: "POST",
|
|
12643
|
+
headers: { "idempotency-key": `prebuilt:${input.deploymentId}:deploy` },
|
|
12644
|
+
body: JSON.stringify({
|
|
12645
|
+
kind: "workspace.service.deploy",
|
|
12646
|
+
stack: input.stack,
|
|
12647
|
+
environmentId: input.environmentId,
|
|
12648
|
+
payload: {
|
|
12649
|
+
deploymentId: input.deploymentId,
|
|
12650
|
+
artifactImage: input.artifactImage,
|
|
12651
|
+
stack: input.stack,
|
|
12652
|
+
targetHost: input.targetHost,
|
|
12653
|
+
services: input.services,
|
|
12654
|
+
serviceUrls: input.serviceUrls ?? {},
|
|
12655
|
+
workspace: input.workspace ?? {},
|
|
12656
|
+
placement: input.placement ?? { requiredWorkerRole: "runtime" }
|
|
12657
|
+
},
|
|
12658
|
+
maxAttempts: 3
|
|
12659
|
+
})
|
|
12660
|
+
});
|
|
12661
|
+
return this.waitForOperation(operation.id, { timeoutMs: input.timeoutMs ?? 30 * 60000 });
|
|
12662
|
+
}
|
|
12663
|
+
getOperation(operationId) {
|
|
12664
|
+
return this.request(`/v1/operations/${encodeURIComponent(operationId)}`);
|
|
12665
|
+
}
|
|
12666
|
+
listOperations(projectId, sandboxId) {
|
|
12667
|
+
const query = sandboxId ? `?sandboxId=${encodeURIComponent(sandboxId)}` : "";
|
|
12668
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/operations${query}`);
|
|
12669
|
+
}
|
|
12670
|
+
listOperationEvents(operationId) {
|
|
12671
|
+
return this.request(`/v1/operations/${encodeURIComponent(operationId)}/events`);
|
|
12672
|
+
}
|
|
12673
|
+
cancelOperation(operationId) {
|
|
12674
|
+
return this.request(`/v1/operations/${encodeURIComponent(operationId)}/cancel`, { method: "POST" });
|
|
12675
|
+
}
|
|
12676
|
+
cancelSandboxOperations(projectId, sandboxId) {
|
|
12677
|
+
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/operations/cancel`, { method: "POST" });
|
|
12678
|
+
}
|
|
12679
|
+
async waitForOperation(operationId, options = {}) {
|
|
12680
|
+
const deadline = Date.now() + (options.timeoutMs ?? 120000);
|
|
12681
|
+
while (Date.now() < deadline) {
|
|
12682
|
+
const { operation } = await this.getOperation(operationId);
|
|
12683
|
+
if (operation.state === "succeeded")
|
|
12684
|
+
return operation;
|
|
12685
|
+
if (["failed", "cancelled"].includes(operation.state)) {
|
|
12686
|
+
throw new Error(operation.failureReason ?? `Sandblocks operation ${operation.state}`);
|
|
12687
|
+
}
|
|
12688
|
+
await new Promise((resolve3) => setTimeout(resolve3, options.pollMs ?? 500));
|
|
12583
12689
|
}
|
|
12584
|
-
|
|
12690
|
+
throw new Error("Sandblocks operation timed out");
|
|
12585
12691
|
}
|
|
12586
|
-
|
|
12692
|
+
resourcePath(projectId, stack, app, suffix) {
|
|
12693
|
+
return `/v1/projects/${encodeURIComponent(projectId)}/stacks/${encodeURIComponent(stack)}${app ? `/apps/${encodeURIComponent(app)}` : ""}/${suffix}`;
|
|
12694
|
+
}
|
|
12695
|
+
secretPath(projectId, stack, app) {
|
|
12696
|
+
if (stack && app)
|
|
12697
|
+
return this.resourcePath(projectId, stack, app, "deployment-secrets");
|
|
12698
|
+
if (stack)
|
|
12699
|
+
return `/v1/projects/${encodeURIComponent(projectId)}/stacks/${encodeURIComponent(stack)}/deployment-secrets`;
|
|
12700
|
+
return `/v1/projects/${encodeURIComponent(projectId)}/deployment-secrets`;
|
|
12701
|
+
}
|
|
12702
|
+
async request(path3, init2 = {}) {
|
|
12703
|
+
const headers = new Headers(init2.headers);
|
|
12704
|
+
headers.set("x-sandblocks-api-key", this.options.apiKey);
|
|
12705
|
+
if (init2.body)
|
|
12706
|
+
headers.set("content-type", "application/json");
|
|
12707
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path3}`, { ...init2, headers });
|
|
12708
|
+
const body = await response.json().catch(() => ({}));
|
|
12709
|
+
if (!response.ok)
|
|
12710
|
+
throw new Error(body.error ?? `Sandblocks request failed (${response.status})`);
|
|
12711
|
+
return body;
|
|
12712
|
+
}
|
|
12713
|
+
}
|
|
12714
|
+
|
|
12715
|
+
// packages/libs/cli/src/hooks.ts
|
|
12716
|
+
import { chmod, copyFile, mkdir as mkdir3, readFile as readFile4, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
12717
|
+
import path3 from "path";
|
|
12718
|
+
var marker = "# sandblocks-managed-post-commit";
|
|
12719
|
+
var backupName = "post-commit.before-sandblocks";
|
|
12720
|
+
async function runHooksCommand(args, dependencies) {
|
|
12721
|
+
const [command = "status", ...rest] = args;
|
|
12722
|
+
if (command === "run") {
|
|
12723
|
+
const [hook, directory] = rest;
|
|
12724
|
+
if (hook !== "post-commit")
|
|
12725
|
+
throw new Error("hooks run supports only post-commit");
|
|
12726
|
+
return runPostCommit(path3.resolve(directory ?? process.cwd()), dependencies);
|
|
12727
|
+
}
|
|
12728
|
+
const root = path3.resolve(rest.find((value) => !value.startsWith("--")) ?? process.cwd());
|
|
12729
|
+
if (command === "install")
|
|
12730
|
+
return install(root);
|
|
12731
|
+
if (command === "uninstall")
|
|
12732
|
+
return uninstall(root);
|
|
12733
|
+
if (command === "status")
|
|
12734
|
+
return status(root);
|
|
12735
|
+
throw new Error(`unsupported hooks command '${command}'`);
|
|
12736
|
+
}
|
|
12737
|
+
async function install(root) {
|
|
12738
|
+
const hooksDir = await gitPath(root, "hooks");
|
|
12739
|
+
const hookPath = path3.join(hooksDir, "post-commit");
|
|
12740
|
+
const backupPath = path3.join(hooksDir, backupName);
|
|
12741
|
+
await mkdir3(hooksDir, { recursive: true });
|
|
12742
|
+
const current = await text(hookPath);
|
|
12743
|
+
if (current && !current.includes(marker)) {
|
|
12744
|
+
await copyFile(hookPath, backupPath);
|
|
12745
|
+
await chmod(backupPath, 493);
|
|
12746
|
+
}
|
|
12747
|
+
await writeFile2(hookPath, `#!/bin/sh
|
|
12748
|
+
${marker}
|
|
12749
|
+
set -u
|
|
12750
|
+
previous="$(dirname "$0")/${backupName}"
|
|
12751
|
+
if [ -x "$previous" ]; then "$previous" || exit $?; fi
|
|
12752
|
+
root=$(git rev-parse --show-toplevel) || exit $?
|
|
12753
|
+
cd "$root" || exit $?
|
|
12754
|
+
if [ -x node_modules/.bin/sandblocks ]; then exec node_modules/.bin/sandblocks hooks run post-commit "$root"; fi
|
|
12755
|
+
exec sandblocks hooks run post-commit "$root"
|
|
12756
|
+
`);
|
|
12757
|
+
await chmod(hookPath, 493);
|
|
12758
|
+
console.log(`installed ${hookPath}`);
|
|
12759
|
+
if (await exists(backupPath))
|
|
12760
|
+
console.log(`chaining ${backupPath}`);
|
|
12761
|
+
}
|
|
12762
|
+
async function uninstall(root) {
|
|
12763
|
+
const hooksDir = await gitPath(root, "hooks");
|
|
12764
|
+
const hookPath = path3.join(hooksDir, "post-commit");
|
|
12765
|
+
const backupPath = path3.join(hooksDir, backupName);
|
|
12766
|
+
const current = await text(hookPath);
|
|
12767
|
+
if (!current?.includes(marker))
|
|
12768
|
+
throw new Error("Sandblocks post-commit hook is not installed");
|
|
12769
|
+
if (await exists(backupPath)) {
|
|
12770
|
+
await copyFile(backupPath, hookPath);
|
|
12771
|
+
await chmod(hookPath, 493);
|
|
12772
|
+
await rm2(backupPath);
|
|
12773
|
+
console.log(`restored ${hookPath}`);
|
|
12774
|
+
} else {
|
|
12775
|
+
await rm2(hookPath);
|
|
12776
|
+
console.log(`removed ${hookPath}`);
|
|
12777
|
+
}
|
|
12778
|
+
}
|
|
12779
|
+
async function status(root) {
|
|
12780
|
+
const hooksDir = await gitPath(root, "hooks");
|
|
12781
|
+
const installed = (await text(path3.join(hooksDir, "post-commit")))?.includes(marker) === true;
|
|
12782
|
+
const loaded = await loadPolicy(root);
|
|
12783
|
+
console.log(`post-commit ${installed ? "installed" : "not installed"}`);
|
|
12784
|
+
console.log(`policy ${loaded ? loaded.enabled ? "enabled" : "disabled" : "not configured"}`);
|
|
12785
|
+
if (loaded) {
|
|
12786
|
+
console.log(`branches ${loaded.branches.join(",")}`);
|
|
12787
|
+
console.log(`environment ${loaded.environment}`);
|
|
12788
|
+
console.log(`mode ${loaded.background ? "background" : "foreground"}`);
|
|
12789
|
+
}
|
|
12790
|
+
}
|
|
12791
|
+
async function runPostCommit(root, dependencies) {
|
|
12792
|
+
if (process.env.SANDBLOCKS_SKIP_POST_COMMIT === "1") {
|
|
12793
|
+
console.log("sandblocks post-commit skipped by SANDBLOCKS_SKIP_POST_COMMIT");
|
|
12794
|
+
return;
|
|
12795
|
+
}
|
|
12796
|
+
const policy = (await dependencies.load(root)).manifest.hooks.postCommit;
|
|
12797
|
+
if (!policy?.enabled)
|
|
12798
|
+
return;
|
|
12799
|
+
const branch = await git(root, ["branch", "--show-current"]);
|
|
12800
|
+
if (!policy.branches.includes(branch)) {
|
|
12801
|
+
console.log(`sandblocks post-commit skipped on branch ${branch || "(detached)"}`);
|
|
12802
|
+
return;
|
|
12803
|
+
}
|
|
12804
|
+
const stateFile = path3.join(root, ".sandblocks", `sandbox-${policy.environment}.json`);
|
|
12805
|
+
const command = await exists(stateFile) ? "redeploy" : "up";
|
|
12806
|
+
const commandArgs = [root, "--environment", policy.environment];
|
|
12807
|
+
if (policy.background) {
|
|
12808
|
+
const logDir = path3.join(root, ".sandblocks", "logs");
|
|
12809
|
+
await mkdir3(logDir, { recursive: true });
|
|
12810
|
+
const log = path3.join(logDir, `post-commit-${Date.now()}.log`);
|
|
12811
|
+
const executable = process.argv[1];
|
|
12812
|
+
if (!executable)
|
|
12813
|
+
throw new Error("unable to resolve Sandblocks CLI executable");
|
|
12814
|
+
const proc = Bun.spawn([process.execPath, executable, "sandbox", command, ...commandArgs], {
|
|
12815
|
+
cwd: root,
|
|
12816
|
+
stdout: Bun.file(log),
|
|
12817
|
+
stderr: Bun.file(log),
|
|
12818
|
+
env: { ...process.env, SANDBLOCKS_SKIP_POST_COMMIT: "1" }
|
|
12819
|
+
});
|
|
12820
|
+
proc.unref();
|
|
12821
|
+
console.log(`sandblocks ${command} started for ${policy.environment} (${log})`);
|
|
12822
|
+
return;
|
|
12823
|
+
}
|
|
12824
|
+
await dependencies.runSandbox(command, commandArgs);
|
|
12825
|
+
}
|
|
12826
|
+
async function loadPolicy(root) {
|
|
12827
|
+
const { loadSandblocksManifest: loadSandblocksManifest2 } = await Promise.resolve().then(() => (init_src(), exports_src));
|
|
12828
|
+
return (await loadSandblocksManifest2({ repositoryRoot: root })).manifest.hooks.postCommit;
|
|
12829
|
+
}
|
|
12830
|
+
async function gitPath(root, name) {
|
|
12831
|
+
return path3.resolve(root, await git(root, ["rev-parse", "--git-path", name]));
|
|
12587
12832
|
}
|
|
12588
|
-
async function
|
|
12589
|
-
const
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12833
|
+
async function git(root, args) {
|
|
12834
|
+
const proc = Bun.spawn(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
|
|
12835
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
12836
|
+
new Response(proc.stdout).text(),
|
|
12837
|
+
new Response(proc.stderr).text(),
|
|
12838
|
+
proc.exited
|
|
12839
|
+
]);
|
|
12840
|
+
if (code !== 0)
|
|
12841
|
+
throw new Error(stderr.trim() || `git ${args.join(" ")} failed`);
|
|
12842
|
+
return stdout.trim();
|
|
12843
|
+
}
|
|
12844
|
+
async function exists(file) {
|
|
12845
|
+
return Bun.file(file).exists();
|
|
12846
|
+
}
|
|
12847
|
+
async function text(file) {
|
|
12848
|
+
try {
|
|
12849
|
+
return await readFile4(file, "utf8");
|
|
12850
|
+
} catch {
|
|
12851
|
+
return;
|
|
12597
12852
|
}
|
|
12598
|
-
throw new Error(`operation ${operationId} timed out`);
|
|
12599
12853
|
}
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12854
|
+
|
|
12855
|
+
// packages/libs/cli/src/runtime.ts
|
|
12856
|
+
import { spawn as spawn2 } from "child_process";
|
|
12857
|
+
import { access as access2, chmod as chmod2, mkdir as mkdir5, readdir, rm as rm3 } from "fs/promises";
|
|
12858
|
+
import path4 from "path";
|
|
12859
|
+
var RUNTIME_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;
|
|
12860
|
+
var NODE_ENV_RUNTIME = {
|
|
12861
|
+
production: "prod",
|
|
12862
|
+
development: "dev",
|
|
12863
|
+
test: "local"
|
|
12864
|
+
};
|
|
12865
|
+
async function runRuntimeCommand(args) {
|
|
12866
|
+
const [action, ...rest] = args;
|
|
12867
|
+
if (action !== "prepare")
|
|
12868
|
+
throw new Error("runtime action must be prepare");
|
|
12869
|
+
const options = parse(rest);
|
|
12870
|
+
const root = path4.resolve(options.positionals[0] ?? process.cwd());
|
|
12871
|
+
const runtime = normalizedRuntime(option(options, "runtime") ?? process.env.SANDBLOCKS_RUNTIME ?? "development" ?? "development");
|
|
12872
|
+
const targetRoot = contained(root, option(options, "target") ?? ".locker", "runtime target");
|
|
12873
|
+
const runtimeRoot = path4.join(targetRoot, runtime);
|
|
12874
|
+
const config = contained(root, option(options, "config") ?? "config/dotlocker.config.ts", "Dotlocker config");
|
|
12875
|
+
const dotlocker = path4.resolve(root, option(options, "dotlocker") ?? path4.join("node_modules", ".bin", "dotlocker"));
|
|
12876
|
+
if (!await exists2(config))
|
|
12877
|
+
throw new Error(`Dotlocker config does not exist: ${config}`);
|
|
12878
|
+
if (!await exists2(dotlocker)) {
|
|
12879
|
+
throw new Error("Dotlocker is not installed; add @dotlocker/dotlocker to the workspace");
|
|
12609
12880
|
}
|
|
12610
|
-
|
|
12881
|
+
await rm3(targetRoot, { recursive: true, force: true });
|
|
12882
|
+
await mkdir5(runtimeRoot, { recursive: true, mode: 493 });
|
|
12883
|
+
await run2(dotlocker, ["pull", "--config", config, "--runtime", runtime, "--out", runtimeRoot], root);
|
|
12884
|
+
const envFile = path4.join(runtimeRoot, ".env");
|
|
12885
|
+
if (!await exists2(envFile)) {
|
|
12886
|
+
throw new Error(`Dotlocker did not materialize ${path4.relative(root, envFile)}`);
|
|
12887
|
+
}
|
|
12888
|
+
await normalizePermissions(targetRoot);
|
|
12889
|
+
console.log(`runtime ${runtime}
|
|
12890
|
+
environment ${path4.relative(root, envFile)}`);
|
|
12611
12891
|
}
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12892
|
+
function run2(executable, args, cwd) {
|
|
12893
|
+
return new Promise((resolve3, reject) => {
|
|
12894
|
+
const child = spawn2(executable, args, { cwd, env: process.env, stdio: "inherit" });
|
|
12895
|
+
child.once("error", reject);
|
|
12896
|
+
child.once("exit", (code, signal) => {
|
|
12897
|
+
if (code === 0)
|
|
12898
|
+
resolve3();
|
|
12899
|
+
else
|
|
12900
|
+
reject(new Error(`Dotlocker pull failed (${signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`})`));
|
|
12901
|
+
});
|
|
12902
|
+
});
|
|
12903
|
+
}
|
|
12904
|
+
async function exists2(file) {
|
|
12615
12905
|
try {
|
|
12616
|
-
|
|
12906
|
+
await access2(file);
|
|
12907
|
+
return true;
|
|
12617
12908
|
} catch {
|
|
12618
|
-
return;
|
|
12909
|
+
return false;
|
|
12619
12910
|
}
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12911
|
+
}
|
|
12912
|
+
function normalizedRuntime(value) {
|
|
12913
|
+
const runtime = NODE_ENV_RUNTIME[value.trim()] ?? value.trim();
|
|
12914
|
+
if (!RUNTIME_PATTERN.test(runtime))
|
|
12915
|
+
throw new Error(`invalid runtime '${value}'`);
|
|
12916
|
+
return runtime;
|
|
12917
|
+
}
|
|
12918
|
+
function contained(root, value, label) {
|
|
12919
|
+
const resolved = path4.resolve(root, value);
|
|
12920
|
+
const relative = path4.relative(root, resolved);
|
|
12921
|
+
if (!relative || !relative.startsWith("..") && !path4.isAbsolute(relative))
|
|
12922
|
+
return resolved;
|
|
12923
|
+
throw new Error(`${label} must remain within the repository`);
|
|
12924
|
+
}
|
|
12925
|
+
async function normalizePermissions(directory) {
|
|
12926
|
+
await chmod2(directory, 493);
|
|
12927
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
12928
|
+
const child = path4.join(directory, entry.name);
|
|
12929
|
+
if (entry.isSymbolicLink())
|
|
12930
|
+
throw new Error("Dotlocker output must not contain symbolic links");
|
|
12931
|
+
if (entry.isDirectory())
|
|
12932
|
+
await normalizePermissions(child);
|
|
12933
|
+
else if (entry.isFile())
|
|
12934
|
+
await chmod2(child, 420);
|
|
12935
|
+
else
|
|
12936
|
+
throw new Error("Dotlocker output contains an unsupported file type");
|
|
12937
|
+
}
|
|
12938
|
+
}
|
|
12939
|
+
function parse(args) {
|
|
12940
|
+
const options = { positionals: [] };
|
|
12941
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
12942
|
+
const value = args[index] ?? "";
|
|
12943
|
+
if (!value.startsWith("--")) {
|
|
12944
|
+
options.positionals.push(value);
|
|
12626
12945
|
continue;
|
|
12627
|
-
|
|
12946
|
+
}
|
|
12947
|
+
const key = value.slice(2);
|
|
12948
|
+
const next = args[index + 1];
|
|
12949
|
+
if (next && !next.startsWith("--")) {
|
|
12950
|
+
options[key] = next;
|
|
12951
|
+
index += 1;
|
|
12952
|
+
} else
|
|
12953
|
+
options[key] = true;
|
|
12954
|
+
}
|
|
12955
|
+
return options;
|
|
12956
|
+
}
|
|
12957
|
+
function option(options, key) {
|
|
12958
|
+
const value = options[key];
|
|
12959
|
+
return typeof value === "string" ? value : undefined;
|
|
12960
|
+
}
|
|
12961
|
+
|
|
12962
|
+
// packages/libs/cli/src/sandbox.ts
|
|
12963
|
+
init_src();
|
|
12964
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
12965
|
+
import { chmod as chmod3, mkdir as mkdir6, readFile as readFile5, rename, rm as rm4 } from "fs/promises";
|
|
12966
|
+
import path5 from "path";
|
|
12967
|
+
var COMMANDS = new Set([
|
|
12968
|
+
"up",
|
|
12969
|
+
"create",
|
|
12970
|
+
"status",
|
|
12971
|
+
"deploy",
|
|
12972
|
+
"redeploy",
|
|
12973
|
+
"renew",
|
|
12974
|
+
"exec",
|
|
12975
|
+
"git",
|
|
12976
|
+
"down",
|
|
12977
|
+
"destroy"
|
|
12978
|
+
]);
|
|
12979
|
+
function isStatefulSandboxCommand(command) {
|
|
12980
|
+
return Boolean(command && COMMANDS.has(command));
|
|
12981
|
+
}
|
|
12982
|
+
async function runStatefulSandboxCommand(command, args, dependencies) {
|
|
12983
|
+
if (command === "up")
|
|
12984
|
+
return up(args, dependencies);
|
|
12985
|
+
if (command === "create")
|
|
12986
|
+
return create(args, dependencies);
|
|
12987
|
+
if (command === "status")
|
|
12988
|
+
return status2(args, dependencies);
|
|
12989
|
+
if (command === "deploy")
|
|
12990
|
+
return deploy(args, dependencies);
|
|
12991
|
+
if (command === "redeploy")
|
|
12992
|
+
return redeploy(args, dependencies);
|
|
12993
|
+
if (command === "renew")
|
|
12994
|
+
return renew(args, dependencies);
|
|
12995
|
+
if (command === "exec")
|
|
12996
|
+
return exec(args, dependencies);
|
|
12997
|
+
if (command === "git")
|
|
12998
|
+
return git2(args, dependencies);
|
|
12999
|
+
return down(args, dependencies);
|
|
13000
|
+
}
|
|
13001
|
+
async function up(args, dependencies) {
|
|
13002
|
+
await withDeploymentLock(args, dependencies, async () => {
|
|
13003
|
+
await create(args, dependencies);
|
|
13004
|
+
try {
|
|
13005
|
+
await deployUnlocked(args, dependencies);
|
|
13006
|
+
} catch (error) {
|
|
13007
|
+
if (process.env.SANDBLOCKS_RETAIN_FAILED !== "true") {
|
|
13008
|
+
await down(args, dependencies).catch(() => {
|
|
13009
|
+
return;
|
|
13010
|
+
});
|
|
13011
|
+
}
|
|
13012
|
+
throw error;
|
|
13013
|
+
}
|
|
13014
|
+
});
|
|
13015
|
+
}
|
|
13016
|
+
async function create(args, dependencies) {
|
|
13017
|
+
const options = dependencies.parse(args);
|
|
13018
|
+
const root = path5.resolve(options.positionals[0] ?? process.cwd());
|
|
13019
|
+
await loadLocalEnvironment(root);
|
|
13020
|
+
const environment = dependencies.option(options, "environment") ?? "preview";
|
|
13021
|
+
const file = statePath(root, environment);
|
|
13022
|
+
if (await Bun.file(file).exists())
|
|
13023
|
+
throw new Error(`sandbox '${environment}' already exists`);
|
|
13024
|
+
const loaded = await dependencies.load(root, dependencies.option(options, "config") ?? dependencies.option(options, "manifest"));
|
|
13025
|
+
const preview = selectPreview(loaded, environment);
|
|
13026
|
+
const projectId = dependencies.option(options, "project") ?? process.env.SANDBLOCKS_PROJECT_ID ?? "default";
|
|
13027
|
+
const repository = loaded.manifest.name ?? path5.basename(root).toLowerCase();
|
|
13028
|
+
const sandboxId = randomUUID2();
|
|
13029
|
+
const workspaceId = `${sandboxId.replaceAll("-", "").slice(0, 8)}-${repository}`.slice(0, 63);
|
|
13030
|
+
const { apiUrl, apiKey } = client(options, dependencies.option, projectId);
|
|
13031
|
+
await request(apiUrl, apiKey, `/v1/projects/${encodeURIComponent(projectId)}/sandboxes`, {
|
|
13032
|
+
method: "POST",
|
|
13033
|
+
body: JSON.stringify({ id: sandboxId, goal: preview.goal, status: "active", repositories: [repository] })
|
|
13034
|
+
});
|
|
13035
|
+
try {
|
|
13036
|
+
const lease = await request(apiUrl, apiKey, `/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${sandboxId}/leases`, {
|
|
13037
|
+
method: "POST",
|
|
13038
|
+
body: JSON.stringify({
|
|
13039
|
+
subject: `sandblocks-cli:${process.env.USER ?? "developer"}`,
|
|
13040
|
+
scopes: ["sandbox:read", "workspace:read", "workspace:write", "workspace:exec", "workspace:git"],
|
|
13041
|
+
ttlSeconds: Number(dependencies.option(options, "ttl-seconds") ?? 28800)
|
|
13042
|
+
})
|
|
13043
|
+
});
|
|
13044
|
+
const imported = await importSource({
|
|
13045
|
+
root,
|
|
13046
|
+
projectId,
|
|
13047
|
+
sandboxId,
|
|
13048
|
+
workspaceId,
|
|
13049
|
+
pool: dependencies.option(options, "pool") ?? process.env.SANDBLOCKS_WORKER_POOL ?? "build-fleet",
|
|
13050
|
+
apiUrl,
|
|
13051
|
+
apiKey,
|
|
13052
|
+
idempotencyKey: `${sandboxId}:workspace-import`,
|
|
13053
|
+
source: dependencies.source,
|
|
13054
|
+
..."image" in loaded.manifest.workspace && loaded.manifest.workspace.image ? { workspaceImage: loaded.manifest.workspace.image } : {}
|
|
13055
|
+
});
|
|
13056
|
+
const hostId = String(imported.operation.assignedHostId ?? "");
|
|
13057
|
+
if (!hostId)
|
|
13058
|
+
throw new Error("workspace import did not bind a sandbox host");
|
|
13059
|
+
const state = {
|
|
13060
|
+
version: 1,
|
|
13061
|
+
environment,
|
|
13062
|
+
projectId,
|
|
13063
|
+
sandboxId,
|
|
13064
|
+
workspaceId,
|
|
13065
|
+
repository,
|
|
13066
|
+
leaseId: lease.lease.id,
|
|
13067
|
+
leaseToken: lease.token,
|
|
13068
|
+
hostId,
|
|
13069
|
+
sequence: 0,
|
|
13070
|
+
createdAt: new Date().toISOString()
|
|
13071
|
+
};
|
|
13072
|
+
await writeState(file, state);
|
|
13073
|
+
console.log(`sandbox ${sandboxId}`);
|
|
13074
|
+
console.log(`workspace ${workspaceId}`);
|
|
13075
|
+
console.log(`host ${hostId}`);
|
|
13076
|
+
} catch (error) {
|
|
13077
|
+
await request(apiUrl, apiKey, `/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${sandboxId}`, {
|
|
13078
|
+
method: "DELETE"
|
|
13079
|
+
}).catch(() => {
|
|
13080
|
+
return;
|
|
13081
|
+
});
|
|
13082
|
+
throw error;
|
|
12628
13083
|
}
|
|
12629
13084
|
}
|
|
12630
|
-
async function
|
|
12631
|
-
const
|
|
12632
|
-
const
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
};
|
|
12643
|
-
}
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
return [];
|
|
12648
|
-
const name = service.id.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
12649
|
-
const host = resolvedServiceDomain(sandboxId, deploymentId, service.id, preview);
|
|
12650
|
-
const configuredStableHosts = preview.stableDomains[service.id];
|
|
12651
|
-
const stableHost = (Array.isArray(configuredStableHosts) ? configuredStableHosts[0] : configuredStableHosts) ?? `${preview.deploymentType}-${stack}-${service.id}.${preview.baseDomain}`;
|
|
12652
|
-
return [
|
|
12653
|
-
[`SANDBLOCKS_SERVICE_${name}_URL`, `https://${host}`],
|
|
12654
|
-
[`SANDBLOCKS_STABLE_SERVICE_${name}_URL`, `https://${stableHost}`]
|
|
12655
|
-
];
|
|
12656
|
-
}));
|
|
12657
|
-
}
|
|
12658
|
-
async function managedServiceDomains(context, stack, preview, deploymentId) {
|
|
12659
|
-
const body = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/routing-policies/${encodeURIComponent(stack)}/${encodeURIComponent(preview.deploymentType)}`);
|
|
12660
|
-
const domains = body.policy.domains ?? {};
|
|
12661
|
-
return Object.keys(domains).length ? domains : resolvedServiceDomains(context.state.sandboxId, deploymentId, preview);
|
|
12662
|
-
}
|
|
12663
|
-
function resolvedServiceDomains(sandboxId, deploymentId, preview) {
|
|
12664
|
-
return Object.fromEntries(preview.services.map((service) => [
|
|
12665
|
-
service.id,
|
|
12666
|
-
resolvedServiceDomain(sandboxId, deploymentId, service.id, preview)
|
|
12667
|
-
]));
|
|
12668
|
-
}
|
|
12669
|
-
function resolvedServiceDomain(sandboxId, deploymentId, service, preview) {
|
|
12670
|
-
const routeId = `${sandboxId.replaceAll("-", "").slice(0, 8)}-${createHash2("sha256").update(`${sandboxId}/${deploymentId}/${service}`).digest("hex").slice(0, 8)}`;
|
|
12671
|
-
const template = preview.domains[service] ?? `{route}.${preview.baseDomain}`;
|
|
12672
|
-
return template.replaceAll("{route}", routeId).replaceAll("{sandbox}", sandboxId.replaceAll("-", "").slice(0, 8)).replaceAll("{revision}", deploymentId.replaceAll("-", "").slice(0, 8)).replaceAll("{service}", service).toLowerCase();
|
|
12673
|
-
}
|
|
12674
|
-
function statePath(root, environment) {
|
|
12675
|
-
if (!/^[a-z][a-z0-9-]{0,62}$/.test(environment))
|
|
12676
|
-
throw new Error("sandbox environment is invalid");
|
|
12677
|
-
return path5.join(root, ".sandblocks", `sandbox-${environment}.json`);
|
|
12678
|
-
}
|
|
12679
|
-
async function writeState(file, state) {
|
|
12680
|
-
await mkdir3(path5.dirname(file), { recursive: true, mode: 448 });
|
|
12681
|
-
await chmod3(path5.dirname(file), 448);
|
|
12682
|
-
const temporary = `${file}.${process.pid}.tmp`;
|
|
12683
|
-
await Bun.write(temporary, `${JSON.stringify(state, null, 2)}
|
|
12684
|
-
`, { mode: 384 });
|
|
12685
|
-
await chmod3(temporary, 384);
|
|
12686
|
-
await rename(temporary, file);
|
|
13085
|
+
async function status2(args, dependencies) {
|
|
13086
|
+
const context = await getContext(args, dependencies);
|
|
13087
|
+
const [sandbox, session] = await Promise.all([
|
|
13088
|
+
request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/sandboxes/${context.state.sandboxId}`),
|
|
13089
|
+
leaseRequest(context, "/v1/sandbox-session")
|
|
13090
|
+
]);
|
|
13091
|
+
if (context.options.json) {
|
|
13092
|
+
console.log(JSON.stringify({ state: redactedState(context.state), sandbox: sandbox.sandbox, session: session.session }, null, 2));
|
|
13093
|
+
return;
|
|
13094
|
+
}
|
|
13095
|
+
console.log(`state ${path5.relative(context.root, context.file)}`);
|
|
13096
|
+
console.log(`sandbox ${context.state.sandboxId} (${sandbox.sandbox.status})`);
|
|
13097
|
+
console.log(`workspace ${context.state.workspaceId}`);
|
|
13098
|
+
console.log(`host ${context.state.hostId}`);
|
|
13099
|
+
console.log(`lease expires ${session.session.expiresAt}`);
|
|
13100
|
+
for (const route of context.state.previewUrls ?? [])
|
|
13101
|
+
console.log(`${route.service ?? "service"} ${route.url}`);
|
|
12687
13102
|
}
|
|
12688
|
-
function
|
|
12689
|
-
|
|
13103
|
+
async function deploy(args, dependencies) {
|
|
13104
|
+
await withDeploymentLock(args, dependencies, () => deployUnlocked(args, dependencies));
|
|
12690
13105
|
}
|
|
12691
|
-
function
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
13106
|
+
async function withDeploymentLock(args, dependencies, action) {
|
|
13107
|
+
const options = dependencies.parse(args);
|
|
13108
|
+
const root = path5.resolve(options.positionals[0] ?? process.cwd());
|
|
13109
|
+
const environment = dependencies.option(options, "environment") ?? "preview";
|
|
13110
|
+
const lock = `${statePath(root, environment)}.deploy.lock`;
|
|
13111
|
+
await mkdir6(path5.dirname(lock), { recursive: true, mode: 448 });
|
|
13112
|
+
try {
|
|
13113
|
+
await mkdir6(lock);
|
|
13114
|
+
} catch (error) {
|
|
13115
|
+
if (error.code === "EEXIST") {
|
|
13116
|
+
throw new Error(`sandbox '${environment}' deployment is already running`);
|
|
13117
|
+
}
|
|
13118
|
+
throw error;
|
|
13119
|
+
}
|
|
13120
|
+
try {
|
|
13121
|
+
await action();
|
|
13122
|
+
} finally {
|
|
13123
|
+
await rm4(lock, { recursive: true, force: true });
|
|
13124
|
+
}
|
|
12698
13125
|
}
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
options;
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
13126
|
+
async function deployUnlocked(args, dependencies) {
|
|
13127
|
+
const context = await getContext(args, dependencies);
|
|
13128
|
+
if (context.state.deploymentId)
|
|
13129
|
+
throw new Error("preview is already deployed; use sandbox redeploy");
|
|
13130
|
+
const loaded = await dependencies.load(context.root, dependencies.option(context.options, "config") ?? dependencies.option(context.options, "manifest"));
|
|
13131
|
+
const stack = loaded.manifest.name ?? context.state.repository;
|
|
13132
|
+
const preview = await applyManagedRoutingPolicy(context, stack, selectPreview(loaded, context.state.environment));
|
|
13133
|
+
const deploymentTarget = await resolveDeploymentTarget(context, process.env.SANDBLOCKS_SANDBOX_TARGET_HOST ?? preview.targetHost);
|
|
13134
|
+
const targetHost = deploymentTarget.targetHost;
|
|
13135
|
+
context.state.deploymentHostId = deploymentTarget.hostId;
|
|
13136
|
+
const resume = context.options.resume === true;
|
|
13137
|
+
const previousCandidate = resume ? context.state.candidate : undefined;
|
|
13138
|
+
if (resume && !previousCandidate)
|
|
13139
|
+
throw new Error("no resumable candidate exists for this sandbox");
|
|
13140
|
+
const requestedDeploymentId = dependencies.option(context.options, "deployment-id");
|
|
13141
|
+
const deploymentId = previousCandidate?.deploymentId ?? requestedDeploymentId ?? randomUUID2();
|
|
13142
|
+
if (!/^[a-f0-9-]{36}$/.test(deploymentId))
|
|
13143
|
+
throw new Error("deployment ID must be a UUID");
|
|
13144
|
+
if (previousCandidate && requestedDeploymentId && requestedDeploymentId !== previousCandidate.deploymentId)
|
|
13145
|
+
throw new Error("--deployment-id does not match the resumable candidate");
|
|
13146
|
+
const environmentSnapshotId = previousCandidate?.environmentSnapshotId ?? await createEnvironmentSnapshot(context);
|
|
13147
|
+
context.state.candidate = previousCandidate ?? { deploymentId, environmentSnapshotId };
|
|
13148
|
+
await writeState(context.file, context.state);
|
|
13149
|
+
const serviceUrls = expectedPreviewServiceUrls(context.state.sandboxId, deploymentId, context.state.repository, preview);
|
|
13150
|
+
const serviceSteps = resolveServiceSteps(loaded.manifest, context.state.environment);
|
|
13151
|
+
let buildWorkspaceId = previousCandidate?.buildWorkspaceId;
|
|
13152
|
+
let buildHostId = previousCandidate?.buildHostId;
|
|
13153
|
+
const suppliedArtifact = dependencies.option(context.options, "artifact");
|
|
13154
|
+
if (suppliedArtifact && !/@sha256:[a-f0-9]{64}$/.test(suppliedArtifact))
|
|
13155
|
+
throw new Error("--artifact must be an immutable OCI image digest");
|
|
13156
|
+
let artifactImage = suppliedArtifact ?? previousCandidate?.artifactImage;
|
|
13157
|
+
if (suppliedArtifact) {
|
|
13158
|
+
context.state.candidate = { deploymentId, environmentSnapshotId, artifactImage: suppliedArtifact };
|
|
13159
|
+
await writeState(context.file, context.state);
|
|
12718
13160
|
}
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
const base = typeof this.options.command === "function" ? this.options.command(context) : [...this.options.command];
|
|
12722
|
-
const promptMode = this.options.prompt ?? "stdin";
|
|
12723
|
-
const command = promptMode === "argument" ? [...base, context.prompt] : base;
|
|
12724
|
-
const idle = new AbortController;
|
|
12725
|
-
const completed = new AbortController;
|
|
12726
|
-
let idleTimer;
|
|
12727
|
-
let completionTimer;
|
|
12728
|
-
const resetIdle = () => {
|
|
12729
|
-
if (idleTimer)
|
|
12730
|
-
clearTimeout(idleTimer);
|
|
12731
|
-
idleTimer = setTimeout(() => idle.abort(new Error(`agent idle timeout after ${context.idleTimeoutSeconds}s`)), context.idleTimeoutSeconds * 1000);
|
|
12732
|
-
};
|
|
12733
|
-
resetIdle();
|
|
12734
|
-
let output = "";
|
|
12735
|
-
let stdout = "";
|
|
12736
|
-
let stderr = "";
|
|
12737
|
-
let lineBuffer = "";
|
|
12738
|
-
let completionSignal;
|
|
12739
|
-
const consume = async (chunk, stream) => {
|
|
12740
|
-
resetIdle();
|
|
12741
|
-
output += chunk;
|
|
12742
|
-
if (stream === "stdout")
|
|
12743
|
-
stdout += chunk;
|
|
12744
|
-
else
|
|
12745
|
-
stderr += chunk;
|
|
12746
|
-
lineBuffer += chunk;
|
|
12747
|
-
const lines = lineBuffer.split(/\r?\n/);
|
|
12748
|
-
lineBuffer = lines.pop() ?? "";
|
|
12749
|
-
for (const line of lines)
|
|
12750
|
-
await this.emitLine(line, stream, context);
|
|
12751
|
-
completionSignal ??= context.completionSignal.find((signal) => output.includes(signal));
|
|
12752
|
-
if (completionSignal && !completionTimer) {
|
|
12753
|
-
completionTimer = setTimeout(() => completed.abort(new Error("agent did not exit after its completion signal")), context.completionTimeoutSeconds * 1000);
|
|
12754
|
-
}
|
|
12755
|
-
};
|
|
13161
|
+
if (!artifactImage && serviceSteps.length) {
|
|
13162
|
+
buildWorkspaceId ??= `${context.state.sandboxId.slice(0, 8)}-${context.state.repository}-build-${deploymentId.slice(0, 8)}`.slice(0, 63);
|
|
12756
13163
|
try {
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
13164
|
+
if (!previousCandidate?.buildCompleted) {
|
|
13165
|
+
const imported = await importSource({
|
|
13166
|
+
root: context.root,
|
|
13167
|
+
projectId: context.state.projectId,
|
|
13168
|
+
workspaceId: buildWorkspaceId,
|
|
13169
|
+
pool: dependencies.option(context.options, "build-pool") ?? process.env.SANDBLOCKS_BUILD_WORKER_POOL ?? "build-fleet",
|
|
13170
|
+
apiUrl: context.apiUrl,
|
|
13171
|
+
apiKey: context.apiKey,
|
|
13172
|
+
idempotencyKey: `preview:${deploymentId}:build-import`,
|
|
13173
|
+
source: dependencies.source,
|
|
13174
|
+
..."image" in loaded.manifest.workspace && loaded.manifest.workspace.image ? { workspaceImage: loaded.manifest.workspace.image } : {}
|
|
12765
13175
|
});
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
13176
|
+
buildHostId = String(imported.operation.assignedHostId ?? "");
|
|
13177
|
+
if (!buildHostId)
|
|
13178
|
+
throw new Error("build workspace did not bind a build host");
|
|
13179
|
+
context.state.candidate = { deploymentId, environmentSnapshotId, buildWorkspaceId, buildHostId };
|
|
13180
|
+
await writeState(context.file, context.state);
|
|
13181
|
+
const stepOperation = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/operations`, {
|
|
13182
|
+
method: "POST",
|
|
13183
|
+
headers: { "idempotency-key": `preview:${deploymentId}:steps` },
|
|
13184
|
+
body: JSON.stringify({
|
|
13185
|
+
kind: "workspace.step.run",
|
|
13186
|
+
stack: context.state.repository,
|
|
13187
|
+
environmentId: context.state.environment,
|
|
13188
|
+
environmentSnapshotId,
|
|
13189
|
+
payload: {
|
|
13190
|
+
workspaceId: buildWorkspaceId,
|
|
13191
|
+
sandboxId: context.state.sandboxId,
|
|
13192
|
+
revisionId: deploymentId,
|
|
13193
|
+
runtimeType: preview.deploymentType,
|
|
13194
|
+
serviceUrls,
|
|
13195
|
+
steps: serviceSteps,
|
|
13196
|
+
..."image" in loaded.manifest.workspace && loaded.manifest.workspace.image ? { workspaceImage: loaded.manifest.workspace.image } : {},
|
|
13197
|
+
placement: { hostId: buildHostId, memoryReservationBytes: 2 * 1024 * 1024 * 1024 }
|
|
13198
|
+
},
|
|
13199
|
+
maxAttempts: 1
|
|
13200
|
+
})
|
|
13201
|
+
});
|
|
13202
|
+
await wait(context.apiUrl, context.apiKey, stepOperation.operation.id, 60 * 60000);
|
|
13203
|
+
context.state.candidate = {
|
|
13204
|
+
deploymentId,
|
|
13205
|
+
environmentSnapshotId,
|
|
13206
|
+
buildWorkspaceId,
|
|
13207
|
+
buildHostId,
|
|
13208
|
+
buildCompleted: true
|
|
13209
|
+
};
|
|
13210
|
+
await writeState(context.file, context.state);
|
|
12773
13211
|
}
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
12780
|
-
|
|
13212
|
+
if (!buildHostId)
|
|
13213
|
+
throw new Error("resumable build host is unavailable");
|
|
13214
|
+
const publish = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/operations`, {
|
|
13215
|
+
method: "POST",
|
|
13216
|
+
headers: { "idempotency-key": `preview:${deploymentId}:artifact` },
|
|
13217
|
+
body: JSON.stringify({
|
|
13218
|
+
kind: "workspace.artifact.publish",
|
|
13219
|
+
payload: {
|
|
13220
|
+
workspaceId: buildWorkspaceId,
|
|
13221
|
+
deploymentId,
|
|
13222
|
+
stack,
|
|
13223
|
+
placement: { hostId: buildHostId, memoryReservationBytes: 1024 * 1024 * 1024 }
|
|
13224
|
+
},
|
|
13225
|
+
maxAttempts: 1
|
|
13226
|
+
})
|
|
13227
|
+
});
|
|
13228
|
+
const published = await wait(context.apiUrl, context.apiKey, publish.operation.id, 60 * 60000);
|
|
13229
|
+
artifactImage = String(published.result?.image ?? "");
|
|
13230
|
+
if (!/@sha256:[a-f0-9]{64}$/.test(artifactImage))
|
|
13231
|
+
throw new Error("build artifact digest is invalid");
|
|
13232
|
+
context.state.candidate = {
|
|
13233
|
+
deploymentId,
|
|
13234
|
+
environmentSnapshotId,
|
|
13235
|
+
buildWorkspaceId,
|
|
13236
|
+
buildHostId,
|
|
13237
|
+
buildCompleted: true,
|
|
13238
|
+
artifactImage
|
|
13239
|
+
};
|
|
13240
|
+
await writeState(context.file, context.state);
|
|
13241
|
+
} catch (error) {
|
|
13242
|
+
if (!context.state.candidate?.buildCompleted && buildWorkspaceId)
|
|
13243
|
+
await destroyDetachedWorkspace(context, buildWorkspaceId, `preview:${deploymentId}:build-cleanup`).catch(() => {
|
|
13244
|
+
return;
|
|
13245
|
+
});
|
|
13246
|
+
if (!context.state.candidate?.buildCompleted) {
|
|
13247
|
+
context.state.candidate = undefined;
|
|
13248
|
+
await releaseEnvironmentSnapshot(context, environmentSnapshotId).catch(() => {
|
|
13249
|
+
return;
|
|
13250
|
+
});
|
|
13251
|
+
await writeState(context.file, context.state);
|
|
13252
|
+
}
|
|
13253
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13254
|
+
throw new Error(`${message}${context.state.candidate?.buildCompleted ? "; retry with `sandblocks sandbox deploy . --resume`" : ""}`);
|
|
12781
13255
|
}
|
|
12782
13256
|
}
|
|
12783
|
-
|
|
12784
|
-
const
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
13257
|
+
if (!artifactImage) {
|
|
13258
|
+
const publish = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/operations`, {
|
|
13259
|
+
method: "POST",
|
|
13260
|
+
headers: { "idempotency-key": `preview:${deploymentId}:artifact` },
|
|
13261
|
+
body: JSON.stringify({
|
|
13262
|
+
kind: "workspace.artifact.publish",
|
|
13263
|
+
payload: {
|
|
13264
|
+
workspaceId: context.state.workspaceId,
|
|
13265
|
+
deploymentId,
|
|
13266
|
+
stack,
|
|
13267
|
+
placement: {
|
|
13268
|
+
hostId: context.state.hostId,
|
|
13269
|
+
memoryReservationBytes: 1024 * 1024 * 1024
|
|
13270
|
+
}
|
|
13271
|
+
},
|
|
13272
|
+
maxAttempts: 1
|
|
13273
|
+
})
|
|
13274
|
+
});
|
|
13275
|
+
const published = await wait(context.apiUrl, context.apiKey, publish.operation.id, 60 * 60000);
|
|
13276
|
+
artifactImage = String(published.result?.image ?? "");
|
|
13277
|
+
if (!/@sha256:[a-f0-9]{64}$/.test(artifactImage))
|
|
13278
|
+
throw new Error("build artifact digest is invalid");
|
|
13279
|
+
}
|
|
13280
|
+
let operation;
|
|
13281
|
+
try {
|
|
13282
|
+
const submitted = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/operations`, {
|
|
13283
|
+
method: "POST",
|
|
13284
|
+
headers: {
|
|
13285
|
+
"idempotency-key": `preview:${deploymentId}:deploy`
|
|
13286
|
+
},
|
|
13287
|
+
body: JSON.stringify({
|
|
13288
|
+
kind: "workspace.service.deploy",
|
|
13289
|
+
stack: context.state.repository,
|
|
13290
|
+
environmentId: context.state.environment,
|
|
13291
|
+
environmentSnapshotId,
|
|
13292
|
+
payload: {
|
|
13293
|
+
workspaceId: context.state.workspaceId,
|
|
13294
|
+
deploymentId,
|
|
13295
|
+
sandboxId: context.state.sandboxId,
|
|
13296
|
+
runtimeType: preview.deploymentType,
|
|
13297
|
+
stack,
|
|
13298
|
+
workspace: preview.workspace,
|
|
13299
|
+
artifactImage,
|
|
13300
|
+
targetHost,
|
|
13301
|
+
serviceUrls,
|
|
13302
|
+
services: preview.services,
|
|
13303
|
+
placement: {
|
|
13304
|
+
hostId: deploymentTarget.hostId,
|
|
13305
|
+
requiredWorkerRole: "runtime",
|
|
13306
|
+
pool: deploymentTarget.pool,
|
|
13307
|
+
memoryReservationBytes: serviceMemoryReservation(preview.services)
|
|
13308
|
+
}
|
|
13309
|
+
},
|
|
13310
|
+
maxAttempts: 3
|
|
13311
|
+
})
|
|
13312
|
+
});
|
|
13313
|
+
operation = await wait(context.apiUrl, context.apiKey, submitted.operation.id, 30 * 60000);
|
|
13314
|
+
} catch (error) {
|
|
13315
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13316
|
+
throw new Error(`${message}; retry with ` + "`sandblocks sandbox deploy . --resume`");
|
|
13317
|
+
}
|
|
13318
|
+
context.state.deploymentId = deploymentId;
|
|
13319
|
+
await writeState(context.file, context.state);
|
|
13320
|
+
const routes = operation.result?.serviceRoutes ?? [];
|
|
13321
|
+
const images = operation.result?.images ?? [];
|
|
13322
|
+
if (typeof preview.ssoEnabled === "boolean") {
|
|
13323
|
+
await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/routing-policies/${encodeURIComponent(stack)}/${encodeURIComponent(preview.deploymentType)}`, { method: "PUT", body: JSON.stringify({ ssoEnabled: preview.ssoEnabled }) });
|
|
12795
13324
|
}
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
13325
|
+
let sandbox = await recordSandbox(context, loaded, preview, routes, operation.result?.healthEvidence ?? [], images, preview.steps.length ? "active" : "available");
|
|
13326
|
+
let checkEvidence = [];
|
|
13327
|
+
try {
|
|
13328
|
+
if (preview.steps.length) {
|
|
13329
|
+
const checkRoutes = sandbox.previewUrls ?? [];
|
|
13330
|
+
await waitForPublishedRoutes(checkRoutes);
|
|
13331
|
+
const serviceUrls2 = Object.fromEntries(checkRoutes.flatMap((route) => route.service ? [
|
|
13332
|
+
[
|
|
13333
|
+
`SANDBLOCKS_SERVICE_${route.service.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_URL`,
|
|
13334
|
+
route.url
|
|
13335
|
+
]
|
|
13336
|
+
] : []));
|
|
13337
|
+
const check = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/operations`, {
|
|
13338
|
+
method: "POST",
|
|
13339
|
+
headers: {
|
|
13340
|
+
"idempotency-key": `preview:${deploymentId}:checks`
|
|
13341
|
+
},
|
|
13342
|
+
body: JSON.stringify({
|
|
13343
|
+
kind: "workspace.check.run",
|
|
13344
|
+
stack: context.state.repository,
|
|
13345
|
+
environmentId: context.state.environment,
|
|
13346
|
+
environmentSnapshotId,
|
|
13347
|
+
payload: {
|
|
13348
|
+
workspaceId: buildWorkspaceId ?? context.state.workspaceId,
|
|
13349
|
+
deploymentId,
|
|
13350
|
+
sandboxId: context.state.sandboxId,
|
|
13351
|
+
runtimeType: preview.deploymentType,
|
|
13352
|
+
checks: preview.steps,
|
|
13353
|
+
serviceUrls: serviceUrls2,
|
|
13354
|
+
placement: { hostId: buildHostId ?? context.state.hostId }
|
|
13355
|
+
},
|
|
13356
|
+
maxAttempts: 1
|
|
13357
|
+
})
|
|
13358
|
+
});
|
|
13359
|
+
const checked2 = await wait(context.apiUrl, context.apiKey, check.operation.id, 60 * 60000);
|
|
13360
|
+
checkEvidence = checked2.result?.checks ?? [];
|
|
13361
|
+
if (checked2.result?.passed !== true) {
|
|
13362
|
+
throw new Error(`one or more post-deploy checks failed: ${JSON.stringify(checkEvidence)}`);
|
|
12805
13363
|
}
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
command: (context) => [
|
|
12819
|
-
options.binary ?? "claude",
|
|
12820
|
-
"--print",
|
|
12821
|
-
"--model",
|
|
12822
|
-
options.model,
|
|
12823
|
-
...options.effort ? ["--effort", options.effort] : [],
|
|
12824
|
-
...options.extraArgs ?? []
|
|
12825
|
-
],
|
|
12826
|
-
sessionId: extractSessionId,
|
|
12827
|
-
parseEvent: parseJsonAgentEvent
|
|
13364
|
+
sandbox = await recordSandbox(context, loaded, preview, routes, [...operation.result?.healthEvidence ?? [], ...checkEvidence], images, "available");
|
|
13365
|
+
}
|
|
13366
|
+
} catch (error) {
|
|
13367
|
+
await recordSandbox(context, loaded, preview, routes, [...operation.result?.healthEvidence ?? [], ...checkEvidence], images, "failed").catch(() => {
|
|
13368
|
+
return;
|
|
13369
|
+
});
|
|
13370
|
+
if (buildWorkspaceId)
|
|
13371
|
+
await destroyDetachedWorkspace(context, buildWorkspaceId, `preview:${deploymentId}:build-cleanup`).catch(() => {
|
|
13372
|
+
return;
|
|
13373
|
+
});
|
|
13374
|
+
await releaseEnvironmentSnapshot(context, environmentSnapshotId).catch(() => {
|
|
13375
|
+
return;
|
|
12828
13376
|
});
|
|
13377
|
+
throw error;
|
|
12829
13378
|
}
|
|
13379
|
+
if (buildWorkspaceId)
|
|
13380
|
+
await destroyDetachedWorkspace(context, buildWorkspaceId, `preview:${deploymentId}:build-cleanup`);
|
|
13381
|
+
await releaseEnvironmentSnapshot(context, environmentSnapshotId);
|
|
13382
|
+
context.state.deploymentId = deploymentId;
|
|
13383
|
+
context.state.candidate = undefined;
|
|
13384
|
+
const previewUrls = sandbox.previewUrls ?? [];
|
|
13385
|
+
context.state.previewUrls = previewUrls;
|
|
13386
|
+
await writeState(context.file, context.state);
|
|
13387
|
+
console.log(`deployment ${deploymentId} available`);
|
|
13388
|
+
for (const route of previewUrls)
|
|
13389
|
+
console.log(`${route.service ?? "service"} ${route.url}`);
|
|
12830
13390
|
}
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
13391
|
+
async function redeploy(args, dependencies) {
|
|
13392
|
+
const context = await getContext(args, dependencies);
|
|
13393
|
+
const routing = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/routing-aliases`);
|
|
13394
|
+
if (Array.isArray(routing.aliases) && routing.aliases.some((alias) => alias.sandboxId === context.state.sandboxId)) {
|
|
13395
|
+
throw new Error("promoted sandboxes are immutable; create a new sandbox and promote it instead");
|
|
13396
|
+
}
|
|
13397
|
+
await renewLease(context, dependencies);
|
|
13398
|
+
const loaded = await dependencies.load(context.root, dependencies.option(context.options, "config") ?? dependencies.option(context.options, "manifest"));
|
|
13399
|
+
const previousDeploymentId = context.state.deploymentId;
|
|
13400
|
+
const previousUrls = context.state.previewUrls;
|
|
13401
|
+
const { deploymentId: _deploymentId, previewUrls: _previewUrls, ...candidateState } = context.state;
|
|
13402
|
+
context.state = candidateState;
|
|
13403
|
+
await writeState(context.file, context.state);
|
|
13404
|
+
try {
|
|
13405
|
+
await destroyWorkspace(context, `${context.state.sandboxId}:workspace-redeploy-destroy:${Date.now()}`);
|
|
13406
|
+
const imported = await importSource({
|
|
13407
|
+
root: context.root,
|
|
13408
|
+
projectId: context.state.projectId,
|
|
13409
|
+
sandboxId: context.state.sandboxId,
|
|
13410
|
+
workspaceId: context.state.workspaceId,
|
|
13411
|
+
pool: dependencies.option(context.options, "pool") ?? process.env.SANDBLOCKS_WORKER_POOL ?? "build-fleet",
|
|
13412
|
+
apiUrl: context.apiUrl,
|
|
13413
|
+
apiKey: context.apiKey,
|
|
13414
|
+
idempotencyKey: `${context.state.sandboxId}:workspace-redeploy:${Date.now()}`,
|
|
13415
|
+
source: dependencies.source,
|
|
13416
|
+
..."image" in loaded.manifest.workspace && loaded.manifest.workspace.image ? { workspaceImage: loaded.manifest.workspace.image } : {}
|
|
12848
13417
|
});
|
|
13418
|
+
const hostId = String(imported.operation.assignedHostId ?? "");
|
|
13419
|
+
if (!hostId)
|
|
13420
|
+
throw new Error("workspace re-import did not bind a sandbox host");
|
|
13421
|
+
context.state.hostId = hostId;
|
|
13422
|
+
await writeState(context.file, context.state);
|
|
13423
|
+
await deploy(args, dependencies);
|
|
13424
|
+
if (previousDeploymentId)
|
|
13425
|
+
await destroyDeploymentById(context, previousDeploymentId);
|
|
13426
|
+
} catch (error) {
|
|
13427
|
+
const failedCandidateId = context.state.deploymentId;
|
|
13428
|
+
if (failedCandidateId && failedCandidateId !== previousDeploymentId) {
|
|
13429
|
+
await destroyDeploymentById(context, failedCandidateId).catch(() => {
|
|
13430
|
+
return;
|
|
13431
|
+
});
|
|
13432
|
+
}
|
|
13433
|
+
context.state.deploymentId = previousDeploymentId;
|
|
13434
|
+
context.state.previewUrls = previousUrls;
|
|
13435
|
+
await writeState(context.file, context.state);
|
|
13436
|
+
throw error;
|
|
12849
13437
|
}
|
|
12850
13438
|
}
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
13439
|
+
async function renew(args, dependencies) {
|
|
13440
|
+
const context = await getContext(args, dependencies);
|
|
13441
|
+
const lease = await renewLease(context, dependencies);
|
|
13442
|
+
console.log(`lease ${lease.lease.id} renewed until ${lease.lease.expiresAt}`);
|
|
13443
|
+
}
|
|
13444
|
+
async function renewLease(context, dependencies) {
|
|
13445
|
+
const previousLeaseId = context.state.leaseId;
|
|
13446
|
+
const lease = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/sandboxes/${context.state.sandboxId}/leases`, {
|
|
13447
|
+
method: "POST",
|
|
13448
|
+
body: JSON.stringify({
|
|
13449
|
+
subject: `sandblocks-cli:${process.env.USER ?? "developer"}`,
|
|
13450
|
+
scopes: ["sandbox:read", "workspace:read", "workspace:write", "workspace:exec", "workspace:git"],
|
|
13451
|
+
ttlSeconds: Number(dependencies.option(context.options, "ttl-seconds") ?? 28800)
|
|
13452
|
+
})
|
|
13453
|
+
});
|
|
13454
|
+
context.state.leaseId = lease.lease.id;
|
|
13455
|
+
context.state.leaseToken = lease.token;
|
|
13456
|
+
await writeState(context.file, context.state);
|
|
13457
|
+
if (previousLeaseId && previousLeaseId !== lease.lease.id) {
|
|
13458
|
+
await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/sandboxes/${context.state.sandboxId}/leases/${previousLeaseId}`, { method: "DELETE" }).catch(() => {
|
|
13459
|
+
return;
|
|
12868
13460
|
});
|
|
12869
13461
|
}
|
|
13462
|
+
return lease;
|
|
12870
13463
|
}
|
|
12871
|
-
function
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
}
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
13464
|
+
async function exec(args, dependencies) {
|
|
13465
|
+
const separator = args.indexOf("--");
|
|
13466
|
+
if (separator < 0 || separator === args.length - 1)
|
|
13467
|
+
throw new Error("sandbox exec requires -- followed by a command");
|
|
13468
|
+
const context = await getContext(args.slice(0, separator), dependencies);
|
|
13469
|
+
const command = args.slice(separator + 1);
|
|
13470
|
+
const operation = await dataOperation(context, "exec", { executable: command[0], args: command.slice(1) });
|
|
13471
|
+
printOutput(operation);
|
|
13472
|
+
}
|
|
13473
|
+
async function git2(args, dependencies) {
|
|
13474
|
+
const actionIndex = args.findIndex((value) => ["status", "diff", "stage", "checkout"].includes(value));
|
|
13475
|
+
if (actionIndex < 0)
|
|
13476
|
+
throw new Error("sandbox git action must be status, diff, stage, or checkout");
|
|
13477
|
+
const context = await getContext(args.slice(0, actionIndex), dependencies);
|
|
13478
|
+
const action = args[actionIndex];
|
|
13479
|
+
if (!action)
|
|
13480
|
+
throw new Error("sandbox git action is required");
|
|
13481
|
+
const rest = args.slice(actionIndex + 1);
|
|
13482
|
+
const operation = await dataOperation(context, "git", action === "checkout" ? { action, branch: rest[0] } : { action, ...rest.length ? { paths: rest } : {} });
|
|
13483
|
+
printOutput(operation);
|
|
13484
|
+
}
|
|
13485
|
+
async function down(args, dependencies) {
|
|
13486
|
+
const context = await getContext(args, dependencies);
|
|
13487
|
+
await destroyDeployment(context);
|
|
13488
|
+
await destroyWorkspace(context, `${context.state.sandboxId}:workspace-destroy`).catch(() => {
|
|
13489
|
+
return;
|
|
13490
|
+
});
|
|
13491
|
+
await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/sandboxes/${context.state.sandboxId}`, { method: "DELETE" }).catch(() => {
|
|
13492
|
+
return;
|
|
13493
|
+
});
|
|
13494
|
+
await rm4(context.file, { force: true });
|
|
13495
|
+
console.log(`sandbox ${context.state.sandboxId} destroyed`);
|
|
13496
|
+
}
|
|
13497
|
+
async function resolveDeploymentTarget(context, fallback) {
|
|
13498
|
+
const project = encodeURIComponent(context.state.projectId);
|
|
13499
|
+
const [hostBody, workerBody] = await Promise.all([
|
|
13500
|
+
request(context.apiUrl, context.apiKey, `/v1/projects/${project}/hosts`),
|
|
13501
|
+
request(context.apiUrl, context.apiKey, `/v1/projects/${project}/workers`)
|
|
13502
|
+
]);
|
|
13503
|
+
const workers = Array.isArray(workerBody.workers) ? workerBody.workers : [];
|
|
13504
|
+
const requestedPool = process.env.SANDBLOCKS_DEPLOYMENT_WORKER_POOL?.trim() || process.env.SANDBLOCKS_RUNTIME_WORKER_POOL?.trim();
|
|
13505
|
+
const eligible = workers.filter((worker2) => worker2?.status === "online" && Array.isArray(worker2.roles) && worker2.roles.includes("runtime"));
|
|
13506
|
+
const worker = eligible.find((candidate) => requestedPool && candidate.pool === requestedPool) ?? eligible[0];
|
|
13507
|
+
if (!worker?.hostId)
|
|
13508
|
+
throw new Error("no online deployment worker is available");
|
|
13509
|
+
const hosts = Array.isArray(hostBody.hosts) ? hostBody.hosts : [];
|
|
13510
|
+
const host = hosts.find((candidate) => candidate?.id === worker.hostId);
|
|
13511
|
+
const configuredTarget = worker?.configuration?.targetAddress ?? host?.labels?.targetHost;
|
|
13512
|
+
const targetHost = typeof fallback === "string" && fallback.trim() ? fallback.trim() : typeof configuredTarget === "string" && configuredTarget.trim() ? configuredTarget.trim() : undefined;
|
|
13513
|
+
if (!targetHost)
|
|
13514
|
+
throw new Error("deployment host targetHost label or preview targetHost is required");
|
|
13515
|
+
return { hostId: String(worker.hostId), targetHost, pool: String(worker.pool ?? "unpooled") };
|
|
13516
|
+
}
|
|
13517
|
+
async function recordSandbox(context, loaded, preview, serviceRoutes, healthEvidence, images, status3) {
|
|
13518
|
+
const deploymentId = context.state.deploymentId;
|
|
13519
|
+
if (!deploymentId)
|
|
13520
|
+
throw new Error("deployment ID is required when recording sandbox routes");
|
|
13521
|
+
const body = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/sandboxes`, {
|
|
13522
|
+
method: "POST",
|
|
13523
|
+
body: JSON.stringify({
|
|
13524
|
+
id: context.state.sandboxId,
|
|
13525
|
+
goal: preview.goal,
|
|
13526
|
+
status: status3,
|
|
13527
|
+
stack: loaded.manifest.name ?? context.state.repository,
|
|
13528
|
+
deploymentType: preview.deploymentType,
|
|
13529
|
+
revisionId: deploymentId,
|
|
13530
|
+
generation: Date.now(),
|
|
13531
|
+
baseDomain: preview.baseDomain,
|
|
13532
|
+
domains: await managedServiceDomains(context, loaded.manifest.name ?? context.state.repository, preview, deploymentId),
|
|
13533
|
+
stableDomains: preview.stableDomains,
|
|
13534
|
+
serviceRoutes,
|
|
13535
|
+
healthEvidence,
|
|
13536
|
+
images,
|
|
13537
|
+
volumes: [
|
|
13538
|
+
{
|
|
13539
|
+
name: `${context.state.repository}-workspace`,
|
|
13540
|
+
target: "/workspace",
|
|
13541
|
+
access: "read-write"
|
|
13542
|
+
}
|
|
13543
|
+
],
|
|
13544
|
+
repositories: [context.state.repository],
|
|
13545
|
+
executor: "sandblocks"
|
|
13546
|
+
})
|
|
13547
|
+
});
|
|
13548
|
+
return body.sandbox;
|
|
12889
13549
|
}
|
|
12890
|
-
function
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
13550
|
+
async function createEnvironmentSnapshot(context) {
|
|
13551
|
+
const body = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/environment-snapshots`, {
|
|
13552
|
+
method: "POST",
|
|
13553
|
+
body: JSON.stringify({
|
|
13554
|
+
stack: context.state.repository,
|
|
13555
|
+
app: "*",
|
|
13556
|
+
environmentId: context.state.environment
|
|
13557
|
+
})
|
|
13558
|
+
});
|
|
13559
|
+
const id2 = body.snapshot?.id;
|
|
13560
|
+
if (typeof id2 !== "string" || !/^[a-f0-9-]{36}$/.test(id2)) {
|
|
13561
|
+
throw new Error("managed environment snapshot response is invalid");
|
|
13562
|
+
}
|
|
13563
|
+
return id2;
|
|
12902
13564
|
}
|
|
12903
|
-
function
|
|
12904
|
-
|
|
12905
|
-
${stderr}`.match(/(?:session[_ -]?id|session)[:=\s]+([A-Za-z0-9._-]{8,})/i);
|
|
12906
|
-
return match?.[1];
|
|
13565
|
+
async function releaseEnvironmentSnapshot(context, snapshotId) {
|
|
13566
|
+
await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/environment-snapshots/${snapshotId}`, { method: "DELETE" });
|
|
12907
13567
|
}
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
return
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
};
|
|
12916
|
-
async function extractOutput(spec, text2) {
|
|
12917
|
-
const pattern = new RegExp(`<${escapeRegex(spec.tag)}>([\\s\\S]*?)<\\/${escapeRegex(spec.tag)}>`);
|
|
12918
|
-
const match = text2.match(pattern);
|
|
12919
|
-
if (!match)
|
|
12920
|
-
throw new Error(`structured output tag <${spec.tag}> was not found`);
|
|
12921
|
-
const content = match[1]?.trim();
|
|
12922
|
-
if (content === undefined)
|
|
12923
|
-
throw new Error(`structured output tag <${spec.tag}> is empty`);
|
|
12924
|
-
if (spec.kind === "string")
|
|
12925
|
-
return content;
|
|
12926
|
-
let value;
|
|
12927
|
-
try {
|
|
12928
|
-
value = JSON.parse(content);
|
|
12929
|
-
} catch {
|
|
12930
|
-
throw new Error(`structured output <${spec.tag}> is not valid JSON`);
|
|
12931
|
-
}
|
|
12932
|
-
const schema = spec.schema;
|
|
12933
|
-
if (schema["~standard"]) {
|
|
12934
|
-
const result = await schema["~standard"].validate(value);
|
|
12935
|
-
if (result.issues?.length)
|
|
12936
|
-
throw new Error(`structured output validation failed: ${JSON.stringify(result.issues)}`);
|
|
12937
|
-
return result.value;
|
|
12938
|
-
}
|
|
12939
|
-
if (schema.safeParse) {
|
|
12940
|
-
const result = schema.safeParse(value);
|
|
12941
|
-
if (!result.success)
|
|
12942
|
-
throw new Error(`structured output validation failed: ${String(result.error)}`);
|
|
12943
|
-
return result.data;
|
|
12944
|
-
}
|
|
12945
|
-
if (schema.parse)
|
|
12946
|
-
return schema.parse(value);
|
|
12947
|
-
throw new Error("structured output schema is unsupported");
|
|
13568
|
+
async function destroyDeployment(context) {
|
|
13569
|
+
if (!context.state.deploymentId)
|
|
13570
|
+
return;
|
|
13571
|
+
await destroyDeploymentById(context, context.state.deploymentId);
|
|
13572
|
+
const { deploymentId: _deploymentId, previewUrls: _previewUrls, ...remainingState } = context.state;
|
|
13573
|
+
context.state = remainingState;
|
|
13574
|
+
await writeState(context.file, context.state);
|
|
12948
13575
|
}
|
|
12949
|
-
function
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
13576
|
+
async function destroyDeploymentById(context, deploymentId) {
|
|
13577
|
+
const body = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/operations`, {
|
|
13578
|
+
method: "POST",
|
|
13579
|
+
headers: { "idempotency-key": `preview:${deploymentId}:destroy:${Date.now()}` },
|
|
13580
|
+
body: JSON.stringify({
|
|
13581
|
+
kind: "workspace.service.destroy",
|
|
13582
|
+
payload: {
|
|
13583
|
+
deploymentId,
|
|
13584
|
+
placement: { hostId: context.state.deploymentHostId ?? context.state.hostId }
|
|
13585
|
+
},
|
|
13586
|
+
maxAttempts: 1
|
|
13587
|
+
})
|
|
13588
|
+
});
|
|
13589
|
+
await wait(context.apiUrl, context.apiKey, body.operation.id);
|
|
12953
13590
|
}
|
|
12954
|
-
function
|
|
12955
|
-
|
|
13591
|
+
async function destroyWorkspace(context, idempotencyKey) {
|
|
13592
|
+
await destroyDetachedWorkspace(context, context.state.workspaceId, idempotencyKey);
|
|
12956
13593
|
}
|
|
12957
|
-
async function
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
cwd: options.cwd ?? options.hostCwd,
|
|
12967
|
-
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
12968
|
-
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
13594
|
+
async function destroyDetachedWorkspace(context, workspaceId, idempotencyKey) {
|
|
13595
|
+
const response = await fetchWithRetry(`${context.apiUrl}/v1/projects/${encodeURIComponent(context.state.projectId)}/workspaces/${encodeURIComponent(workspaceId)}`, {
|
|
13596
|
+
method: "DELETE",
|
|
13597
|
+
headers: {
|
|
13598
|
+
"content-type": "application/json",
|
|
13599
|
+
"x-sandblocks-api-key": context.apiKey,
|
|
13600
|
+
"idempotency-key": idempotencyKey
|
|
13601
|
+
},
|
|
13602
|
+
body: JSON.stringify({ idempotencyKey })
|
|
12969
13603
|
});
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
13604
|
+
const body = await response.json().catch(() => ({}));
|
|
13605
|
+
if (response.ok)
|
|
13606
|
+
await wait(context.apiUrl, context.apiKey, body.operation.id);
|
|
13607
|
+
else if (![404, 409].includes(response.status))
|
|
13608
|
+
throw new Error(String(body.error ?? "workspace destroy failed"));
|
|
13609
|
+
}
|
|
13610
|
+
async function importSource(input) {
|
|
13611
|
+
const source = await input.source(input.root);
|
|
13612
|
+
const response = await fetchWithRetry(`${input.apiUrl}/v1/projects/${encodeURIComponent(input.projectId)}/workspaces/import`, {
|
|
13613
|
+
method: "POST",
|
|
13614
|
+
headers: {
|
|
13615
|
+
"content-type": "application/x-tar",
|
|
13616
|
+
"content-length": String(source.bundle.byteLength),
|
|
13617
|
+
"x-sandblocks-api-key": input.apiKey,
|
|
13618
|
+
"x-sandblocks-workspace-id": input.workspaceId,
|
|
13619
|
+
...input.sandboxId ? { "x-sandblocks-sandbox-id": input.sandboxId } : {},
|
|
13620
|
+
"x-sandblocks-worker-pool": input.pool,
|
|
13621
|
+
...input.workspaceImage ? { "x-sandblocks-workspace-image": input.workspaceImage } : {},
|
|
13622
|
+
"idempotency-key": input.idempotencyKey
|
|
13623
|
+
},
|
|
13624
|
+
body: new Blob([Uint8Array.from(source.bundle)])
|
|
12982
13625
|
});
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
|
|
12986
|
-
|
|
12987
|
-
|
|
12988
|
-
|
|
13626
|
+
const submitted = await response.json().catch(() => ({}));
|
|
13627
|
+
if (!response.ok)
|
|
13628
|
+
throw new Error(String(submitted?.error ?? `sandbox import failed (${response.status})`));
|
|
13629
|
+
return {
|
|
13630
|
+
files: source.files,
|
|
13631
|
+
bytes: source.bundle.byteLength,
|
|
13632
|
+
operation: await wait(input.apiUrl, input.apiKey, submitted.operation.id)
|
|
13633
|
+
};
|
|
13634
|
+
}
|
|
13635
|
+
async function dataOperation(context, action, body) {
|
|
13636
|
+
context.state.sequence += 1;
|
|
13637
|
+
await writeState(context.file, context.state);
|
|
13638
|
+
const submitted = await leaseRequest(context, `/v1/sandboxes/${context.state.sandboxId}/workspaces/${context.state.repository}/${action}`, {
|
|
13639
|
+
method: "POST",
|
|
13640
|
+
headers: { "idempotency-key": `cli:${context.state.sandboxId}:${context.state.sequence}:${action}` },
|
|
13641
|
+
body: JSON.stringify(body)
|
|
12989
13642
|
});
|
|
12990
|
-
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
|
|
13643
|
+
return waitLease(context, submitted.operation.id);
|
|
13644
|
+
}
|
|
13645
|
+
async function getContext(args, dependencies) {
|
|
13646
|
+
const options = dependencies.parse(args);
|
|
13647
|
+
const root = path5.resolve(options.positionals[0] ?? process.cwd());
|
|
13648
|
+
await loadLocalEnvironment(root);
|
|
13649
|
+
const environment = dependencies.option(options, "environment") ?? "preview";
|
|
13650
|
+
const file = statePath(root, environment);
|
|
13651
|
+
let state;
|
|
12996
13652
|
try {
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
});
|
|
13001
|
-
await callbackQueue;
|
|
13002
|
-
if (options.signal?.aborted)
|
|
13003
|
-
throw options.signal.reason ?? new Error("operation aborted");
|
|
13004
|
-
return {
|
|
13005
|
-
exitCode: timedOut ? 124 : exitCode,
|
|
13006
|
-
stdout,
|
|
13007
|
-
stderr,
|
|
13008
|
-
timedOut,
|
|
13009
|
-
startedAt,
|
|
13010
|
-
finishedAt: new Date().toISOString()
|
|
13011
|
-
};
|
|
13012
|
-
} finally {
|
|
13013
|
-
if (timer)
|
|
13014
|
-
clearTimeout(timer);
|
|
13015
|
-
options.signal?.removeEventListener("abort", stop);
|
|
13653
|
+
state = JSON.parse(await readFile5(file, "utf8"));
|
|
13654
|
+
} catch {
|
|
13655
|
+
throw new Error(`sandbox '${environment}' does not exist; use sandbox create or up`);
|
|
13016
13656
|
}
|
|
13657
|
+
const configured = client(options, dependencies.option, state.projectId);
|
|
13658
|
+
return { root, file, options, state, ...configured };
|
|
13017
13659
|
}
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
}
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13660
|
+
function selectPreview(loaded, id2) {
|
|
13661
|
+
return resolveEnvironment(loaded.manifest, id2);
|
|
13662
|
+
}
|
|
13663
|
+
function client(options, option2, projectId) {
|
|
13664
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
13665
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY ?? "";
|
|
13666
|
+
if (!apiUrl || !apiKey)
|
|
13667
|
+
throw new Error("SANDBLOCKS_API_URL and SANDBLOCKS_API_KEY are required");
|
|
13668
|
+
return { apiUrl, apiKey, projectId };
|
|
13669
|
+
}
|
|
13670
|
+
async function request(apiUrl, apiKey, pathname, init = {}) {
|
|
13671
|
+
const headers = new Headers(init.headers);
|
|
13672
|
+
headers.set("x-sandblocks-api-key", apiKey);
|
|
13673
|
+
if (init.body)
|
|
13674
|
+
headers.set("content-type", "application/json");
|
|
13675
|
+
const response = await fetchWithRetry(`${apiUrl}${pathname}`, { ...init, headers });
|
|
13676
|
+
const body = await response.json().catch(() => ({}));
|
|
13677
|
+
if (!response.ok)
|
|
13678
|
+
throw new Error(String(body.error ?? `Sandblocks request failed (${response.status})`));
|
|
13679
|
+
return body;
|
|
13680
|
+
}
|
|
13681
|
+
async function leaseRequest(context, pathname, init = {}) {
|
|
13682
|
+
const headers = new Headers(init.headers);
|
|
13683
|
+
headers.set("authorization", `Bearer ${context.state.leaseToken}`);
|
|
13684
|
+
if (init.body)
|
|
13685
|
+
headers.set("content-type", "application/json");
|
|
13686
|
+
const response = await fetchWithRetry(`${context.apiUrl}${pathname}`, { ...init, headers });
|
|
13687
|
+
const body = await response.json().catch(() => ({}));
|
|
13688
|
+
if (!response.ok)
|
|
13689
|
+
throw new Error(String(body.error ?? `Sandblocks lease request failed (${response.status})`));
|
|
13690
|
+
return body;
|
|
13691
|
+
}
|
|
13692
|
+
async function waitForPublishedRoutes(routes, timeoutMs = 240000) {
|
|
13693
|
+
const deadline = Date.now() + timeoutMs;
|
|
13694
|
+
let pending = routes.map((route) => ({
|
|
13695
|
+
label: route.url,
|
|
13696
|
+
url: route.service === "api" ? new URL("/health", route.url).toString() : route.url
|
|
13697
|
+
}));
|
|
13698
|
+
while (pending.length && Date.now() < deadline) {
|
|
13699
|
+
const checks = await Promise.all(pending.map(async (route) => {
|
|
13700
|
+
try {
|
|
13701
|
+
const response = await fetch(route.url, { redirect: "manual" });
|
|
13702
|
+
await response.body?.cancel().catch(() => {
|
|
13703
|
+
return;
|
|
13704
|
+
});
|
|
13705
|
+
return response.status >= 200 && response.status < 400 ? undefined : route;
|
|
13706
|
+
} catch {
|
|
13707
|
+
return route;
|
|
13708
|
+
}
|
|
13709
|
+
}));
|
|
13710
|
+
pending = checks.filter((route) => typeof route === "object" && route !== null);
|
|
13711
|
+
if (pending.length)
|
|
13712
|
+
await Bun.sleep(2000);
|
|
13049
13713
|
}
|
|
13050
|
-
|
|
13051
|
-
|
|
13714
|
+
if (pending.length) {
|
|
13715
|
+
throw new Error(`preview routes did not become ready: ${pending.map((route) => route.label).join(", ")}`);
|
|
13052
13716
|
}
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13717
|
+
}
|
|
13718
|
+
async function fetchWithRetry(url, init = {}) {
|
|
13719
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
13720
|
+
const headers = new Headers(init.headers);
|
|
13721
|
+
const safeToRetry = method === "GET" || method === "HEAD" || headers.has("idempotency-key");
|
|
13722
|
+
const attempts = safeToRetry ? 8 : 1;
|
|
13723
|
+
let lastError;
|
|
13724
|
+
for (let attempt = 0;attempt < attempts; attempt += 1) {
|
|
13725
|
+
try {
|
|
13726
|
+
const response = await fetch(url, init);
|
|
13727
|
+
if (![409, 429, 502, 503, 504].includes(response.status) || attempt === attempts - 1)
|
|
13728
|
+
return response;
|
|
13729
|
+
await response.body?.cancel().catch(() => {
|
|
13056
13730
|
return;
|
|
13057
|
-
const queued = this.eventQueue.shift();
|
|
13058
|
-
if (queued) {
|
|
13059
|
-
yield queued;
|
|
13060
|
-
continue;
|
|
13061
|
-
}
|
|
13062
|
-
const event = await new Promise((resolve22) => {
|
|
13063
|
-
const abort = () => resolve22(undefined);
|
|
13064
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
13065
|
-
this.eventWaiters.push((value) => {
|
|
13066
|
-
options.signal?.removeEventListener("abort", abort);
|
|
13067
|
-
resolve22(value);
|
|
13068
|
-
});
|
|
13069
13731
|
});
|
|
13070
|
-
if (!event)
|
|
13071
|
-
return;
|
|
13072
|
-
yield event;
|
|
13073
|
-
}
|
|
13074
|
-
}
|
|
13075
|
-
async run(options) {
|
|
13076
|
-
if (options.output && (options.maxIterations ?? 1) !== 1)
|
|
13077
|
-
throw new Error("structured output requires maxIterations === 1");
|
|
13078
|
-
const startedAt = new Date().toISOString();
|
|
13079
|
-
const prompt = await resolvePrompt(options);
|
|
13080
|
-
const signals = array(options.completionSignal ?? "<promise>COMPLETE</promise>");
|
|
13081
|
-
const logger = createLogger(options.logging, this.git.hostCwd);
|
|
13082
|
-
const baseline = await gitHead(this.git.runtimeCwd);
|
|
13083
|
-
const iterations = [];
|
|
13084
|
-
try {
|
|
13085
|
-
await runSandboxHooks(this.runtime, options.hooks?.sandbox?.beforeAgent);
|
|
13086
|
-
for (let iteration = 1;iteration <= (options.maxIterations ?? 1); iteration += 1) {
|
|
13087
|
-
const result = await options.agent.run({
|
|
13088
|
-
runtime: this.runtime,
|
|
13089
|
-
prompt,
|
|
13090
|
-
iteration,
|
|
13091
|
-
signal: options.signal,
|
|
13092
|
-
idleTimeoutSeconds: options.idleTimeoutSeconds ?? 600,
|
|
13093
|
-
completionTimeoutSeconds: options.completionTimeoutSeconds ?? 60,
|
|
13094
|
-
completionSignal: signals,
|
|
13095
|
-
emit: async (event) => {
|
|
13096
|
-
this.publish(event);
|
|
13097
|
-
await logger(event);
|
|
13098
|
-
}
|
|
13099
|
-
});
|
|
13100
|
-
iterations.push(result);
|
|
13101
|
-
if (result.exitCode !== 0)
|
|
13102
|
-
throw new Error(result.stderr || `${options.agent.id} exited ${result.exitCode}`);
|
|
13103
|
-
if (result.completionSignal)
|
|
13104
|
-
break;
|
|
13105
|
-
}
|
|
13106
|
-
await runSandboxHooks(this.runtime, options.hooks?.sandbox?.afterAgent);
|
|
13107
|
-
await this.runtime.syncToHost?.();
|
|
13108
|
-
const commits = await collectCommits(this.git.runtimeCwd, baseline, this.options.timeouts?.commitCollectionMs);
|
|
13109
|
-
if (this.git.mergeToHead && this.git.branch)
|
|
13110
|
-
await mergeBranch(this.git, this.options.timeouts?.mergeToHostMs);
|
|
13111
|
-
const allOutput = iterations.map((item) => item.stdout).join(`
|
|
13112
|
-
`);
|
|
13113
|
-
const completionSignal = iterations.find((item) => item.completionSignal)?.completionSignal;
|
|
13114
|
-
return {
|
|
13115
|
-
sandboxId: this.id,
|
|
13116
|
-
provider: this.provider,
|
|
13117
|
-
iterations,
|
|
13118
|
-
commits,
|
|
13119
|
-
...this.git.branch ? { branch: this.git.branch } : {},
|
|
13120
|
-
...completionSignal ? { completionSignal } : {},
|
|
13121
|
-
...options.output ? { output: await extractOutput(options.output, allOutput) } : {},
|
|
13122
|
-
startedAt,
|
|
13123
|
-
finishedAt: new Date().toISOString()
|
|
13124
|
-
};
|
|
13125
13732
|
} catch (error) {
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
}
|
|
13130
|
-
async destroy() {
|
|
13131
|
-
if (this.disposed)
|
|
13132
|
-
return;
|
|
13133
|
-
this.disposed = true;
|
|
13134
|
-
await runHostHooks(this.options.hooks?.host?.beforeDestroy, this.git.hostCwd);
|
|
13135
|
-
const retention = this.options.retention ?? "destroy";
|
|
13136
|
-
if (retention === "destroy" || retention === "on-failure" && !this.failed)
|
|
13137
|
-
await this.runtime.destroy();
|
|
13138
|
-
for (const waiter of this.eventWaiters.splice(0))
|
|
13139
|
-
waiter(undefined);
|
|
13140
|
-
if (this.git.worktree && retention !== "always") {
|
|
13141
|
-
await executeProcess([
|
|
13142
|
-
"git",
|
|
13143
|
-
"-C",
|
|
13144
|
-
this.git.hostCwd,
|
|
13145
|
-
"worktree",
|
|
13146
|
-
"remove",
|
|
13147
|
-
"--force",
|
|
13148
|
-
this.git.worktree
|
|
13149
|
-
]);
|
|
13150
|
-
await rm4(this.git.worktree, { recursive: true, force: true });
|
|
13733
|
+
lastError = error;
|
|
13734
|
+
if (attempt === attempts - 1)
|
|
13735
|
+
throw error;
|
|
13151
13736
|
}
|
|
13737
|
+
await Bun.sleep(Math.min(5000, 250 * 2 ** attempt));
|
|
13152
13738
|
}
|
|
13153
|
-
|
|
13154
|
-
await this.destroy();
|
|
13155
|
-
}
|
|
13156
|
-
publish(event) {
|
|
13157
|
-
const waiter = this.eventWaiters.shift();
|
|
13158
|
-
if (waiter)
|
|
13159
|
-
waiter(event);
|
|
13160
|
-
else
|
|
13161
|
-
this.eventQueue.push(event);
|
|
13162
|
-
}
|
|
13739
|
+
throw lastError instanceof Error ? lastError : new Error("Sandblocks request failed");
|
|
13163
13740
|
}
|
|
13164
|
-
async function
|
|
13165
|
-
const
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
throw new Error("copyToWorktree is not supported with the head branch strategy");
|
|
13174
|
-
}
|
|
13175
|
-
const git3 = await prepareGit(hostCwd, branchStrategy, options.timeouts?.gitSetupMs);
|
|
13176
|
-
await runHostHooks(options.hooks?.host?.onWorktreeReady, git3.runtimeCwd);
|
|
13177
|
-
for (const file of options.copyToWorktree ?? []) {
|
|
13178
|
-
const source = resolve(hostCwd, file);
|
|
13179
|
-
const destination = resolve(git3.runtimeCwd, file);
|
|
13180
|
-
await mkdir4(dirname(destination), { recursive: true });
|
|
13181
|
-
await withTimeout(cp(source, destination, { recursive: true }), options.timeouts?.copyToWorktreeMs ?? 60000, `copying '${file}'`);
|
|
13182
|
-
}
|
|
13183
|
-
const id2 = options.id ?? randomUUID2();
|
|
13184
|
-
const runtime = await provider.create({
|
|
13185
|
-
id: id2,
|
|
13186
|
-
cwd: git3.runtimeCwd,
|
|
13187
|
-
image: options.image,
|
|
13188
|
-
env: options.env,
|
|
13189
|
-
mounts: options.mounts,
|
|
13190
|
-
network: options.network,
|
|
13191
|
-
groups: options.groups,
|
|
13192
|
-
devices: options.devices,
|
|
13193
|
-
cpus: options.cpus,
|
|
13194
|
-
memoryMb: options.memoryMb,
|
|
13195
|
-
pids: options.pids,
|
|
13196
|
-
tmpfsMb: options.tmpfsMb,
|
|
13197
|
-
signal: options.signal,
|
|
13198
|
-
idempotencyKey: options.idempotencyKey,
|
|
13199
|
-
metadata: options.metadata
|
|
13200
|
-
});
|
|
13201
|
-
const sandbox = new SandblocksSandbox(runtime, options, git3);
|
|
13202
|
-
try {
|
|
13203
|
-
await runHostHooks(options.hooks?.host?.onSandboxReady, hostCwd);
|
|
13204
|
-
await runHostHooks(options.hooks?.host?.afterCreate, hostCwd);
|
|
13205
|
-
await runSandboxHooks(runtime, options.hooks?.sandbox?.onSandboxReady);
|
|
13206
|
-
await runSandboxHooks(runtime, options.hooks?.sandbox?.afterCreate);
|
|
13207
|
-
return sandbox;
|
|
13208
|
-
} catch (error) {
|
|
13209
|
-
await sandbox.destroy().catch(() => {
|
|
13210
|
-
return;
|
|
13211
|
-
});
|
|
13212
|
-
throw error;
|
|
13741
|
+
async function wait(apiUrl, apiKey, operationId, timeoutMs = 30 * 60000) {
|
|
13742
|
+
const deadline = Date.now() + timeoutMs;
|
|
13743
|
+
while (Date.now() < deadline) {
|
|
13744
|
+
const body = await request(apiUrl, apiKey, `/v1/operations/${operationId}`);
|
|
13745
|
+
if (body.operation.state === "succeeded")
|
|
13746
|
+
return body.operation;
|
|
13747
|
+
if (["failed", "cancelled"].includes(body.operation.state))
|
|
13748
|
+
throw new Error(body.operation.failureReason ?? `operation ${body.operation.state}`);
|
|
13749
|
+
await Bun.sleep(500);
|
|
13213
13750
|
}
|
|
13751
|
+
throw new Error(`operation ${operationId} timed out`);
|
|
13214
13752
|
}
|
|
13215
|
-
async function
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13753
|
+
async function waitLease(context, operationId) {
|
|
13754
|
+
const deadline = Date.now() + 3 * 60000;
|
|
13755
|
+
while (Date.now() < deadline) {
|
|
13756
|
+
const body = await leaseRequest(context, `/v1/sandbox-operations/${operationId}`);
|
|
13757
|
+
if (body.operation.state === "succeeded")
|
|
13758
|
+
return body.operation;
|
|
13759
|
+
if (["failed", "cancelled"].includes(body.operation.state))
|
|
13760
|
+
throw new Error(body.operation.failureReason ?? `operation ${body.operation.state}`);
|
|
13761
|
+
await Bun.sleep(500);
|
|
13762
|
+
}
|
|
13763
|
+
throw new Error(`sandbox operation ${operationId} timed out`);
|
|
13221
13764
|
}
|
|
13222
|
-
async function
|
|
13223
|
-
const
|
|
13765
|
+
async function loadLocalEnvironment(root) {
|
|
13766
|
+
const file = path5.join(root, ".sandblocks", "config.env");
|
|
13767
|
+
let contents;
|
|
13224
13768
|
try {
|
|
13225
|
-
|
|
13226
|
-
}
|
|
13227
|
-
|
|
13769
|
+
contents = await readFile5(file, "utf8");
|
|
13770
|
+
} catch {
|
|
13771
|
+
return;
|
|
13772
|
+
}
|
|
13773
|
+
for (const raw of contents.split(/\r?\n/)) {
|
|
13774
|
+
const line = raw.trim();
|
|
13775
|
+
if (!line || line.startsWith("#") || !line.includes("="))
|
|
13776
|
+
continue;
|
|
13777
|
+
const [key, ...parts] = line.split("=");
|
|
13778
|
+
if (!key?.startsWith("SANDBLOCKS_") || process.env[key] !== undefined)
|
|
13779
|
+
continue;
|
|
13780
|
+
process.env[key] = parts.join("=").trim().replace(/^['"]|['"]$/g, "");
|
|
13228
13781
|
}
|
|
13229
13782
|
}
|
|
13230
|
-
async function
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
const
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
return prompt;
|
|
13238
|
-
}
|
|
13239
|
-
async function prepareGit(hostCwd, strategy, timeoutMs = 30000) {
|
|
13240
|
-
if (strategy.type === "head")
|
|
13241
|
-
return { hostCwd, runtimeCwd: hostCwd };
|
|
13242
|
-
const originalHead = await gitHead(hostCwd);
|
|
13243
|
-
if (strategy.type === "branch") {
|
|
13244
|
-
await checked(["git", "-C", hostCwd, "switch", "-C", strategy.name], timeoutMs);
|
|
13245
|
-
return { hostCwd, runtimeCwd: hostCwd, branch: strategy.name, originalHead };
|
|
13246
|
-
}
|
|
13247
|
-
const branch = strategy.name ?? `sandblocks/${randomUUID2().slice(0, 8)}`;
|
|
13248
|
-
const worktree = resolve(hostCwd, ".sandblocks", "worktrees", branch.replace(/[^A-Za-z0-9_.-]/g, "-"));
|
|
13249
|
-
await mkdir4(dirname(worktree), { recursive: true });
|
|
13250
|
-
await checked(["git", "-C", hostCwd, "worktree", "add", "-B", branch, worktree, "HEAD"], timeoutMs);
|
|
13783
|
+
async function applyManagedRoutingPolicy(context, stack, preview) {
|
|
13784
|
+
const body = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/routing-policies/${encodeURIComponent(stack)}/${encodeURIComponent(preview.deploymentType)}`);
|
|
13785
|
+
const policy = body.policy;
|
|
13786
|
+
const primaryDomains = Object.fromEntries(Object.entries(policy.domains ?? {}).flatMap(([service, domains]) => {
|
|
13787
|
+
const domain = Array.isArray(domains) ? domains[0] : domains;
|
|
13788
|
+
return domain ? [[service, domain]] : [];
|
|
13789
|
+
}));
|
|
13251
13790
|
return {
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
mergeToHead: strategy.type === "merge-to-head",
|
|
13257
|
-
originalHead
|
|
13791
|
+
...preview,
|
|
13792
|
+
baseDomain: policy.baseDomain ?? preview.baseDomain,
|
|
13793
|
+
domains: { ...preview.domains, ...primaryDomains },
|
|
13794
|
+
stableDomains: { ...preview.stableDomains, ...policy.stableDomains }
|
|
13258
13795
|
};
|
|
13259
13796
|
}
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
timeoutMs
|
|
13274
|
-
});
|
|
13275
|
-
if (result.exitCode !== 0 || !result.stdout.trim())
|
|
13276
|
-
return [];
|
|
13277
|
-
return result.stdout.trim().split(`
|
|
13278
|
-
`).map((line) => {
|
|
13279
|
-
const [sha, message] = line.split("\x00");
|
|
13280
|
-
if (!sha)
|
|
13281
|
-
throw new Error("Git returned an invalid commit record");
|
|
13282
|
-
return { sha, ...message ? { message } : {} };
|
|
13283
|
-
});
|
|
13797
|
+
function expectedPreviewServiceUrls(sandboxId, deploymentId, stack, preview) {
|
|
13798
|
+
return Object.fromEntries(preview.services.flatMap((service) => {
|
|
13799
|
+
if (service.kind === "process" && !service.port)
|
|
13800
|
+
return [];
|
|
13801
|
+
const name = service.id.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
13802
|
+
const host = resolvedServiceDomain(sandboxId, deploymentId, service.id, preview);
|
|
13803
|
+
const configuredStableHosts = preview.stableDomains[service.id];
|
|
13804
|
+
const stableHost = (Array.isArray(configuredStableHosts) ? configuredStableHosts[0] : configuredStableHosts) ?? `${preview.deploymentType}-${stack}-${service.id}.${preview.baseDomain}`;
|
|
13805
|
+
return [
|
|
13806
|
+
[`SANDBLOCKS_SERVICE_${name}_URL`, `https://${host}`],
|
|
13807
|
+
[`SANDBLOCKS_STABLE_SERVICE_${name}_URL`, `https://${stableHost}`]
|
|
13808
|
+
];
|
|
13809
|
+
}));
|
|
13284
13810
|
}
|
|
13285
|
-
async function
|
|
13286
|
-
const
|
|
13287
|
-
|
|
13288
|
-
|
|
13811
|
+
async function managedServiceDomains(context, stack, preview, deploymentId) {
|
|
13812
|
+
const body = await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/routing-policies/${encodeURIComponent(stack)}/${encodeURIComponent(preview.deploymentType)}`);
|
|
13813
|
+
const domains = body.policy.domains ?? {};
|
|
13814
|
+
return Object.keys(domains).length ? domains : resolvedServiceDomains(context.state.sandboxId, deploymentId, preview);
|
|
13289
13815
|
}
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
env: hook.env,
|
|
13296
|
-
timeoutMs: hook.timeoutMs
|
|
13297
|
-
});
|
|
13298
|
-
if (result.exitCode !== 0)
|
|
13299
|
-
throw new Error(`host hook failed: ${result.stderr || result.stdout}`);
|
|
13300
|
-
}
|
|
13816
|
+
function resolvedServiceDomains(sandboxId, deploymentId, preview) {
|
|
13817
|
+
return Object.fromEntries(preview.services.map((service) => [
|
|
13818
|
+
service.id,
|
|
13819
|
+
resolvedServiceDomain(sandboxId, deploymentId, service.id, preview)
|
|
13820
|
+
]));
|
|
13301
13821
|
}
|
|
13302
|
-
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
env: hook.env,
|
|
13307
|
-
timeoutMs: hook.timeoutMs
|
|
13308
|
-
});
|
|
13309
|
-
if (result.exitCode !== 0)
|
|
13310
|
-
throw new Error(`sandbox hook failed: ${result.stderr || result.stdout}`);
|
|
13311
|
-
}
|
|
13822
|
+
function resolvedServiceDomain(sandboxId, deploymentId, service, preview) {
|
|
13823
|
+
const routeId = `${sandboxId.replaceAll("-", "").slice(0, 8)}-${createHash2("sha256").update(`${sandboxId}/${deploymentId}/${service}`).digest("hex").slice(0, 8)}`;
|
|
13824
|
+
const template = preview.domains[service] ?? `{route}.${preview.baseDomain}`;
|
|
13825
|
+
return template.replaceAll("{route}", routeId).replaceAll("{sandbox}", sandboxId.replaceAll("-", "").slice(0, 8)).replaceAll("{revision}", deploymentId.replaceAll("-", "").slice(0, 8)).replaceAll("{service}", service).toLowerCase();
|
|
13312
13826
|
}
|
|
13313
|
-
function
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13827
|
+
function serviceMemoryReservation(services) {
|
|
13828
|
+
return services.reduce((total, service) => {
|
|
13829
|
+
const match = /^(\d+)(m|g)$/i.exec(service.memory);
|
|
13830
|
+
if (!match)
|
|
13831
|
+
return total;
|
|
13832
|
+
const amount = Number(match[1]);
|
|
13833
|
+
return total + amount * (match[2]?.toLowerCase() === "g" ? 1024 ** 3 : 1024 ** 2);
|
|
13834
|
+
}, 0);
|
|
13321
13835
|
}
|
|
13322
|
-
function
|
|
13323
|
-
|
|
13324
|
-
|
|
13325
|
-
return
|
|
13326
|
-
const line = `${JSON.stringify(event)}
|
|
13327
|
-
`;
|
|
13328
|
-
if (config.type === "stdout")
|
|
13329
|
-
process.stdout.write(event.type === "text" ? event.text : event.type === "raw" ? `${event.line}
|
|
13330
|
-
` : line);
|
|
13331
|
-
if (config.type !== "stdout" && config.type !== "silent") {
|
|
13332
|
-
await mkdir4(dirname(file), { recursive: true });
|
|
13333
|
-
await appendFile(file, line);
|
|
13334
|
-
}
|
|
13335
|
-
try {
|
|
13336
|
-
await config.onAgentStreamEvent?.(event);
|
|
13337
|
-
} catch {}
|
|
13338
|
-
};
|
|
13836
|
+
function statePath(root, environment) {
|
|
13837
|
+
if (!/^[a-z][a-z0-9-]{0,62}$/.test(environment))
|
|
13838
|
+
throw new Error("sandbox environment is invalid");
|
|
13839
|
+
return path5.join(root, ".sandblocks", `sandbox-${environment}.json`);
|
|
13339
13840
|
}
|
|
13340
|
-
async function
|
|
13341
|
-
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
]);
|
|
13349
|
-
} finally {
|
|
13350
|
-
if (timer)
|
|
13351
|
-
clearTimeout(timer);
|
|
13352
|
-
}
|
|
13841
|
+
async function writeState(file, state) {
|
|
13842
|
+
await mkdir6(path5.dirname(file), { recursive: true, mode: 448 });
|
|
13843
|
+
await chmod3(path5.dirname(file), 448);
|
|
13844
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
13845
|
+
await Bun.write(temporary, `${JSON.stringify(state, null, 2)}
|
|
13846
|
+
`, { mode: 384 });
|
|
13847
|
+
await chmod3(temporary, 384);
|
|
13848
|
+
await rename(temporary, file);
|
|
13353
13849
|
}
|
|
13354
|
-
function
|
|
13355
|
-
|
|
13356
|
-
if (options.mounts?.length && !capabilities.bindMounts)
|
|
13357
|
-
throw new Error(`${provider.kind} provider does not support bind mounts`);
|
|
13358
|
-
if (options.network && !capabilities.networks)
|
|
13359
|
-
throw new Error(`${provider.kind} provider does not support network selection`);
|
|
13360
|
-
if (options.devices?.length && !capabilities.devices)
|
|
13361
|
-
throw new Error(`${provider.kind} provider does not support devices`);
|
|
13362
|
-
if ((options.retention === "always" || options.retention === "on-failure") && !capabilities.persistent)
|
|
13363
|
-
throw new Error(`${provider.kind} provider does not support retained sandboxes`);
|
|
13850
|
+
function redactedState(state) {
|
|
13851
|
+
return { ...state, leaseToken: "<redacted>" };
|
|
13364
13852
|
}
|
|
13365
|
-
function
|
|
13366
|
-
|
|
13853
|
+
function printOutput(operation) {
|
|
13854
|
+
if (operation.result?.stdout)
|
|
13855
|
+
process.stdout.write(String(operation.result.stdout));
|
|
13856
|
+
if (operation.result?.stderr)
|
|
13857
|
+
process.stderr.write(String(operation.result.stderr));
|
|
13858
|
+
if (Number(operation.result?.exitCode ?? 0) !== 0)
|
|
13859
|
+
throw new Error(`command exited ${operation.result.exitCode}`);
|
|
13367
13860
|
}
|
|
13368
|
-
async function initSdk(options = {}) {
|
|
13369
|
-
const cwd = resolve2(options.cwd ?? process.cwd());
|
|
13370
|
-
const root = resolve2(cwd, ".sandblocks");
|
|
13371
|
-
const files = {
|
|
13372
|
-
"main.ts": `import { ClaudeCodeAgent, createSandbox } from "@sandblocks/sdk";
|
|
13373
|
-
import { DockerProvider } from "@sandblocks/sdk/providers/docker";
|
|
13374
|
-
|
|
13375
|
-
await using sandbox = await createSandbox({
|
|
13376
|
-
provider: new DockerProvider({ image: "node:22-bookworm" }),
|
|
13377
|
-
cwd: process.cwd(),
|
|
13378
|
-
});
|
|
13379
|
-
|
|
13380
|
-
const result = await sandbox.run({
|
|
13381
|
-
agent: new ClaudeCodeAgent({ model: "claude-sonnet-4-6" }),
|
|
13382
|
-
promptFile: ".sandblocks/prompts/implement.md",
|
|
13383
|
-
logging: { type: "stdout" },
|
|
13384
|
-
});
|
|
13385
|
-
|
|
13386
|
-
console.log(result);
|
|
13387
|
-
`,
|
|
13388
|
-
"prompts/implement.md": `Implement the requested change, run the relevant checks, and commit the result.
|
|
13389
13861
|
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
CLAUDE_CODE_OAUTH_TOKEN=
|
|
13394
|
-
# ANTHROPIC_API_KEY=
|
|
13395
|
-
SANDBLOCKS_API_URL=
|
|
13396
|
-
SANDBLOCKS_API_KEY=
|
|
13397
|
-
`,
|
|
13398
|
-
"config.json": `${JSON.stringify({ provider: "docker", image: "node:22-bookworm", agent: "claude-code", model: "claude-sonnet-4-6", branchStrategy: "head" }, null, 2)}
|
|
13399
|
-
`
|
|
13400
|
-
};
|
|
13401
|
-
const written = [];
|
|
13402
|
-
for (const [relative, content] of Object.entries(files)) {
|
|
13403
|
-
const path6 = resolve2(root, relative);
|
|
13404
|
-
await mkdir22(resolve2(path6, ".."), { recursive: true });
|
|
13405
|
-
if (!options.force && await access2(path6).then(() => true).catch(() => false))
|
|
13406
|
-
continue;
|
|
13407
|
-
await writeFile2(path6, content, { mode: relative.includes(".env") ? 384 : 420 });
|
|
13408
|
-
written.push(path6);
|
|
13409
|
-
}
|
|
13410
|
-
return written;
|
|
13411
|
-
}
|
|
13862
|
+
// packages/libs/cli/src/sdk.ts
|
|
13863
|
+
import { mkdir as mkdir7, readFile as readFile7, rm as rm6, writeFile as writeFile4 } from "fs/promises";
|
|
13864
|
+
import { dirname as dirname2, resolve as resolve7 } from "path";
|
|
13412
13865
|
|
|
13413
|
-
//
|
|
13866
|
+
// packages/libs/sdk/dist/providers/docker.js
|
|
13414
13867
|
import { realpath as realpath3 } from "fs/promises";
|
|
13415
13868
|
import { resolve as resolve3 } from "path";
|
|
13416
13869
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -13656,7 +14109,7 @@ class DockerProvider extends OciProvider {
|
|
|
13656
14109
|
}
|
|
13657
14110
|
}
|
|
13658
14111
|
|
|
13659
|
-
//
|
|
14112
|
+
// packages/libs/sdk/dist/providers/podman.js
|
|
13660
14113
|
import { realpath as realpath4 } from "fs/promises";
|
|
13661
14114
|
import { resolve as resolve5 } from "path";
|
|
13662
14115
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -13902,7 +14355,7 @@ class PodmanProvider extends OciProvider2 {
|
|
|
13902
14355
|
}
|
|
13903
14356
|
}
|
|
13904
14357
|
|
|
13905
|
-
//
|
|
14358
|
+
// packages/libs/sdk/dist/providers/remote.js
|
|
13906
14359
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
13907
14360
|
import { mkdtemp, readFile as readFile6, rm as rm5, stat as stat2, writeFile as writeFile3 } from "fs/promises";
|
|
13908
14361
|
import { tmpdir } from "os";
|
|
@@ -14226,7 +14679,7 @@ async function sourceBundle(cwd) {
|
|
|
14226
14679
|
}
|
|
14227
14680
|
}
|
|
14228
14681
|
|
|
14229
|
-
//
|
|
14682
|
+
// packages/libs/sdk/dist/providers/unsafe-host.js
|
|
14230
14683
|
import { cp as cp2, realpath as realpath5 } from "fs/promises";
|
|
14231
14684
|
import { resolve as resolve6 } from "path";
|
|
14232
14685
|
import { spawn as spawn6 } from "child_process";
|
|
@@ -14335,7 +14788,7 @@ class UnsafeHostProvider {
|
|
|
14335
14788
|
}
|
|
14336
14789
|
}
|
|
14337
14790
|
|
|
14338
|
-
// src/sdk.ts
|
|
14791
|
+
// packages/libs/cli/src/sdk.ts
|
|
14339
14792
|
async function runSdkCommand(args, helpers) {
|
|
14340
14793
|
const [command = "help", ...rest] = args;
|
|
14341
14794
|
const parsed = helpers.parse(rest);
|
|
@@ -14351,7 +14804,7 @@ async function runSdkCommand(args, helpers) {
|
|
|
14351
14804
|
const provider = providerFrom(options, helpers, cwd);
|
|
14352
14805
|
const agent = agentFrom(options, helpers);
|
|
14353
14806
|
const outputTag = helpers.option(options, "output-tag");
|
|
14354
|
-
const result = await
|
|
14807
|
+
const result = await run({
|
|
14355
14808
|
provider,
|
|
14356
14809
|
cwd,
|
|
14357
14810
|
image: helpers.option(options, "image"),
|
|
@@ -14402,7 +14855,7 @@ async function sdkSandbox(args, helpers, defaultCwd) {
|
|
|
14402
14855
|
hooks: hooksFrom(options),
|
|
14403
14856
|
copyToWorktree: multi(options["copy-to-worktree"])
|
|
14404
14857
|
});
|
|
14405
|
-
await
|
|
14858
|
+
await mkdir7(dirname2(stateFile), { recursive: true });
|
|
14406
14859
|
const strategy = branchFrom(options, helpers);
|
|
14407
14860
|
const worktree = strategy.type === "worktree" || strategy.type === "merge-to-head" ? sandbox2.cwd : undefined;
|
|
14408
14861
|
await writeFile4(stateFile, JSON.stringify({
|
|
@@ -14649,14 +15102,14 @@ function parseMount(value) {
|
|
|
14649
15102
|
return { source, target, readonly: mode === "ro" };
|
|
14650
15103
|
}
|
|
14651
15104
|
|
|
14652
|
-
// src/cli.ts
|
|
15105
|
+
// packages/libs/cli/src/cli.ts
|
|
14653
15106
|
var HELP = `Sandblocks CLI
|
|
14654
15107
|
|
|
14655
15108
|
sandblocks configure [directory] [--name <stack>] [--force]
|
|
14656
15109
|
sandblocks validate [directory] [--config <path>] [--json]
|
|
14657
15110
|
sandblocks register [directory] --project <id> [--repository-url <url>]
|
|
14658
15111
|
[--api-url <url>] [--api-key <key>] [--json]
|
|
14659
|
-
sandblocks doctor [directory] [--api-url <url>]
|
|
15112
|
+
sandblocks doctor [directory] [--api-url <url>] [--explain-placement] [--project <id>] [--json]
|
|
14660
15113
|
sandblocks whoami [directory] [--api-url <url>] [--api-key <key>] [--json]
|
|
14661
15114
|
sandblocks hooks <install|status|uninstall> [directory]
|
|
14662
15115
|
sandblocks hooks run post-commit [directory]
|
|
@@ -14666,10 +15119,12 @@ var HELP = `Sandblocks CLI
|
|
|
14666
15119
|
sandblocks app <list|get|settings|settings-set> --project <id> [--stack <id>] [--app <id>] [--file <json>] [--json]
|
|
14667
15120
|
sandblocks secret <list|set|delete> --project <id> --stack <id> --app <id>
|
|
14668
15121
|
[--environment <id>] [--name <name>] [--value <value>] [--id <id>] [--json]
|
|
14669
|
-
sandblocks
|
|
15122
|
+
sandblocks artifact push <directory> --project <id> --stack <id> [--pool <pool>] [--json]
|
|
15123
|
+
sandblocks sandbox up [directory] [--environment <id>] [--project <id>] [--build-pool <pool>]
|
|
14670
15124
|
sandblocks sandbox create [directory] [--environment <id>] [--project <id>]
|
|
14671
|
-
sandblocks sandbox deploy [directory] [--environment <id>]
|
|
14672
|
-
|
|
15125
|
+
sandblocks sandbox deploy [directory] [--environment <id>] [--build-pool <pool>]
|
|
15126
|
+
[--artifact <image@sha256:digest>] [--deployment-id <uuid>] [--resume]
|
|
15127
|
+
sandblocks sandbox redeploy [directory] [--environment <id>] [--build-pool <pool>]
|
|
14673
15128
|
sandblocks sandbox status [directory] [--environment <id>] [--json]
|
|
14674
15129
|
sandblocks sandbox promote --project <id> --sandbox <id> [--json]
|
|
14675
15130
|
sandblocks sandbox agent <run|list|get|cancel|redeploy> --project <id> --sandbox <id>
|
|
@@ -14736,6 +15191,8 @@ async function run3(argv = process.argv.slice(2)) {
|
|
|
14736
15191
|
await resource(command, args);
|
|
14737
15192
|
else if (command === "secret")
|
|
14738
15193
|
await secret(args);
|
|
15194
|
+
else if (command === "artifact")
|
|
15195
|
+
await artifact(args);
|
|
14739
15196
|
else if (command === "sandbox")
|
|
14740
15197
|
await sandbox(args);
|
|
14741
15198
|
else if (command === "sdk")
|
|
@@ -14836,6 +15293,38 @@ async function doctor(args) {
|
|
|
14836
15293
|
if (!response.ok)
|
|
14837
15294
|
throw new Error(`API health failed (${response.status})`);
|
|
14838
15295
|
console.log(`api ok (${apiUrl})`);
|
|
15296
|
+
if (options["explain-placement"]) {
|
|
15297
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
15298
|
+
const projectId = option2(options, "project") ?? process.env.SANDBLOCKS_PROJECT_ID;
|
|
15299
|
+
if (!apiKey || !projectId)
|
|
15300
|
+
throw new Error("doctor --explain-placement requires a project and Sandblocks API key");
|
|
15301
|
+
const checks = [
|
|
15302
|
+
{ label: "source import", kind: "workspace.import", role: "build", memory: 1024 ** 3 },
|
|
15303
|
+
{ label: "artifact publish", kind: "workspace.artifact.publish", role: "build", memory: 1024 ** 3 },
|
|
15304
|
+
{ label: "service deploy", kind: "workspace.service.deploy", role: "runtime", memory: 6 * 1024 ** 3 }
|
|
15305
|
+
];
|
|
15306
|
+
const explanations = await Promise.all(checks.map(async (check) => {
|
|
15307
|
+
const query = new URLSearchParams({
|
|
15308
|
+
kind: check.kind,
|
|
15309
|
+
role: check.role,
|
|
15310
|
+
memoryReservationBytes: String(check.memory)
|
|
15311
|
+
});
|
|
15312
|
+
const result = await sandblocksRequest(apiUrl, apiKey, `/v1/projects/${encodeURIComponent(projectId)}/placement/explain?${query}`);
|
|
15313
|
+
return { ...check, explanation: result.explanation };
|
|
15314
|
+
}));
|
|
15315
|
+
if (options.json)
|
|
15316
|
+
console.log(JSON.stringify({ projectId, explanations }, null, 2));
|
|
15317
|
+
else
|
|
15318
|
+
for (const check of explanations) {
|
|
15319
|
+
const explanation = objectRecord(check.explanation);
|
|
15320
|
+
console.log(`${check.label.padEnd(16)} ${explanation.eligible ? "eligible" : "blocked"}`);
|
|
15321
|
+
if (!explanation.eligible && Array.isArray(explanation.candidates))
|
|
15322
|
+
for (const candidate of explanation.candidates) {
|
|
15323
|
+
const row = objectRecord(candidate);
|
|
15324
|
+
console.log(` ${row.hostName ?? "unknown"}/${row.workerName ?? "unknown"}: ${row.reasons?.join(", ") || "rejected"}`);
|
|
15325
|
+
}
|
|
15326
|
+
}
|
|
15327
|
+
}
|
|
14839
15328
|
}
|
|
14840
15329
|
const gitDetected = await stat3(path6.join(root, ".git")).then(() => true).catch(() => false);
|
|
14841
15330
|
console.log(`git ${gitDetected ? "ok" : "not detected"}`);
|
|
@@ -14951,6 +15440,45 @@ async function secret(args) {
|
|
|
14951
15440
|
}
|
|
14952
15441
|
}
|
|
14953
15442
|
}
|
|
15443
|
+
async function artifact(args) {
|
|
15444
|
+
const [action = "push", ...rest] = args;
|
|
15445
|
+
if (action !== "push")
|
|
15446
|
+
throw new Error(`unsupported artifact command '${action}'`);
|
|
15447
|
+
const options = parse2(rest);
|
|
15448
|
+
const root = path6.resolve(options.positionals[0] ?? process.cwd());
|
|
15449
|
+
const projectId = option2(options, "project") ?? process.env.SANDBLOCKS_PROJECT_ID;
|
|
15450
|
+
const stack = option2(options, "stack");
|
|
15451
|
+
const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
|
|
15452
|
+
const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
|
|
15453
|
+
if (!projectId || !stack)
|
|
15454
|
+
throw new Error("artifact push requires --project and --stack");
|
|
15455
|
+
if (!apiUrl || !apiKey)
|
|
15456
|
+
throw new Error("artifact push requires Sandblocks API URL and key");
|
|
15457
|
+
const deploymentId = option2(options, "deployment-id") ?? randomUUID4();
|
|
15458
|
+
if (!/^[a-f0-9-]{36}$/.test(deploymentId))
|
|
15459
|
+
throw new Error("artifact deployment ID must be a UUID");
|
|
15460
|
+
const workspaceId = option2(options, "workspace") ?? `prebuilt-${deploymentId.slice(0, 8)}`;
|
|
15461
|
+
const bundle = await createPrebuiltTar(root);
|
|
15462
|
+
const client2 = new SandblocksClient({ baseUrl: apiUrl, apiKey });
|
|
15463
|
+
const result = await client2.uploadPrebuiltArtifact({
|
|
15464
|
+
projectId,
|
|
15465
|
+
workspaceId,
|
|
15466
|
+
deploymentId,
|
|
15467
|
+
stack,
|
|
15468
|
+
bundle,
|
|
15469
|
+
pool: option2(options, "pool") ?? "build-fleet",
|
|
15470
|
+
workspaceImage: option2(options, "image")
|
|
15471
|
+
});
|
|
15472
|
+
if (options.json)
|
|
15473
|
+
console.log(JSON.stringify(result, null, 2));
|
|
15474
|
+
else {
|
|
15475
|
+
console.log(`artifact ${result.image}`);
|
|
15476
|
+
console.log(`deployment ${result.deploymentId}`);
|
|
15477
|
+
console.log(`workspace ${result.workspaceId}`);
|
|
15478
|
+
console.log(`bytes ${result.bytes}`);
|
|
15479
|
+
console.log(`deploy with: sandblocks sandbox deploy . --artifact ${result.image} --deployment-id ${result.deploymentId}`);
|
|
15480
|
+
}
|
|
15481
|
+
}
|
|
14954
15482
|
function printResources(kind, action, body) {
|
|
14955
15483
|
if (action === "settings") {
|
|
14956
15484
|
console.log(JSON.stringify(body.settings ?? {}, null, 2));
|
|
@@ -15120,6 +15648,51 @@ async function sandboxPromote(args) {
|
|
|
15120
15648
|
console.log(`promoted ${sandboxId} to production`);
|
|
15121
15649
|
}
|
|
15122
15650
|
var sourceFileCount = 0;
|
|
15651
|
+
async function createPrebuiltTar(root) {
|
|
15652
|
+
const names = [];
|
|
15653
|
+
const visit2 = async (directory, prefix = "") => {
|
|
15654
|
+
for (const entry of await readdir2(directory, { withFileTypes: true })) {
|
|
15655
|
+
if (!prefix && [".git", ".sandblocks"].includes(entry.name))
|
|
15656
|
+
continue;
|
|
15657
|
+
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15658
|
+
const absolute = path6.join(directory, entry.name);
|
|
15659
|
+
if (entry.isSymbolicLink())
|
|
15660
|
+
throw new Error(`prebuilt artifact symlinks are not supported: ${relative}`);
|
|
15661
|
+
if (entry.isDirectory())
|
|
15662
|
+
await visit2(absolute, relative);
|
|
15663
|
+
else if (entry.isFile())
|
|
15664
|
+
names.push(relative);
|
|
15665
|
+
if (names.length > 20000)
|
|
15666
|
+
throw new Error("prebuilt artifact contains more than 20000 files");
|
|
15667
|
+
}
|
|
15668
|
+
};
|
|
15669
|
+
await visit2(root);
|
|
15670
|
+
if (!names.length)
|
|
15671
|
+
throw new Error("prebuilt artifact directory has no files");
|
|
15672
|
+
const chunks = [];
|
|
15673
|
+
let total = 1024;
|
|
15674
|
+
for (const name of names.sort()) {
|
|
15675
|
+
if (name.includes("\\") || name.startsWith("/") || name.split("/").includes(".."))
|
|
15676
|
+
throw new Error(`prebuilt artifact path is unsafe: ${name}`);
|
|
15677
|
+
const file = path6.join(root, name);
|
|
15678
|
+
const info = await lstat(file);
|
|
15679
|
+
const contents = new Uint8Array(await readFile8(file));
|
|
15680
|
+
const header = tarHeader(name, contents.byteLength, info.mode, Math.floor(info.mtimeMs / 1000));
|
|
15681
|
+
const padding = (512 - contents.byteLength % 512) % 512;
|
|
15682
|
+
total += 512 + contents.byteLength + padding;
|
|
15683
|
+
if (total > 256 * 1024 * 1024)
|
|
15684
|
+
throw new Error("prebuilt artifact exceeds 256 MiB");
|
|
15685
|
+
chunks.push(header, contents, new Uint8Array(padding));
|
|
15686
|
+
}
|
|
15687
|
+
chunks.push(new Uint8Array(1024));
|
|
15688
|
+
const output = new Uint8Array(total);
|
|
15689
|
+
let offset = 0;
|
|
15690
|
+
for (const chunk of chunks) {
|
|
15691
|
+
output.set(chunk, offset);
|
|
15692
|
+
offset += chunk.byteLength;
|
|
15693
|
+
}
|
|
15694
|
+
return output;
|
|
15695
|
+
}
|
|
15123
15696
|
async function createSourceTar(root) {
|
|
15124
15697
|
const process2 = Bun.spawn(["git", "ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
15125
15698
|
cwd: root,
|
|
@@ -15430,4 +16003,4 @@ export {
|
|
|
15430
16003
|
parse2 as parse
|
|
15431
16004
|
};
|
|
15432
16005
|
|
|
15433
|
-
//# debugId=
|
|
16006
|
+
//# debugId=E7F4E37BF2F49E8E64756E2164756E21
|