@mastra/docker 0.11.1-alpha.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/index.cjs +350 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +348 -16
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +8 -0
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/template/build-progress.d.ts +92 -0
- package/dist/template/build-progress.d.ts.map +1 -0
- package/dist/template/build-session.d.ts.map +1 -1
- package/dist/template/index.d.ts +1 -0
- package/dist/template/index.d.ts.map +1 -1
- package/dist/template/template.d.ts +32 -2
- package/dist/template/template.d.ts.map +1 -1
- package/package.json +8 -9
package/dist/index.js
CHANGED
|
@@ -582,6 +582,7 @@ var DockerSandbox = class DockerSandbox extends MastraSandbox {
|
|
|
582
582
|
/** Image the container boots from; rewritten by a template resolution on `start()`. */
|
|
583
583
|
_image;
|
|
584
584
|
_templateSpec;
|
|
585
|
+
_onTemplateBuildProgress;
|
|
585
586
|
_workingDirectoryWasSet;
|
|
586
587
|
_command;
|
|
587
588
|
_env;
|
|
@@ -625,6 +626,7 @@ var DockerSandbox = class DockerSandbox extends MastraSandbox {
|
|
|
625
626
|
this._containerName = sanitizeContainerName(options.name ?? this.id);
|
|
626
627
|
if (options.image !== void 0 && options.template !== void 0) throw new TypeError("DockerSandbox: `image` and `template` are mutually exclusive");
|
|
627
628
|
this._templateSpec = options.template;
|
|
629
|
+
this._onTemplateBuildProgress = options.onTemplateBuildProgress;
|
|
628
630
|
this._image = options.image ?? "node:22-slim";
|
|
629
631
|
this._workingDirectoryWasSet = options.workingDirectory !== void 0 || options.workingDir !== void 0;
|
|
630
632
|
this._command = options.command ?? ["sleep", "infinity"];
|
|
@@ -984,7 +986,8 @@ var DockerSandbox = class DockerSandbox extends MastraSandbox {
|
|
|
984
986
|
throwIfAborted(abortSignal, "start Docker sandbox");
|
|
985
987
|
const result = await template.build({
|
|
986
988
|
docker: this._docker,
|
|
987
|
-
abortSignal
|
|
989
|
+
abortSignal,
|
|
990
|
+
...this._onTemplateBuildProgress && { onProgress: this._onTemplateBuildProgress }
|
|
988
991
|
});
|
|
989
992
|
if (result.status !== "ready") throw new SandboxError(`Docker template build failed: ${result.error ?? "unknown error"}`, "START_FAILED", {
|
|
990
993
|
templateId: result.templateId,
|
|
@@ -1337,6 +1340,287 @@ const dockerSandboxProvider = {
|
|
|
1337
1340
|
createSandbox: (config) => new DockerSandbox(config)
|
|
1338
1341
|
};
|
|
1339
1342
|
//#endregion
|
|
1343
|
+
//#region src/template/build-progress.ts
|
|
1344
|
+
/**
|
|
1345
|
+
* Build progress for `DockerTemplate.build()`.
|
|
1346
|
+
*
|
|
1347
|
+
* The daemon reports build progress as newline-delimited JSON. The legacy
|
|
1348
|
+
* builder sends plain text (`{stream: "Step 2/5 : RUN ..."}`); BuildKit sends
|
|
1349
|
+
* `{id: 'moby.buildkit.trace', aux: <base64 StatusResponse protobuf>}`. This
|
|
1350
|
+
* module normalizes both into {@link DockerTemplateBuildEvent}s and keeps a
|
|
1351
|
+
* bounded tail of raw output for failure diagnostics.
|
|
1352
|
+
*/
|
|
1353
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 65536;
|
|
1354
|
+
/** Keeps at most `maxBytes` (UTF-8) of the most recent text. */
|
|
1355
|
+
var BoundedTail = class {
|
|
1356
|
+
#maxBytes;
|
|
1357
|
+
#chunks = [];
|
|
1358
|
+
#bytes = 0;
|
|
1359
|
+
#truncated = false;
|
|
1360
|
+
constructor(maxBytes) {
|
|
1361
|
+
this.#maxBytes = maxBytes;
|
|
1362
|
+
}
|
|
1363
|
+
get truncated() {
|
|
1364
|
+
return this.#truncated;
|
|
1365
|
+
}
|
|
1366
|
+
push(text) {
|
|
1367
|
+
if (text.length === 0) return;
|
|
1368
|
+
let chunk = Buffer.from(text, "utf8");
|
|
1369
|
+
if (chunk.length > this.#maxBytes) {
|
|
1370
|
+
chunk = chunk.subarray(chunk.length - this.#maxBytes);
|
|
1371
|
+
this.#chunks = [];
|
|
1372
|
+
this.#bytes = 0;
|
|
1373
|
+
this.#truncated = true;
|
|
1374
|
+
}
|
|
1375
|
+
this.#chunks.push(chunk);
|
|
1376
|
+
this.#bytes += chunk.length;
|
|
1377
|
+
while (this.#bytes > this.#maxBytes) {
|
|
1378
|
+
const head = this.#chunks[0];
|
|
1379
|
+
const excess = this.#bytes - this.#maxBytes;
|
|
1380
|
+
this.#truncated = true;
|
|
1381
|
+
if (head.length <= excess) {
|
|
1382
|
+
this.#chunks.shift();
|
|
1383
|
+
this.#bytes -= head.length;
|
|
1384
|
+
} else {
|
|
1385
|
+
this.#chunks[0] = head.subarray(excess);
|
|
1386
|
+
this.#bytes -= excess;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
toString() {
|
|
1391
|
+
let text = Buffer.concat(this.#chunks).toString("utf8");
|
|
1392
|
+
if (this.#truncated) text = text.replace(/^\uFFFD+/, "");
|
|
1393
|
+
return text;
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
function readFields(buffer) {
|
|
1397
|
+
const fields = [];
|
|
1398
|
+
let offset = 0;
|
|
1399
|
+
const readVarint = () => {
|
|
1400
|
+
let result = 0;
|
|
1401
|
+
let shift = 0;
|
|
1402
|
+
for (;;) {
|
|
1403
|
+
if (offset >= buffer.length) throw new Error("truncated varint");
|
|
1404
|
+
const byte = buffer[offset++];
|
|
1405
|
+
result += (byte & 127) * 2 ** shift;
|
|
1406
|
+
if ((byte & 128) === 0) return result;
|
|
1407
|
+
shift += 7;
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
while (offset < buffer.length) {
|
|
1411
|
+
const key = readVarint();
|
|
1412
|
+
const field = Math.floor(key / 8);
|
|
1413
|
+
const wireType = key % 8;
|
|
1414
|
+
switch (wireType) {
|
|
1415
|
+
case 0:
|
|
1416
|
+
fields.push({
|
|
1417
|
+
field,
|
|
1418
|
+
value: readVarint()
|
|
1419
|
+
});
|
|
1420
|
+
break;
|
|
1421
|
+
case 1:
|
|
1422
|
+
offset += 8;
|
|
1423
|
+
break;
|
|
1424
|
+
case 5:
|
|
1425
|
+
offset += 4;
|
|
1426
|
+
break;
|
|
1427
|
+
case 2: {
|
|
1428
|
+
const length = readVarint();
|
|
1429
|
+
if (offset + length > buffer.length) throw new Error("truncated field");
|
|
1430
|
+
fields.push({
|
|
1431
|
+
field,
|
|
1432
|
+
value: buffer.subarray(offset, offset + length)
|
|
1433
|
+
});
|
|
1434
|
+
offset += length;
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
default: throw new Error(`unsupported protobuf wire type ${wireType}`);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return fields;
|
|
1441
|
+
}
|
|
1442
|
+
/** @internal exported for tests. Throws on malformed input. */
|
|
1443
|
+
function decodeBuildkitStatus(aux) {
|
|
1444
|
+
const vertexes = [];
|
|
1445
|
+
const logs = [];
|
|
1446
|
+
for (const { field, value } of readFields(Buffer.from(aux, "base64"))) {
|
|
1447
|
+
if (!Buffer.isBuffer(value)) continue;
|
|
1448
|
+
if (field === 1) {
|
|
1449
|
+
const vertex = {
|
|
1450
|
+
digest: "",
|
|
1451
|
+
cached: false,
|
|
1452
|
+
started: false,
|
|
1453
|
+
completed: false
|
|
1454
|
+
};
|
|
1455
|
+
for (const f of readFields(value)) if (f.field === 1 && Buffer.isBuffer(f.value)) vertex.digest = f.value.toString("utf8");
|
|
1456
|
+
else if (f.field === 3 && Buffer.isBuffer(f.value)) vertex.name = f.value.toString("utf8");
|
|
1457
|
+
else if (f.field === 4) vertex.cached = f.value !== 0;
|
|
1458
|
+
else if (f.field === 5) vertex.started = true;
|
|
1459
|
+
else if (f.field === 6) vertex.completed = true;
|
|
1460
|
+
else if (f.field === 7 && Buffer.isBuffer(f.value) && f.value.length > 0) vertex.error = f.value.toString("utf8");
|
|
1461
|
+
if (vertex.digest) vertexes.push(vertex);
|
|
1462
|
+
} else if (field === 3) {
|
|
1463
|
+
let vertex = "";
|
|
1464
|
+
let data = Buffer.alloc(0);
|
|
1465
|
+
for (const f of readFields(value)) if (f.field === 1 && Buffer.isBuffer(f.value)) vertex = f.value.toString("utf8");
|
|
1466
|
+
else if (f.field === 4 && Buffer.isBuffer(f.value)) data = f.value;
|
|
1467
|
+
logs.push({
|
|
1468
|
+
vertex,
|
|
1469
|
+
data
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return {
|
|
1474
|
+
vertexes,
|
|
1475
|
+
logs
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
const LEGACY_STEP = /^Step (\d+)\/(\d+) : (.*)$/;
|
|
1479
|
+
function createBuildEventNormalizer(emit, tail) {
|
|
1480
|
+
let failure;
|
|
1481
|
+
let failedStep;
|
|
1482
|
+
let legacyStep;
|
|
1483
|
+
let legacyCached;
|
|
1484
|
+
let pending = "";
|
|
1485
|
+
const vertexes = /* @__PURE__ */ new Map();
|
|
1486
|
+
const decoders = /* @__PURE__ */ new Map();
|
|
1487
|
+
const output = (text, stepId) => {
|
|
1488
|
+
if (!text) return;
|
|
1489
|
+
tail.push(text);
|
|
1490
|
+
emit({
|
|
1491
|
+
type: "output",
|
|
1492
|
+
...stepId !== void 0 && { stepId },
|
|
1493
|
+
text
|
|
1494
|
+
});
|
|
1495
|
+
};
|
|
1496
|
+
const completeLegacyStep = (error) => {
|
|
1497
|
+
if (!legacyStep) return;
|
|
1498
|
+
const step = {
|
|
1499
|
+
...legacyStep,
|
|
1500
|
+
...legacyCached !== void 0 && { cached: legacyCached }
|
|
1501
|
+
};
|
|
1502
|
+
if (error !== void 0) failedStep = step;
|
|
1503
|
+
emit({
|
|
1504
|
+
type: "step-complete",
|
|
1505
|
+
step,
|
|
1506
|
+
...error !== void 0 && { error }
|
|
1507
|
+
});
|
|
1508
|
+
legacyStep = void 0;
|
|
1509
|
+
legacyCached = void 0;
|
|
1510
|
+
};
|
|
1511
|
+
const handleLegacyLine = (line) => {
|
|
1512
|
+
const match = LEGACY_STEP.exec(line.replace(/\r?\n$/, ""));
|
|
1513
|
+
if (match) {
|
|
1514
|
+
completeLegacyStep();
|
|
1515
|
+
legacyStep = {
|
|
1516
|
+
id: match[1],
|
|
1517
|
+
name: match[3],
|
|
1518
|
+
index: Number(match[1]),
|
|
1519
|
+
total: Number(match[2])
|
|
1520
|
+
};
|
|
1521
|
+
emit({
|
|
1522
|
+
type: "step-start",
|
|
1523
|
+
step: { ...legacyStep }
|
|
1524
|
+
});
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
if (legacyStep) {
|
|
1528
|
+
const trimmed = line.trim();
|
|
1529
|
+
if (trimmed === "---> Using cache") legacyCached = true;
|
|
1530
|
+
else if (trimmed.startsWith("---> Running in ") && legacyCached === void 0) legacyCached = false;
|
|
1531
|
+
}
|
|
1532
|
+
output(line, legacyStep?.id);
|
|
1533
|
+
};
|
|
1534
|
+
const handleLegacyStream = (text) => {
|
|
1535
|
+
pending += text;
|
|
1536
|
+
let newline = pending.indexOf("\n");
|
|
1537
|
+
while (newline !== -1) {
|
|
1538
|
+
handleLegacyLine(pending.slice(0, newline + 1));
|
|
1539
|
+
pending = pending.slice(newline + 1);
|
|
1540
|
+
newline = pending.indexOf("\n");
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
const flushLegacy = () => {
|
|
1544
|
+
if (pending) {
|
|
1545
|
+
handleLegacyLine(pending);
|
|
1546
|
+
pending = "";
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
const handleBuildkit = (aux) => {
|
|
1550
|
+
const { vertexes: updates, logs } = decodeBuildkitStatus(aux);
|
|
1551
|
+
for (const update of updates) {
|
|
1552
|
+
let state = vertexes.get(update.digest);
|
|
1553
|
+
if (!state) {
|
|
1554
|
+
state = {
|
|
1555
|
+
step: {
|
|
1556
|
+
id: update.digest,
|
|
1557
|
+
name: update.name ?? ""
|
|
1558
|
+
},
|
|
1559
|
+
started: false,
|
|
1560
|
+
completed: false
|
|
1561
|
+
};
|
|
1562
|
+
vertexes.set(update.digest, state);
|
|
1563
|
+
}
|
|
1564
|
+
if (update.name && !state.step.name) state.step.name = update.name;
|
|
1565
|
+
if (update.cached) state.step.cached = true;
|
|
1566
|
+
if (state.step.name.startsWith("[internal]")) continue;
|
|
1567
|
+
if (!state.started && (update.started || update.completed || update.cached || update.error)) {
|
|
1568
|
+
state.started = true;
|
|
1569
|
+
emit({
|
|
1570
|
+
type: "step-start",
|
|
1571
|
+
step: { ...state.step }
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
if (!state.completed && (update.completed || update.error)) {
|
|
1575
|
+
state.completed = true;
|
|
1576
|
+
if (state.step.cached === void 0) state.step.cached = false;
|
|
1577
|
+
const step = { ...state.step };
|
|
1578
|
+
if (update.error) failedStep = step;
|
|
1579
|
+
emit({
|
|
1580
|
+
type: "step-complete",
|
|
1581
|
+
step,
|
|
1582
|
+
...update.error && { error: update.error }
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
for (const log of logs) {
|
|
1587
|
+
if (vertexes.get(log.vertex)?.step.name.startsWith("[internal]")) continue;
|
|
1588
|
+
let decoder = decoders.get(log.vertex);
|
|
1589
|
+
if (!decoder) {
|
|
1590
|
+
decoder = new StringDecoder("utf8");
|
|
1591
|
+
decoders.set(log.vertex, decoder);
|
|
1592
|
+
}
|
|
1593
|
+
output(decoder.write(log.data), log.vertex || void 0);
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
const handleError = (raw) => {
|
|
1597
|
+
const detail = raw.errorDetail;
|
|
1598
|
+
const message = String(detail?.message ?? raw.error ?? "docker build failed");
|
|
1599
|
+
flushLegacy();
|
|
1600
|
+
completeLegacyStep(message);
|
|
1601
|
+
if (!failure) failure = {
|
|
1602
|
+
message,
|
|
1603
|
+
...failedStep && { step: failedStep }
|
|
1604
|
+
};
|
|
1605
|
+
};
|
|
1606
|
+
return {
|
|
1607
|
+
handle(raw) {
|
|
1608
|
+
if (!raw || typeof raw !== "object") return;
|
|
1609
|
+
const event = raw;
|
|
1610
|
+
try {
|
|
1611
|
+
if (event.error !== void 0 || event.errorDetail !== void 0) handleError(event);
|
|
1612
|
+
else if (event.id === "moby.buildkit.trace" && typeof event.aux === "string") handleBuildkit(event.aux);
|
|
1613
|
+
else if (typeof event.stream === "string") handleLegacyStream(event.stream);
|
|
1614
|
+
} catch {}
|
|
1615
|
+
},
|
|
1616
|
+
finish() {
|
|
1617
|
+
flushLegacy();
|
|
1618
|
+
completeLegacyStep();
|
|
1619
|
+
},
|
|
1620
|
+
failure: () => failure
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
//#endregion
|
|
1340
1624
|
//#region src/template/build-session.ts
|
|
1341
1625
|
/**
|
|
1342
1626
|
* BuildKit session that serves build secrets to the daemon.
|
|
@@ -1618,9 +1902,7 @@ function synthesizeDockerfile(definition) {
|
|
|
1618
1902
|
case "pipInstall":
|
|
1619
1903
|
lines.push(renderPipInstall(operation.args[0], operation.args[1]));
|
|
1620
1904
|
break;
|
|
1621
|
-
case "npmInstall":
|
|
1622
|
-
lines.push(renderNpmInstall(operation.args[0], operation.args[1]));
|
|
1623
|
-
break;
|
|
1905
|
+
case "npmInstall": lines.push(renderNpmInstall(operation.args[0], operation.args[1]));
|
|
1624
1906
|
}
|
|
1625
1907
|
});
|
|
1626
1908
|
return `${lines.join("\n")}\n`;
|
|
@@ -1705,7 +1987,7 @@ function templateImageTag(definition) {
|
|
|
1705
1987
|
* ```
|
|
1706
1988
|
*/
|
|
1707
1989
|
const MAX_OPERATIONS = 256;
|
|
1708
|
-
const MAX_STRING_LENGTH =
|
|
1990
|
+
const MAX_STRING_LENGTH = 32768;
|
|
1709
1991
|
const MAX_COLLECTION_ITEMS = 512;
|
|
1710
1992
|
/**
|
|
1711
1993
|
* Immutable, chainable builder + build/lifecycle for a local Docker template.
|
|
@@ -1873,20 +2155,29 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1873
2155
|
* `options.secrets`, the template's `secrets` option, or `process.env`.
|
|
1874
2156
|
*/
|
|
1875
2157
|
async build(options = {}) {
|
|
2158
|
+
const { maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES } = options;
|
|
2159
|
+
if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes <= 0) throw new RangeError("maxOutputBytes must be a positive integer");
|
|
1876
2160
|
throwIfAborted(options.abortSignal, "build Docker template");
|
|
1877
2161
|
const isPlain = !options.force && options.secrets === void 0 && options.docker === void 0;
|
|
1878
|
-
if (isPlain && this.#plainBuild) return this.#waitForBuild(this.#plainBuild, options.abortSignal);
|
|
2162
|
+
if (isPlain && this.#plainBuild) return this.#waitForBuild(this.#plainBuild, options.abortSignal, options.onProgress);
|
|
1879
2163
|
const previous = this.#queueTail?.promise.catch(() => void 0) ?? Promise.resolve();
|
|
1880
2164
|
const controller = new AbortController();
|
|
2165
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2166
|
+
const emit = (event) => {
|
|
2167
|
+
for (const listener of [...listeners]) try {
|
|
2168
|
+
listener(event);
|
|
2169
|
+
} catch {}
|
|
2170
|
+
};
|
|
1881
2171
|
const run = {
|
|
1882
2172
|
controller,
|
|
1883
2173
|
waiters: 0,
|
|
2174
|
+
listeners,
|
|
1884
2175
|
promise: previous.then(() => {
|
|
1885
2176
|
throwIfAborted(controller.signal, "build Docker template");
|
|
1886
2177
|
return this.#build({
|
|
1887
2178
|
...options,
|
|
1888
2179
|
abortSignal: controller.signal
|
|
1889
|
-
});
|
|
2180
|
+
}, emit, maxOutputBytes);
|
|
1890
2181
|
})
|
|
1891
2182
|
};
|
|
1892
2183
|
this.#queueTail = run;
|
|
@@ -1895,20 +2186,23 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1895
2186
|
if (this.#queueTail === run) this.#queueTail = void 0;
|
|
1896
2187
|
if (this.#plainBuild === run) this.#plainBuild = void 0;
|
|
1897
2188
|
}).catch(() => void 0);
|
|
1898
|
-
return this.#waitForBuild(run, options.abortSignal);
|
|
2189
|
+
return this.#waitForBuild(run, options.abortSignal, options.onProgress);
|
|
1899
2190
|
}
|
|
1900
|
-
#waitForBuild(run, abortSignal) {
|
|
2191
|
+
#waitForBuild(run, abortSignal, onProgress) {
|
|
1901
2192
|
run.waiters += 1;
|
|
2193
|
+
const listener = onProgress && ((event) => onProgress(event));
|
|
2194
|
+
if (listener) run.listeners.add(listener);
|
|
1902
2195
|
let settled = false;
|
|
1903
2196
|
const release = (aborted) => {
|
|
1904
2197
|
if (settled) return;
|
|
1905
2198
|
settled = true;
|
|
2199
|
+
if (listener) run.listeners.delete(listener);
|
|
1906
2200
|
run.waiters -= 1;
|
|
1907
2201
|
if (aborted && run.waiters === 0) run.controller.abort();
|
|
1908
2202
|
};
|
|
1909
2203
|
return waitForAbortable(run.promise, abortSignal, "build Docker template", () => release(true)).finally(() => release(false));
|
|
1910
2204
|
}
|
|
1911
|
-
async #build(options) {
|
|
2205
|
+
async #build(options, emit, maxOutputBytes) {
|
|
1912
2206
|
const docker = options.docker ?? this.#getDocker();
|
|
1913
2207
|
const tag = this.templateId;
|
|
1914
2208
|
const { abortSignal } = options;
|
|
@@ -1917,6 +2211,10 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1917
2211
|
await docker.getImage(tag).inspect();
|
|
1918
2212
|
throwIfAborted(abortSignal, "build Docker template");
|
|
1919
2213
|
this.#built = true;
|
|
2214
|
+
emit({
|
|
2215
|
+
type: "reused",
|
|
2216
|
+
templateId: tag
|
|
2217
|
+
});
|
|
1920
2218
|
return {
|
|
1921
2219
|
status: "ready",
|
|
1922
2220
|
templateId: tag
|
|
@@ -1929,12 +2227,19 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1929
2227
|
const context = pack();
|
|
1930
2228
|
context.entry({ name: "Dockerfile" }, this.dockerfile);
|
|
1931
2229
|
context.finalize();
|
|
2230
|
+
const tail = new BoundedTail(maxOutputBytes);
|
|
2231
|
+
const normalizer = createBuildEventNormalizer(emit, tail);
|
|
2232
|
+
emit({
|
|
2233
|
+
type: "build-start",
|
|
2234
|
+
templateId: tag,
|
|
2235
|
+
builder: secrets ? "buildkit" : "legacy"
|
|
2236
|
+
});
|
|
1932
2237
|
try {
|
|
1933
2238
|
const nocache = options.force === true;
|
|
1934
2239
|
if (secrets) {
|
|
1935
2240
|
const { stream, session } = await this.#buildWithSecrets(docker, context, tag, secrets, nocache, abortSignal);
|
|
1936
2241
|
try {
|
|
1937
|
-
await this.#followBuild(docker, stream, abortSignal);
|
|
2242
|
+
await this.#followBuild(docker, stream, normalizer, abortSignal);
|
|
1938
2243
|
} finally {
|
|
1939
2244
|
session.close();
|
|
1940
2245
|
}
|
|
@@ -1952,18 +2257,31 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1952
2257
|
}), abortSignal, "build Docker template", () => {
|
|
1953
2258
|
context.destroy?.();
|
|
1954
2259
|
});
|
|
1955
|
-
await this.#followBuild(docker, stream, abortSignal);
|
|
2260
|
+
await this.#followBuild(docker, stream, normalizer, abortSignal);
|
|
1956
2261
|
}
|
|
1957
2262
|
} catch (error) {
|
|
1958
2263
|
this.#built = false;
|
|
1959
2264
|
if (abortSignal?.aborted) throw normalizeAbortError(error, "build Docker template");
|
|
2265
|
+
emit({
|
|
2266
|
+
type: "build-complete",
|
|
2267
|
+
status: "failed"
|
|
2268
|
+
});
|
|
2269
|
+
const failedStep = normalizer.failure()?.step;
|
|
1960
2270
|
return {
|
|
1961
2271
|
status: "failed",
|
|
1962
2272
|
templateId: tag,
|
|
1963
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2273
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2274
|
+
...failedStep && { failedStep },
|
|
2275
|
+
output: tail.toString(),
|
|
2276
|
+
outputTruncated: tail.truncated
|
|
1964
2277
|
};
|
|
1965
2278
|
}
|
|
2279
|
+
normalizer.finish();
|
|
1966
2280
|
this.#built = true;
|
|
2281
|
+
emit({
|
|
2282
|
+
type: "build-complete",
|
|
2283
|
+
status: "ready"
|
|
2284
|
+
});
|
|
1967
2285
|
return {
|
|
1968
2286
|
status: "ready",
|
|
1969
2287
|
templateId: tag
|
|
@@ -2041,7 +2359,7 @@ var DockerTemplate = class DockerTemplate {
|
|
|
2041
2359
|
session
|
|
2042
2360
|
};
|
|
2043
2361
|
}
|
|
2044
|
-
#followBuild(docker, stream, abortSignal) {
|
|
2362
|
+
#followBuild(docker, stream, normalizer, abortSignal) {
|
|
2045
2363
|
return new Promise((resolve, reject) => {
|
|
2046
2364
|
let settled = false;
|
|
2047
2365
|
const finish = (error) => {
|
|
@@ -2064,6 +2382,11 @@ var DockerTemplate = class DockerTemplate {
|
|
|
2064
2382
|
finish(err);
|
|
2065
2383
|
return;
|
|
2066
2384
|
}
|
|
2385
|
+
const reported = normalizer.failure();
|
|
2386
|
+
if (reported) {
|
|
2387
|
+
finish(new Error(reported.message));
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2067
2390
|
const failure = output?.find((entry) => entry && (entry.error !== void 0 || entry.errorDetail !== void 0));
|
|
2068
2391
|
if (failure) {
|
|
2069
2392
|
const detail = failure.errorDetail;
|
|
@@ -2071,6 +2394,8 @@ var DockerTemplate = class DockerTemplate {
|
|
|
2071
2394
|
return;
|
|
2072
2395
|
}
|
|
2073
2396
|
finish();
|
|
2397
|
+
}, (event) => {
|
|
2398
|
+
if (!settled) normalizer.handle(event);
|
|
2074
2399
|
});
|
|
2075
2400
|
});
|
|
2076
2401
|
}
|
|
@@ -2209,11 +2534,18 @@ const REPO_DIR_PATTERN = /^[\w.-]+$/;
|
|
|
2209
2534
|
function guardedSetupCommand({ repoDir, command, continueOnFailure }) {
|
|
2210
2535
|
if (!REPO_DIR_PATTERN.test(repoDir)) throw new TypeError(`Repository directory "${repoDir}" must match ${REPO_DIR_PATTERN}.`);
|
|
2211
2536
|
if (!continueOnFailure) return `cd "${repoDir}" && ${command}`;
|
|
2212
|
-
return `( cd "${repoDir}" && sh -c ${shellQuote$
|
|
2537
|
+
return `( cd "${repoDir}" && sh -c ${shellQuote$2(command)} ) || { mkdir -p "${SETUP_MARKER_DIR}" && grep -qxF -- '${repoDir}' "${SETUP_FAILED_MARKER_PATH}" 2>/dev/null || printf '%s\\n' '${repoDir}' >> "${SETUP_FAILED_MARKER_PATH}"; }`;
|
|
2213
2538
|
}
|
|
2214
|
-
function shellQuote$
|
|
2539
|
+
function shellQuote$2(value) {
|
|
2215
2540
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
2216
2541
|
}
|
|
2542
|
+
const { notFound, isDirectory, exists, notDirectory, destIsDirectory } = {
|
|
2543
|
+
notFound: 20,
|
|
2544
|
+
isDirectory: 21,
|
|
2545
|
+
exists: 22,
|
|
2546
|
+
notDirectory: 23,
|
|
2547
|
+
destIsDirectory: 24
|
|
2548
|
+
};
|
|
2217
2549
|
//#endregion
|
|
2218
2550
|
//#region src/template/repo-template.ts
|
|
2219
2551
|
/**
|