@mastra/docker 0.11.1-alpha.0 → 0.12.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/index.cjs +340 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +340 -12
- 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/sandbox/process-manager.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 +5 -6
package/README.md
CHANGED
|
@@ -141,6 +141,43 @@ const template = new DockerTemplate({ secrets: { GITHUB_TOKEN: token } })
|
|
|
141
141
|
`dispose()` removes the image; Docker refuses while a container still references
|
|
142
142
|
it, so destroy the template's sandboxes first.
|
|
143
143
|
|
|
144
|
+
#### Build progress
|
|
145
|
+
|
|
146
|
+
Pass `onProgress` to `build()` (or `onTemplateBuildProgress` to `DockerSandbox`)
|
|
147
|
+
to follow a long preparation while it runs:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const result = await template.build({
|
|
151
|
+
onProgress: event => {
|
|
152
|
+
if (event.type === 'step-start') console.log(`> ${event.step.name}${event.step.cached ? ' (cached)' : ''}`);
|
|
153
|
+
if (event.type === 'output') process.stdout.write(redact(event.text));
|
|
154
|
+
},
|
|
155
|
+
maxOutputBytes: 32 * 1024, // default 64 KiB
|
|
156
|
+
});
|
|
157
|
+
if (result.status === 'failed') {
|
|
158
|
+
console.error(result.error, result.failedStep?.name, result.output, result.outputTruncated);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Events are `reused` (the image already exists, nothing is built),
|
|
163
|
+
`build-start` (with `builder: 'legacy' | 'buildkit'`), `step-start`,
|
|
164
|
+
`step-complete` (with `cached` when the builder reports a cache hit), `output`,
|
|
165
|
+
and `build-complete`. Plain builds use the legacy builder, which reports
|
|
166
|
+
`Step N/M` steps; builds with secrets use BuildKit, which reports build
|
|
167
|
+
vertices. The callback contract:
|
|
168
|
+
|
|
169
|
+
- Delivery is synchronous, in arrival order, with no backpressure — keep the
|
|
170
|
+
callback fast and hand heavy work off elsewhere.
|
|
171
|
+
- Errors thrown by the callback are ignored and never fail the build.
|
|
172
|
+
- Concurrent callers sharing a build each receive its events from the moment
|
|
173
|
+
they join (no replay). A caller that aborts stops receiving events; the build
|
|
174
|
+
continues for the remaining callers.
|
|
175
|
+
- Only the most recent `maxOutputBytes` of output are retained, and only to
|
|
176
|
+
populate `output` on failed results; `outputTruncated` reports whether older
|
|
177
|
+
output was dropped.
|
|
178
|
+
- `output` text is raw command output. Commands run with `runWithSecrets` can
|
|
179
|
+
echo secret values into it, so redact before logging or persisting.
|
|
180
|
+
|
|
144
181
|
#### Repository templates
|
|
145
182
|
|
|
146
183
|
`createDockerRepoTemplate` prepares a repository checkout plus setup commands,
|
package/dist/index.cjs
CHANGED
|
@@ -304,10 +304,11 @@ var DockerProcessHandle = class extends _mastra_core_workspace.ProcessHandle {
|
|
|
304
304
|
"sh",
|
|
305
305
|
this._pgidFile
|
|
306
306
|
],
|
|
307
|
-
AttachStdout:
|
|
308
|
-
AttachStderr:
|
|
307
|
+
AttachStdout: true,
|
|
308
|
+
AttachStderr: true
|
|
309
309
|
});
|
|
310
310
|
const killStream = await killExec.start({});
|
|
311
|
+
killStream.resume();
|
|
311
312
|
try {
|
|
312
313
|
let killInfo = await killExec.inspect();
|
|
313
314
|
while (killInfo.Running) {
|
|
@@ -606,6 +607,7 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
606
607
|
/** Image the container boots from; rewritten by a template resolution on `start()`. */
|
|
607
608
|
_image;
|
|
608
609
|
_templateSpec;
|
|
610
|
+
_onTemplateBuildProgress;
|
|
609
611
|
_workingDirectoryWasSet;
|
|
610
612
|
_command;
|
|
611
613
|
_env;
|
|
@@ -649,6 +651,7 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
649
651
|
this._containerName = sanitizeContainerName(options.name ?? this.id);
|
|
650
652
|
if (options.image !== void 0 && options.template !== void 0) throw new TypeError("DockerSandbox: `image` and `template` are mutually exclusive");
|
|
651
653
|
this._templateSpec = options.template;
|
|
654
|
+
this._onTemplateBuildProgress = options.onTemplateBuildProgress;
|
|
652
655
|
this._image = options.image ?? "node:22-slim";
|
|
653
656
|
this._workingDirectoryWasSet = options.workingDirectory !== void 0 || options.workingDir !== void 0;
|
|
654
657
|
this._command = options.command ?? ["sleep", "infinity"];
|
|
@@ -1008,7 +1011,8 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
1008
1011
|
throwIfAborted(abortSignal, "start Docker sandbox");
|
|
1009
1012
|
const result = await template.build({
|
|
1010
1013
|
docker: this._docker,
|
|
1011
|
-
abortSignal
|
|
1014
|
+
abortSignal,
|
|
1015
|
+
...this._onTemplateBuildProgress && { onProgress: this._onTemplateBuildProgress }
|
|
1012
1016
|
});
|
|
1013
1017
|
if (result.status !== "ready") throw new _mastra_core_workspace.SandboxError(`Docker template build failed: ${result.error ?? "unknown error"}`, "START_FAILED", {
|
|
1014
1018
|
templateId: result.templateId,
|
|
@@ -1361,6 +1365,287 @@ const dockerSandboxProvider = {
|
|
|
1361
1365
|
createSandbox: (config) => new DockerSandbox(config)
|
|
1362
1366
|
};
|
|
1363
1367
|
//#endregion
|
|
1368
|
+
//#region src/template/build-progress.ts
|
|
1369
|
+
/**
|
|
1370
|
+
* Build progress for `DockerTemplate.build()`.
|
|
1371
|
+
*
|
|
1372
|
+
* The daemon reports build progress as newline-delimited JSON. The legacy
|
|
1373
|
+
* builder sends plain text (`{stream: "Step 2/5 : RUN ..."}`); BuildKit sends
|
|
1374
|
+
* `{id: 'moby.buildkit.trace', aux: <base64 StatusResponse protobuf>}`. This
|
|
1375
|
+
* module normalizes both into {@link DockerTemplateBuildEvent}s and keeps a
|
|
1376
|
+
* bounded tail of raw output for failure diagnostics.
|
|
1377
|
+
*/
|
|
1378
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
1379
|
+
/** Keeps at most `maxBytes` (UTF-8) of the most recent text. */
|
|
1380
|
+
var BoundedTail = class {
|
|
1381
|
+
#maxBytes;
|
|
1382
|
+
#chunks = [];
|
|
1383
|
+
#bytes = 0;
|
|
1384
|
+
#truncated = false;
|
|
1385
|
+
constructor(maxBytes) {
|
|
1386
|
+
this.#maxBytes = maxBytes;
|
|
1387
|
+
}
|
|
1388
|
+
get truncated() {
|
|
1389
|
+
return this.#truncated;
|
|
1390
|
+
}
|
|
1391
|
+
push(text) {
|
|
1392
|
+
if (text.length === 0) return;
|
|
1393
|
+
let chunk = Buffer.from(text, "utf8");
|
|
1394
|
+
if (chunk.length > this.#maxBytes) {
|
|
1395
|
+
chunk = chunk.subarray(chunk.length - this.#maxBytes);
|
|
1396
|
+
this.#chunks = [];
|
|
1397
|
+
this.#bytes = 0;
|
|
1398
|
+
this.#truncated = true;
|
|
1399
|
+
}
|
|
1400
|
+
this.#chunks.push(chunk);
|
|
1401
|
+
this.#bytes += chunk.length;
|
|
1402
|
+
while (this.#bytes > this.#maxBytes) {
|
|
1403
|
+
const head = this.#chunks[0];
|
|
1404
|
+
const excess = this.#bytes - this.#maxBytes;
|
|
1405
|
+
this.#truncated = true;
|
|
1406
|
+
if (head.length <= excess) {
|
|
1407
|
+
this.#chunks.shift();
|
|
1408
|
+
this.#bytes -= head.length;
|
|
1409
|
+
} else {
|
|
1410
|
+
this.#chunks[0] = head.subarray(excess);
|
|
1411
|
+
this.#bytes -= excess;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
toString() {
|
|
1416
|
+
let text = Buffer.concat(this.#chunks).toString("utf8");
|
|
1417
|
+
if (this.#truncated) text = text.replace(/^\uFFFD+/, "");
|
|
1418
|
+
return text;
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
function readFields(buffer) {
|
|
1422
|
+
const fields = [];
|
|
1423
|
+
let offset = 0;
|
|
1424
|
+
const readVarint = () => {
|
|
1425
|
+
let result = 0;
|
|
1426
|
+
let shift = 0;
|
|
1427
|
+
for (;;) {
|
|
1428
|
+
if (offset >= buffer.length) throw new Error("truncated varint");
|
|
1429
|
+
const byte = buffer[offset++];
|
|
1430
|
+
result += (byte & 127) * 2 ** shift;
|
|
1431
|
+
if ((byte & 128) === 0) return result;
|
|
1432
|
+
shift += 7;
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
while (offset < buffer.length) {
|
|
1436
|
+
const key = readVarint();
|
|
1437
|
+
const field = Math.floor(key / 8);
|
|
1438
|
+
const wireType = key % 8;
|
|
1439
|
+
switch (wireType) {
|
|
1440
|
+
case 0:
|
|
1441
|
+
fields.push({
|
|
1442
|
+
field,
|
|
1443
|
+
value: readVarint()
|
|
1444
|
+
});
|
|
1445
|
+
break;
|
|
1446
|
+
case 1:
|
|
1447
|
+
offset += 8;
|
|
1448
|
+
break;
|
|
1449
|
+
case 5:
|
|
1450
|
+
offset += 4;
|
|
1451
|
+
break;
|
|
1452
|
+
case 2: {
|
|
1453
|
+
const length = readVarint();
|
|
1454
|
+
if (offset + length > buffer.length) throw new Error("truncated field");
|
|
1455
|
+
fields.push({
|
|
1456
|
+
field,
|
|
1457
|
+
value: buffer.subarray(offset, offset + length)
|
|
1458
|
+
});
|
|
1459
|
+
offset += length;
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1462
|
+
default: throw new Error(`unsupported protobuf wire type ${wireType}`);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
return fields;
|
|
1466
|
+
}
|
|
1467
|
+
/** @internal exported for tests. Throws on malformed input. */
|
|
1468
|
+
function decodeBuildkitStatus(aux) {
|
|
1469
|
+
const vertexes = [];
|
|
1470
|
+
const logs = [];
|
|
1471
|
+
for (const { field, value } of readFields(Buffer.from(aux, "base64"))) {
|
|
1472
|
+
if (!Buffer.isBuffer(value)) continue;
|
|
1473
|
+
if (field === 1) {
|
|
1474
|
+
const vertex = {
|
|
1475
|
+
digest: "",
|
|
1476
|
+
cached: false,
|
|
1477
|
+
started: false,
|
|
1478
|
+
completed: false
|
|
1479
|
+
};
|
|
1480
|
+
for (const f of readFields(value)) if (f.field === 1 && Buffer.isBuffer(f.value)) vertex.digest = f.value.toString("utf8");
|
|
1481
|
+
else if (f.field === 3 && Buffer.isBuffer(f.value)) vertex.name = f.value.toString("utf8");
|
|
1482
|
+
else if (f.field === 4) vertex.cached = f.value !== 0;
|
|
1483
|
+
else if (f.field === 5) vertex.started = true;
|
|
1484
|
+
else if (f.field === 6) vertex.completed = true;
|
|
1485
|
+
else if (f.field === 7 && Buffer.isBuffer(f.value) && f.value.length > 0) vertex.error = f.value.toString("utf8");
|
|
1486
|
+
if (vertex.digest) vertexes.push(vertex);
|
|
1487
|
+
} else if (field === 3) {
|
|
1488
|
+
let vertex = "";
|
|
1489
|
+
let data = Buffer.alloc(0);
|
|
1490
|
+
for (const f of readFields(value)) if (f.field === 1 && Buffer.isBuffer(f.value)) vertex = f.value.toString("utf8");
|
|
1491
|
+
else if (f.field === 4 && Buffer.isBuffer(f.value)) data = f.value;
|
|
1492
|
+
logs.push({
|
|
1493
|
+
vertex,
|
|
1494
|
+
data
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return {
|
|
1499
|
+
vertexes,
|
|
1500
|
+
logs
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
const LEGACY_STEP = /^Step (\d+)\/(\d+) : (.*)$/;
|
|
1504
|
+
function createBuildEventNormalizer(emit, tail) {
|
|
1505
|
+
let failure;
|
|
1506
|
+
let failedStep;
|
|
1507
|
+
let legacyStep;
|
|
1508
|
+
let legacyCached;
|
|
1509
|
+
let pending = "";
|
|
1510
|
+
const vertexes = /* @__PURE__ */ new Map();
|
|
1511
|
+
const decoders = /* @__PURE__ */ new Map();
|
|
1512
|
+
const output = (text, stepId) => {
|
|
1513
|
+
if (!text) return;
|
|
1514
|
+
tail.push(text);
|
|
1515
|
+
emit({
|
|
1516
|
+
type: "output",
|
|
1517
|
+
...stepId !== void 0 && { stepId },
|
|
1518
|
+
text
|
|
1519
|
+
});
|
|
1520
|
+
};
|
|
1521
|
+
const completeLegacyStep = (error) => {
|
|
1522
|
+
if (!legacyStep) return;
|
|
1523
|
+
const step = {
|
|
1524
|
+
...legacyStep,
|
|
1525
|
+
...legacyCached !== void 0 && { cached: legacyCached }
|
|
1526
|
+
};
|
|
1527
|
+
if (error !== void 0) failedStep = step;
|
|
1528
|
+
emit({
|
|
1529
|
+
type: "step-complete",
|
|
1530
|
+
step,
|
|
1531
|
+
...error !== void 0 && { error }
|
|
1532
|
+
});
|
|
1533
|
+
legacyStep = void 0;
|
|
1534
|
+
legacyCached = void 0;
|
|
1535
|
+
};
|
|
1536
|
+
const handleLegacyLine = (line) => {
|
|
1537
|
+
const match = LEGACY_STEP.exec(line.replace(/\r?\n$/, ""));
|
|
1538
|
+
if (match) {
|
|
1539
|
+
completeLegacyStep();
|
|
1540
|
+
legacyStep = {
|
|
1541
|
+
id: match[1],
|
|
1542
|
+
name: match[3],
|
|
1543
|
+
index: Number(match[1]),
|
|
1544
|
+
total: Number(match[2])
|
|
1545
|
+
};
|
|
1546
|
+
emit({
|
|
1547
|
+
type: "step-start",
|
|
1548
|
+
step: { ...legacyStep }
|
|
1549
|
+
});
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
if (legacyStep) {
|
|
1553
|
+
const trimmed = line.trim();
|
|
1554
|
+
if (trimmed === "---> Using cache") legacyCached = true;
|
|
1555
|
+
else if (trimmed.startsWith("---> Running in ") && legacyCached === void 0) legacyCached = false;
|
|
1556
|
+
}
|
|
1557
|
+
output(line, legacyStep?.id);
|
|
1558
|
+
};
|
|
1559
|
+
const handleLegacyStream = (text) => {
|
|
1560
|
+
pending += text;
|
|
1561
|
+
let newline = pending.indexOf("\n");
|
|
1562
|
+
while (newline !== -1) {
|
|
1563
|
+
handleLegacyLine(pending.slice(0, newline + 1));
|
|
1564
|
+
pending = pending.slice(newline + 1);
|
|
1565
|
+
newline = pending.indexOf("\n");
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
const flushLegacy = () => {
|
|
1569
|
+
if (pending) {
|
|
1570
|
+
handleLegacyLine(pending);
|
|
1571
|
+
pending = "";
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
const handleBuildkit = (aux) => {
|
|
1575
|
+
const { vertexes: updates, logs } = decodeBuildkitStatus(aux);
|
|
1576
|
+
for (const update of updates) {
|
|
1577
|
+
let state = vertexes.get(update.digest);
|
|
1578
|
+
if (!state) {
|
|
1579
|
+
state = {
|
|
1580
|
+
step: {
|
|
1581
|
+
id: update.digest,
|
|
1582
|
+
name: update.name ?? ""
|
|
1583
|
+
},
|
|
1584
|
+
started: false,
|
|
1585
|
+
completed: false
|
|
1586
|
+
};
|
|
1587
|
+
vertexes.set(update.digest, state);
|
|
1588
|
+
}
|
|
1589
|
+
if (update.name && !state.step.name) state.step.name = update.name;
|
|
1590
|
+
if (update.cached) state.step.cached = true;
|
|
1591
|
+
if (state.step.name.startsWith("[internal]")) continue;
|
|
1592
|
+
if (!state.started && (update.started || update.completed || update.cached || update.error)) {
|
|
1593
|
+
state.started = true;
|
|
1594
|
+
emit({
|
|
1595
|
+
type: "step-start",
|
|
1596
|
+
step: { ...state.step }
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
if (!state.completed && (update.completed || update.error)) {
|
|
1600
|
+
state.completed = true;
|
|
1601
|
+
if (state.step.cached === void 0) state.step.cached = false;
|
|
1602
|
+
const step = { ...state.step };
|
|
1603
|
+
if (update.error) failedStep = step;
|
|
1604
|
+
emit({
|
|
1605
|
+
type: "step-complete",
|
|
1606
|
+
step,
|
|
1607
|
+
...update.error && { error: update.error }
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
for (const log of logs) {
|
|
1612
|
+
if (vertexes.get(log.vertex)?.step.name.startsWith("[internal]")) continue;
|
|
1613
|
+
let decoder = decoders.get(log.vertex);
|
|
1614
|
+
if (!decoder) {
|
|
1615
|
+
decoder = new string_decoder.StringDecoder("utf8");
|
|
1616
|
+
decoders.set(log.vertex, decoder);
|
|
1617
|
+
}
|
|
1618
|
+
output(decoder.write(log.data), log.vertex || void 0);
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
const handleError = (raw) => {
|
|
1622
|
+
const detail = raw.errorDetail;
|
|
1623
|
+
const message = String(detail?.message ?? raw.error ?? "docker build failed");
|
|
1624
|
+
flushLegacy();
|
|
1625
|
+
completeLegacyStep(message);
|
|
1626
|
+
if (!failure) failure = {
|
|
1627
|
+
message,
|
|
1628
|
+
...failedStep && { step: failedStep }
|
|
1629
|
+
};
|
|
1630
|
+
};
|
|
1631
|
+
return {
|
|
1632
|
+
handle(raw) {
|
|
1633
|
+
if (!raw || typeof raw !== "object") return;
|
|
1634
|
+
const event = raw;
|
|
1635
|
+
try {
|
|
1636
|
+
if (event.error !== void 0 || event.errorDetail !== void 0) handleError(event);
|
|
1637
|
+
else if (event.id === "moby.buildkit.trace" && typeof event.aux === "string") handleBuildkit(event.aux);
|
|
1638
|
+
else if (typeof event.stream === "string") handleLegacyStream(event.stream);
|
|
1639
|
+
} catch {}
|
|
1640
|
+
},
|
|
1641
|
+
finish() {
|
|
1642
|
+
flushLegacy();
|
|
1643
|
+
completeLegacyStep();
|
|
1644
|
+
},
|
|
1645
|
+
failure: () => failure
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
//#endregion
|
|
1364
1649
|
//#region src/template/build-session.ts
|
|
1365
1650
|
/**
|
|
1366
1651
|
* BuildKit session that serves build secrets to the daemon.
|
|
@@ -1897,20 +2182,29 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1897
2182
|
* `options.secrets`, the template's `secrets` option, or `process.env`.
|
|
1898
2183
|
*/
|
|
1899
2184
|
async build(options = {}) {
|
|
2185
|
+
const { maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES } = options;
|
|
2186
|
+
if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes <= 0) throw new RangeError("maxOutputBytes must be a positive integer");
|
|
1900
2187
|
throwIfAborted(options.abortSignal, "build Docker template");
|
|
1901
2188
|
const isPlain = !options.force && options.secrets === void 0 && options.docker === void 0;
|
|
1902
|
-
if (isPlain && this.#plainBuild) return this.#waitForBuild(this.#plainBuild, options.abortSignal);
|
|
2189
|
+
if (isPlain && this.#plainBuild) return this.#waitForBuild(this.#plainBuild, options.abortSignal, options.onProgress);
|
|
1903
2190
|
const previous = this.#queueTail?.promise.catch(() => void 0) ?? Promise.resolve();
|
|
1904
2191
|
const controller = new AbortController();
|
|
2192
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2193
|
+
const emit = (event) => {
|
|
2194
|
+
for (const listener of [...listeners]) try {
|
|
2195
|
+
listener(event);
|
|
2196
|
+
} catch {}
|
|
2197
|
+
};
|
|
1905
2198
|
const run = {
|
|
1906
2199
|
controller,
|
|
1907
2200
|
waiters: 0,
|
|
2201
|
+
listeners,
|
|
1908
2202
|
promise: previous.then(() => {
|
|
1909
2203
|
throwIfAborted(controller.signal, "build Docker template");
|
|
1910
2204
|
return this.#build({
|
|
1911
2205
|
...options,
|
|
1912
2206
|
abortSignal: controller.signal
|
|
1913
|
-
});
|
|
2207
|
+
}, emit, maxOutputBytes);
|
|
1914
2208
|
})
|
|
1915
2209
|
};
|
|
1916
2210
|
this.#queueTail = run;
|
|
@@ -1919,20 +2213,23 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1919
2213
|
if (this.#queueTail === run) this.#queueTail = void 0;
|
|
1920
2214
|
if (this.#plainBuild === run) this.#plainBuild = void 0;
|
|
1921
2215
|
}).catch(() => void 0);
|
|
1922
|
-
return this.#waitForBuild(run, options.abortSignal);
|
|
2216
|
+
return this.#waitForBuild(run, options.abortSignal, options.onProgress);
|
|
1923
2217
|
}
|
|
1924
|
-
#waitForBuild(run, abortSignal) {
|
|
2218
|
+
#waitForBuild(run, abortSignal, onProgress) {
|
|
1925
2219
|
run.waiters += 1;
|
|
2220
|
+
const listener = onProgress && ((event) => onProgress(event));
|
|
2221
|
+
if (listener) run.listeners.add(listener);
|
|
1926
2222
|
let settled = false;
|
|
1927
2223
|
const release = (aborted) => {
|
|
1928
2224
|
if (settled) return;
|
|
1929
2225
|
settled = true;
|
|
2226
|
+
if (listener) run.listeners.delete(listener);
|
|
1930
2227
|
run.waiters -= 1;
|
|
1931
2228
|
if (aborted && run.waiters === 0) run.controller.abort();
|
|
1932
2229
|
};
|
|
1933
2230
|
return waitForAbortable(run.promise, abortSignal, "build Docker template", () => release(true)).finally(() => release(false));
|
|
1934
2231
|
}
|
|
1935
|
-
async #build(options) {
|
|
2232
|
+
async #build(options, emit, maxOutputBytes) {
|
|
1936
2233
|
const docker = options.docker ?? this.#getDocker();
|
|
1937
2234
|
const tag = this.templateId;
|
|
1938
2235
|
const { abortSignal } = options;
|
|
@@ -1941,6 +2238,10 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1941
2238
|
await docker.getImage(tag).inspect();
|
|
1942
2239
|
throwIfAborted(abortSignal, "build Docker template");
|
|
1943
2240
|
this.#built = true;
|
|
2241
|
+
emit({
|
|
2242
|
+
type: "reused",
|
|
2243
|
+
templateId: tag
|
|
2244
|
+
});
|
|
1944
2245
|
return {
|
|
1945
2246
|
status: "ready",
|
|
1946
2247
|
templateId: tag
|
|
@@ -1953,12 +2254,19 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1953
2254
|
const context = (0, tar_stream.pack)();
|
|
1954
2255
|
context.entry({ name: "Dockerfile" }, this.dockerfile);
|
|
1955
2256
|
context.finalize();
|
|
2257
|
+
const tail = new BoundedTail(maxOutputBytes);
|
|
2258
|
+
const normalizer = createBuildEventNormalizer(emit, tail);
|
|
2259
|
+
emit({
|
|
2260
|
+
type: "build-start",
|
|
2261
|
+
templateId: tag,
|
|
2262
|
+
builder: secrets ? "buildkit" : "legacy"
|
|
2263
|
+
});
|
|
1956
2264
|
try {
|
|
1957
2265
|
const nocache = options.force === true;
|
|
1958
2266
|
if (secrets) {
|
|
1959
2267
|
const { stream, session } = await this.#buildWithSecrets(docker, context, tag, secrets, nocache, abortSignal);
|
|
1960
2268
|
try {
|
|
1961
|
-
await this.#followBuild(docker, stream, abortSignal);
|
|
2269
|
+
await this.#followBuild(docker, stream, normalizer, abortSignal);
|
|
1962
2270
|
} finally {
|
|
1963
2271
|
session.close();
|
|
1964
2272
|
}
|
|
@@ -1976,18 +2284,31 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1976
2284
|
}), abortSignal, "build Docker template", () => {
|
|
1977
2285
|
context.destroy?.();
|
|
1978
2286
|
});
|
|
1979
|
-
await this.#followBuild(docker, stream, abortSignal);
|
|
2287
|
+
await this.#followBuild(docker, stream, normalizer, abortSignal);
|
|
1980
2288
|
}
|
|
1981
2289
|
} catch (error) {
|
|
1982
2290
|
this.#built = false;
|
|
1983
2291
|
if (abortSignal?.aborted) throw normalizeAbortError(error, "build Docker template");
|
|
2292
|
+
emit({
|
|
2293
|
+
type: "build-complete",
|
|
2294
|
+
status: "failed"
|
|
2295
|
+
});
|
|
2296
|
+
const failedStep = normalizer.failure()?.step;
|
|
1984
2297
|
return {
|
|
1985
2298
|
status: "failed",
|
|
1986
2299
|
templateId: tag,
|
|
1987
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2300
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2301
|
+
...failedStep && { failedStep },
|
|
2302
|
+
output: tail.toString(),
|
|
2303
|
+
outputTruncated: tail.truncated
|
|
1988
2304
|
};
|
|
1989
2305
|
}
|
|
2306
|
+
normalizer.finish();
|
|
1990
2307
|
this.#built = true;
|
|
2308
|
+
emit({
|
|
2309
|
+
type: "build-complete",
|
|
2310
|
+
status: "ready"
|
|
2311
|
+
});
|
|
1991
2312
|
return {
|
|
1992
2313
|
status: "ready",
|
|
1993
2314
|
templateId: tag
|
|
@@ -2065,7 +2386,7 @@ var DockerTemplate = class DockerTemplate {
|
|
|
2065
2386
|
session
|
|
2066
2387
|
};
|
|
2067
2388
|
}
|
|
2068
|
-
#followBuild(docker, stream, abortSignal) {
|
|
2389
|
+
#followBuild(docker, stream, normalizer, abortSignal) {
|
|
2069
2390
|
return new Promise((resolve, reject) => {
|
|
2070
2391
|
let settled = false;
|
|
2071
2392
|
const finish = (error) => {
|
|
@@ -2088,6 +2409,11 @@ var DockerTemplate = class DockerTemplate {
|
|
|
2088
2409
|
finish(err);
|
|
2089
2410
|
return;
|
|
2090
2411
|
}
|
|
2412
|
+
const reported = normalizer.failure();
|
|
2413
|
+
if (reported) {
|
|
2414
|
+
finish(new Error(reported.message));
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2091
2417
|
const failure = output?.find((entry) => entry && (entry.error !== void 0 || entry.errorDetail !== void 0));
|
|
2092
2418
|
if (failure) {
|
|
2093
2419
|
const detail = failure.errorDetail;
|
|
@@ -2095,6 +2421,8 @@ var DockerTemplate = class DockerTemplate {
|
|
|
2095
2421
|
return;
|
|
2096
2422
|
}
|
|
2097
2423
|
finish();
|
|
2424
|
+
}, (event) => {
|
|
2425
|
+
if (!settled) normalizer.handle(event);
|
|
2098
2426
|
});
|
|
2099
2427
|
});
|
|
2100
2428
|
}
|