@mastra/docker 0.9.0-alpha.1 → 0.9.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 +8 -0
- package/dist/abort.d.ts +7 -0
- package/dist/abort.d.ts.map +1 -0
- package/dist/index.cjs +194 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -35
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +8 -3
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/template/build-session.d.ts +1 -1
- package/dist/template/build-session.d.ts.map +1 -1
- package/dist/template/index.d.ts +1 -1
- package/dist/template/index.d.ts.map +1 -1
- package/dist/template/repo-template.d.ts +8 -4
- package/dist/template/repo-template.d.ts.map +1 -1
- package/dist/template/template.d.ts +2 -0
- package/dist/template/template.d.ts.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -91,6 +91,11 @@ const b = new DockerSandbox({ template });
|
|
|
91
91
|
const result = await template.build();
|
|
92
92
|
if (result.status !== 'ready') throw new Error(result.error);
|
|
93
93
|
|
|
94
|
+
// Cancel repository resolution or template preparation when needed.
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
await a.start({ abortSignal: controller.signal });
|
|
97
|
+
// Explicit builds accept the same option: template.build({ abortSignal: controller.signal }).
|
|
98
|
+
|
|
94
99
|
// Remove the built image when done (independent of any sandbox's destroy()).
|
|
95
100
|
await template.dispose();
|
|
96
101
|
```
|
|
@@ -105,6 +110,9 @@ template, and their signatures match the E2B and platform template builders.
|
|
|
105
110
|
The image tag is content-addressed (`mastra-template:<hash>`), so `build()` is
|
|
106
111
|
idempotent and reuses an existing image unless you pass `{ force: true }`,
|
|
107
112
|
which also bypasses the daemon's layer cache so every step really re-runs.
|
|
113
|
+
Concurrent callers share preparation work, but each caller can cancel its own
|
|
114
|
+
wait independently; the underlying build is cancelled only after its last
|
|
115
|
+
active caller aborts.
|
|
108
116
|
|
|
109
117
|
Never put secrets in `setEnvs` — they are baked into the image. For a step that
|
|
110
118
|
needs a credential, use `runWithSecrets`: the command runs in a throwaway build
|
package/dist/abort.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SandboxAbortError } from '@mastra/core/workspace';
|
|
2
|
+
export declare function createAbortError(signal: AbortSignal, operation: string): SandboxAbortError;
|
|
3
|
+
export declare function throwIfAborted(signal: AbortSignal | undefined, operation: string): void;
|
|
4
|
+
export declare function isAbortError(error: unknown): boolean;
|
|
5
|
+
export declare function normalizeAbortError(error: unknown, operation: string): unknown;
|
|
6
|
+
export declare function waitForAbortable<T>(promise: Promise<T>, signal: AbortSignal | undefined, operation: string, onAbort?: () => void): Promise<T>;
|
|
7
|
+
//# sourceMappingURL=abort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,iBAAiB,CAE1F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAEvF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKpD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAM9E;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,IAAI,GACnB,OAAO,CAAC,CAAC,CAAC,CAqBZ"}
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,38 @@ let path_posix = require("path/posix");
|
|
|
32
32
|
path_posix = __toESM(path_posix, 1);
|
|
33
33
|
let _grpc_grpc_js = require("@grpc/grpc-js");
|
|
34
34
|
let child_process = require("child_process");
|
|
35
|
+
//#region src/abort.ts
|
|
36
|
+
function createAbortError(signal, operation) {
|
|
37
|
+
return new _mastra_core_workspace.SandboxAbortError(operation, signal.reason);
|
|
38
|
+
}
|
|
39
|
+
function throwIfAborted(signal, operation) {
|
|
40
|
+
if (signal?.aborted) throw createAbortError(signal, operation);
|
|
41
|
+
}
|
|
42
|
+
function isAbortError(error) {
|
|
43
|
+
return error instanceof _mastra_core_workspace.SandboxAbortError || error instanceof Error && (error.name === "AbortError" || "code" in error && error.code === "ABORT_ERR");
|
|
44
|
+
}
|
|
45
|
+
function normalizeAbortError(error, operation) {
|
|
46
|
+
return error instanceof _mastra_core_workspace.SandboxAbortError ? error : isAbortError(error) ? new _mastra_core_workspace.SandboxAbortError(operation, error) : error;
|
|
47
|
+
}
|
|
48
|
+
function waitForAbortable(promise, signal, operation, onAbort) {
|
|
49
|
+
throwIfAborted(signal, operation);
|
|
50
|
+
if (!signal) return promise;
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const abort = () => {
|
|
53
|
+
onAbort?.();
|
|
54
|
+
reject(new _mastra_core_workspace.SandboxAbortError(operation, signal.reason));
|
|
55
|
+
};
|
|
56
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
57
|
+
promise.then((value) => {
|
|
58
|
+
signal.removeEventListener("abort", abort);
|
|
59
|
+
resolve(value);
|
|
60
|
+
}, (error) => {
|
|
61
|
+
signal.removeEventListener("abort", abort);
|
|
62
|
+
reject(normalizeAbortError(error, operation));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
35
67
|
//#region src/sandbox/process-manager.ts
|
|
36
68
|
/**
|
|
37
69
|
* Docker Process Manager
|
|
@@ -569,7 +601,9 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
569
601
|
if (!this._container) throw new _mastra_core_workspace.SandboxNotReadyError(this.id);
|
|
570
602
|
return this._container;
|
|
571
603
|
}
|
|
572
|
-
async start() {
|
|
604
|
+
async start(options = {}) {
|
|
605
|
+
const { abortSignal } = options;
|
|
606
|
+
throwIfAborted(abortSignal, "start Docker sandbox");
|
|
573
607
|
this.logger.debug(`${LOG_PREFIX} Starting sandbox ${this.id}...`);
|
|
574
608
|
const existing = await this._findExistingContainer();
|
|
575
609
|
if (existing) {
|
|
@@ -590,7 +624,7 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
590
624
|
return;
|
|
591
625
|
}
|
|
592
626
|
this._warnOnPrivilegedHardeningConflict(this._privileged);
|
|
593
|
-
await this._resolveTemplate();
|
|
627
|
+
await this._resolveTemplate(abortSignal);
|
|
594
628
|
await this._ensureImage();
|
|
595
629
|
const envArray = Object.entries(this._env).map(([k, v]) => `${k}=${v}`);
|
|
596
630
|
const binds = Object.entries(this._volumes).map(([host, container]) => `${host}:${container}`);
|
|
@@ -850,10 +884,20 @@ var DockerSandbox = class DockerSandbox extends _mastra_core_workspace.MastraSan
|
|
|
850
884
|
* creates a container, so a resolver-form template re-resolves each time.
|
|
851
885
|
* Adopts the template's workdir unless the sandbox was given one explicitly.
|
|
852
886
|
*/
|
|
853
|
-
async _resolveTemplate() {
|
|
887
|
+
async _resolveTemplate(abortSignal) {
|
|
854
888
|
if (!this._templateSpec) return;
|
|
855
|
-
|
|
856
|
-
|
|
889
|
+
throwIfAborted(abortSignal, "start Docker sandbox");
|
|
890
|
+
let template;
|
|
891
|
+
try {
|
|
892
|
+
template = typeof this._templateSpec === "function" ? await this._templateSpec({ abortSignal }) : this._templateSpec;
|
|
893
|
+
} catch (error) {
|
|
894
|
+
throw normalizeAbortError(error, "start Docker sandbox");
|
|
895
|
+
}
|
|
896
|
+
throwIfAborted(abortSignal, "start Docker sandbox");
|
|
897
|
+
const result = await template.build({
|
|
898
|
+
docker: this._docker,
|
|
899
|
+
abortSignal
|
|
900
|
+
});
|
|
857
901
|
if (result.status !== "ready") throw new _mastra_core_workspace.SandboxError(`Docker template build failed: ${result.error ?? "unknown error"}`, "START_FAILED", {
|
|
858
902
|
templateId: result.templateId,
|
|
859
903
|
reason: "template_build_failed"
|
|
@@ -1226,9 +1270,17 @@ const AUTH_CREDENTIALS_METHOD = "/moby.filesync.v1.Auth/Credentials";
|
|
|
1226
1270
|
* Open a session that answers `GetSecret` from `secrets`. The returned id must
|
|
1227
1271
|
* be sent as the `session` query parameter of the build request.
|
|
1228
1272
|
*/
|
|
1229
|
-
function openBuildSession(docker, secrets) {
|
|
1273
|
+
function openBuildSession(docker, secrets, abortSignal) {
|
|
1274
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1230
1275
|
const id = (0, crypto.randomUUID)();
|
|
1231
1276
|
return new Promise((resolve, reject) => {
|
|
1277
|
+
let settled = false;
|
|
1278
|
+
const abort = () => {
|
|
1279
|
+
if (settled || !abortSignal) return;
|
|
1280
|
+
settled = true;
|
|
1281
|
+
reject(createAbortError(abortSignal, "build Docker template"));
|
|
1282
|
+
};
|
|
1283
|
+
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
1232
1284
|
docker.modem.dial({
|
|
1233
1285
|
method: "POST",
|
|
1234
1286
|
path: "/session",
|
|
@@ -1244,8 +1296,14 @@ function openBuildSession(docker, secrets) {
|
|
|
1244
1296
|
500: "server error"
|
|
1245
1297
|
}
|
|
1246
1298
|
}, (err, socket) => {
|
|
1299
|
+
if (settled) {
|
|
1300
|
+
socket?.end?.();
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
settled = true;
|
|
1304
|
+
abortSignal?.removeEventListener("abort", abort);
|
|
1247
1305
|
if (err) {
|
|
1248
|
-
reject(err);
|
|
1306
|
+
reject(abortSignal?.aborted ? createAbortError(abortSignal, "build Docker template") : err);
|
|
1249
1307
|
return;
|
|
1250
1308
|
}
|
|
1251
1309
|
const server = new _grpc_grpc_js.Server();
|
|
@@ -1566,7 +1624,8 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1566
1624
|
#secrets;
|
|
1567
1625
|
#docker;
|
|
1568
1626
|
#built = false;
|
|
1569
|
-
#
|
|
1627
|
+
#queueTail;
|
|
1628
|
+
#plainBuild;
|
|
1570
1629
|
constructor(options = {}, state) {
|
|
1571
1630
|
this.#baseImage = state ? state.baseImage : validateString(options.baseImage ?? "node:22-slim", "baseImage");
|
|
1572
1631
|
this.#operations = state?.operations ?? [];
|
|
@@ -1713,19 +1772,49 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1713
1772
|
* `options.secrets`, the template's `secrets` option, or `process.env`.
|
|
1714
1773
|
*/
|
|
1715
1774
|
async build(options = {}) {
|
|
1716
|
-
|
|
1717
|
-
const
|
|
1718
|
-
this.#
|
|
1719
|
-
|
|
1720
|
-
|
|
1775
|
+
throwIfAborted(options.abortSignal, "build Docker template");
|
|
1776
|
+
const isPlain = !options.force && options.secrets === void 0 && options.docker === void 0;
|
|
1777
|
+
if (isPlain && this.#plainBuild) return this.#waitForBuild(this.#plainBuild, options.abortSignal);
|
|
1778
|
+
const previous = this.#queueTail?.promise.catch(() => void 0) ?? Promise.resolve();
|
|
1779
|
+
const controller = new AbortController();
|
|
1780
|
+
const run = {
|
|
1781
|
+
controller,
|
|
1782
|
+
waiters: 0,
|
|
1783
|
+
promise: previous.then(() => {
|
|
1784
|
+
throwIfAborted(controller.signal, "build Docker template");
|
|
1785
|
+
return this.#build({
|
|
1786
|
+
...options,
|
|
1787
|
+
abortSignal: controller.signal
|
|
1788
|
+
});
|
|
1789
|
+
})
|
|
1790
|
+
};
|
|
1791
|
+
this.#queueTail = run;
|
|
1792
|
+
if (isPlain) this.#plainBuild = run;
|
|
1793
|
+
run.promise.finally(() => {
|
|
1794
|
+
if (this.#queueTail === run) this.#queueTail = void 0;
|
|
1795
|
+
if (this.#plainBuild === run) this.#plainBuild = void 0;
|
|
1721
1796
|
}).catch(() => void 0);
|
|
1722
|
-
return run;
|
|
1797
|
+
return this.#waitForBuild(run, options.abortSignal);
|
|
1798
|
+
}
|
|
1799
|
+
#waitForBuild(run, abortSignal) {
|
|
1800
|
+
run.waiters += 1;
|
|
1801
|
+
let settled = false;
|
|
1802
|
+
const release = (aborted) => {
|
|
1803
|
+
if (settled) return;
|
|
1804
|
+
settled = true;
|
|
1805
|
+
run.waiters -= 1;
|
|
1806
|
+
if (aborted && run.waiters === 0) run.controller.abort();
|
|
1807
|
+
};
|
|
1808
|
+
return waitForAbortable(run.promise, abortSignal, "build Docker template", () => release(true)).finally(() => release(false));
|
|
1723
1809
|
}
|
|
1724
1810
|
async #build(options) {
|
|
1725
1811
|
const docker = options.docker ?? this.#getDocker();
|
|
1726
1812
|
const tag = this.templateId;
|
|
1813
|
+
const { abortSignal } = options;
|
|
1814
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1727
1815
|
if (!options.force) try {
|
|
1728
1816
|
await docker.getImage(tag).inspect();
|
|
1817
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1729
1818
|
this.#built = true;
|
|
1730
1819
|
return {
|
|
1731
1820
|
status: "ready",
|
|
@@ -1735,24 +1824,37 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1735
1824
|
if (!isImageNotFoundError(error)) throw error;
|
|
1736
1825
|
}
|
|
1737
1826
|
const secrets = await this.#resolveSecrets(options.secrets);
|
|
1827
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1738
1828
|
const context = (0, tar_stream.pack)();
|
|
1739
1829
|
context.entry({ name: "Dockerfile" }, this.dockerfile);
|
|
1740
1830
|
context.finalize();
|
|
1741
1831
|
try {
|
|
1742
1832
|
const nocache = options.force === true;
|
|
1743
1833
|
if (secrets) {
|
|
1744
|
-
const { stream, session } = await this.#buildWithSecrets(docker, context, tag, secrets, nocache);
|
|
1834
|
+
const { stream, session } = await this.#buildWithSecrets(docker, context, tag, secrets, nocache, abortSignal);
|
|
1745
1835
|
try {
|
|
1746
|
-
await this.#followBuild(docker, stream);
|
|
1836
|
+
await this.#followBuild(docker, stream, abortSignal);
|
|
1747
1837
|
} finally {
|
|
1748
1838
|
session.close();
|
|
1749
1839
|
}
|
|
1750
|
-
} else
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1840
|
+
} else {
|
|
1841
|
+
const stream = await waitForAbortable(docker.buildImage(context, {
|
|
1842
|
+
t: tag,
|
|
1843
|
+
nocache
|
|
1844
|
+
}).then((stream) => {
|
|
1845
|
+
if (abortSignal?.aborted) {
|
|
1846
|
+
stream.destroy?.();
|
|
1847
|
+
throw createAbortError(abortSignal, "build Docker template");
|
|
1848
|
+
}
|
|
1849
|
+
return stream;
|
|
1850
|
+
}), abortSignal, "build Docker template", () => {
|
|
1851
|
+
context.destroy?.();
|
|
1852
|
+
});
|
|
1853
|
+
await this.#followBuild(docker, stream, abortSignal);
|
|
1854
|
+
}
|
|
1754
1855
|
} catch (error) {
|
|
1755
1856
|
this.#built = false;
|
|
1857
|
+
if (abortSignal?.aborted) throw normalizeAbortError(error, "build Docker template");
|
|
1756
1858
|
return {
|
|
1757
1859
|
status: "failed",
|
|
1758
1860
|
templateId: tag,
|
|
@@ -1783,12 +1885,35 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1783
1885
|
* directly because `docker.buildImage` replaces any session id with its own
|
|
1784
1886
|
* auth-only session when `version` is `'2'`.
|
|
1785
1887
|
*/
|
|
1786
|
-
async #buildWithSecrets(docker, context, tag, secrets, nocache) {
|
|
1787
|
-
|
|
1888
|
+
async #buildWithSecrets(docker, context, tag, secrets, nocache, abortSignal) {
|
|
1889
|
+
throwIfAborted(abortSignal, "build Docker template");
|
|
1890
|
+
const session = await openBuildSession(docker, secrets, abortSignal);
|
|
1788
1891
|
let stream;
|
|
1789
1892
|
try {
|
|
1790
1893
|
stream = await new Promise((resolve, reject) => {
|
|
1791
|
-
|
|
1894
|
+
let settled = false;
|
|
1895
|
+
let request;
|
|
1896
|
+
const finish = (error, data) => {
|
|
1897
|
+
if (settled) {
|
|
1898
|
+
if (data) data.destroy?.();
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
settled = true;
|
|
1902
|
+
abortSignal?.removeEventListener("abort", abort);
|
|
1903
|
+
error === void 0 ? resolve(data) : reject(error);
|
|
1904
|
+
};
|
|
1905
|
+
const abort = () => {
|
|
1906
|
+
context.destroy?.();
|
|
1907
|
+
request?.destroy?.();
|
|
1908
|
+
session.close();
|
|
1909
|
+
if (abortSignal) finish(createAbortError(abortSignal, "build Docker template"));
|
|
1910
|
+
};
|
|
1911
|
+
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
1912
|
+
if (abortSignal?.aborted) {
|
|
1913
|
+
abort();
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
request = docker.modem.dial({
|
|
1792
1917
|
path: "/build?",
|
|
1793
1918
|
method: "POST",
|
|
1794
1919
|
file: context,
|
|
@@ -1803,7 +1928,7 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1803
1928
|
200: true,
|
|
1804
1929
|
500: "server error"
|
|
1805
1930
|
}
|
|
1806
|
-
}, (err, data) => err
|
|
1931
|
+
}, (err, data) => finish(err ?? void 0, data));
|
|
1807
1932
|
});
|
|
1808
1933
|
} catch (error) {
|
|
1809
1934
|
session.close();
|
|
@@ -1814,20 +1939,36 @@ var DockerTemplate = class DockerTemplate {
|
|
|
1814
1939
|
session
|
|
1815
1940
|
};
|
|
1816
1941
|
}
|
|
1817
|
-
#followBuild(docker, stream) {
|
|
1942
|
+
#followBuild(docker, stream, abortSignal) {
|
|
1818
1943
|
return new Promise((resolve, reject) => {
|
|
1944
|
+
let settled = false;
|
|
1945
|
+
const finish = (error) => {
|
|
1946
|
+
if (settled) return;
|
|
1947
|
+
settled = true;
|
|
1948
|
+
abortSignal?.removeEventListener("abort", abort);
|
|
1949
|
+
error === void 0 ? resolve() : reject(error);
|
|
1950
|
+
};
|
|
1951
|
+
const abort = () => {
|
|
1952
|
+
stream.destroy?.();
|
|
1953
|
+
if (abortSignal) finish(createAbortError(abortSignal, "build Docker template"));
|
|
1954
|
+
};
|
|
1955
|
+
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
1956
|
+
if (abortSignal?.aborted) {
|
|
1957
|
+
abort();
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1819
1960
|
docker.modem.followProgress(stream, (err, output) => {
|
|
1820
1961
|
if (err) {
|
|
1821
|
-
|
|
1962
|
+
finish(err);
|
|
1822
1963
|
return;
|
|
1823
1964
|
}
|
|
1824
1965
|
const failure = output?.find((entry) => entry && (entry.error !== void 0 || entry.errorDetail !== void 0));
|
|
1825
1966
|
if (failure) {
|
|
1826
1967
|
const detail = failure.errorDetail;
|
|
1827
|
-
|
|
1968
|
+
finish(new Error(String(detail?.message ?? failure.error ?? "docker build failed")));
|
|
1828
1969
|
return;
|
|
1829
1970
|
}
|
|
1830
|
-
|
|
1971
|
+
finish();
|
|
1831
1972
|
});
|
|
1832
1973
|
});
|
|
1833
1974
|
}
|
|
@@ -1976,16 +2117,31 @@ function createDockerRepoTemplate(options) {
|
|
|
1976
2117
|
if (options.ref !== void 0 && !REF_PATTERN.test(options.ref)) throw new Error(`Invalid ref '${options.ref}': expected a git ref name`);
|
|
1977
2118
|
const workingDirectory = trimTrailingSlashes(options.workingDirectory ?? DEFAULT_WORKING_DIRECTORY);
|
|
1978
2119
|
if (!workingDirectory.startsWith("/")) throw new Error(`workingDirectory must be an absolute path, got '${options.workingDirectory}'`);
|
|
1979
|
-
return () => resolveRepoTemplate(options, workingDirectory);
|
|
2120
|
+
return (resolveOptions) => resolveRepoTemplate(options, workingDirectory, resolveOptions);
|
|
1980
2121
|
}
|
|
1981
|
-
async function resolveRepoTemplate(options, workingDirectory) {
|
|
1982
|
-
const
|
|
2122
|
+
async function resolveRepoTemplate(options, workingDirectory, resolveOptions = {}) {
|
|
2123
|
+
const { abortSignal } = resolveOptions;
|
|
2124
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
2125
|
+
let access;
|
|
2126
|
+
try {
|
|
2127
|
+
access = await options.getRepositoryAccess(resolveOptions);
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
throw normalizeAbortError(error, "resolve Docker repository template");
|
|
2130
|
+
}
|
|
2131
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
1983
2132
|
const cloneUrl = access?.cloneUrl;
|
|
1984
2133
|
if (!cloneUrl) throw new Error("Repo template has no clone URL: repository access returned none.");
|
|
1985
2134
|
assertCloneUrl(cloneUrl);
|
|
1986
2135
|
const token = access?.authorization?.token;
|
|
1987
|
-
|
|
1988
|
-
|
|
2136
|
+
let buildEnv;
|
|
2137
|
+
try {
|
|
2138
|
+
buildEnv = typeof options.buildEnv === "function" ? await options.buildEnv(resolveOptions) : options.buildEnv;
|
|
2139
|
+
} catch (error) {
|
|
2140
|
+
throw normalizeAbortError(error, "resolve Docker repository template");
|
|
2141
|
+
}
|
|
2142
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
2143
|
+
const sha = await resolveHead(cloneUrl, options.ref, token, abortSignal);
|
|
2144
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
1989
2145
|
if (!sha) throw new Error(`Could not resolve ${options.ref ?? "HEAD"} of ${cloneUrl} with git ls-remote; check the ref, the credential and network access`);
|
|
1990
2146
|
return buildRepoTemplate({
|
|
1991
2147
|
cloneUrl,
|
|
@@ -2034,7 +2190,8 @@ function buildRepoTemplate(inputs) {
|
|
|
2034
2190
|
* yields undefined; the caller decides how to surface it.
|
|
2035
2191
|
* @internal exported for tests.
|
|
2036
2192
|
*/
|
|
2037
|
-
async function resolveHead(cloneUrl, ref, token) {
|
|
2193
|
+
async function resolveHead(cloneUrl, ref, token, abortSignal) {
|
|
2194
|
+
throwIfAborted(abortSignal, "resolve Docker repository template");
|
|
2038
2195
|
if (ref && FULL_SHA_PATTERN.test(ref)) return ref.toLowerCase();
|
|
2039
2196
|
try {
|
|
2040
2197
|
const authEnv = token ? {
|
|
@@ -2049,6 +2206,7 @@ async function resolveHead(cloneUrl, ref, token) {
|
|
|
2049
2206
|
ref ?? "HEAD"
|
|
2050
2207
|
], {
|
|
2051
2208
|
timeout: 1e4,
|
|
2209
|
+
signal: abortSignal,
|
|
2052
2210
|
env: {
|
|
2053
2211
|
...process.env,
|
|
2054
2212
|
...authEnv,
|
|
@@ -2058,7 +2216,8 @@ async function resolveHead(cloneUrl, ref, token) {
|
|
|
2058
2216
|
const lines = stdout.split("\n").filter(Boolean).map((line) => line.split(" "));
|
|
2059
2217
|
const sha = (lines.find(([, name]) => name?.endsWith("^{}")) ?? lines[0])?.[0]?.trim();
|
|
2060
2218
|
return sha && SHA_PATTERN.test(sha) ? sha.toLowerCase() : void 0;
|
|
2061
|
-
} catch {
|
|
2219
|
+
} catch (error) {
|
|
2220
|
+
if (abortSignal?.aborted) throw normalizeAbortError(error, "resolve Docker repository template");
|
|
2062
2221
|
return;
|
|
2063
2222
|
}
|
|
2064
2223
|
}
|