@outerlayer/cli 0.6.0 → 0.8.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +15 -5
  3. package/dist/{agent-setup-USZ2SAXC.js → agent-setup-C7PLRXZO.js} +7 -6
  4. package/dist/{build-PAEKVYVK.js → build-MS34XEGN.js} +3 -1
  5. package/dist/build-info.json +1 -1
  6. package/dist/builder-G52A6GW2.js +13 -0
  7. package/dist/{check-CCVPBN7A.js → check-FOF3PUKI.js} +35 -26
  8. package/dist/{chunk-ZILGTTY6.js → chunk-3S4P55NE.js} +3 -3
  9. package/dist/{chunk-MIGEDH5P.js → chunk-4A5E2G4E.js} +9 -9
  10. package/dist/chunk-4ACZCY4I.js +39 -0
  11. package/dist/{chunk-OIRNH6UO.js → chunk-5FWNEQNZ.js} +3 -3
  12. package/dist/{chunk-BNMCXPG7.js → chunk-5QLNZGAQ.js} +12 -3
  13. package/dist/chunk-5TNS3PL7.js +263 -0
  14. package/dist/chunk-7RQ73DLO.js +23 -0
  15. package/dist/{chunk-VMJ3MMBX.js → chunk-7VNURP6M.js} +538 -46
  16. package/dist/{chunk-EMY4I27X.js → chunk-7ZR5UPMB.js} +555 -5
  17. package/dist/{chunk-UMFWVGJO.js → chunk-AIL2R4SI.js} +4 -3
  18. package/dist/{chunk-NQXFAGHU.js → chunk-AX23QKHS.js} +7 -3
  19. package/dist/{chunk-RZSF5B46.js → chunk-CM5QTKM4.js} +152 -158
  20. package/dist/chunk-CX22S5BA.js +45 -0
  21. package/dist/{chunk-H5NCNVDA.js → chunk-EV3M3EH7.js} +2 -2
  22. package/dist/{chunk-ANK2B36Y.js → chunk-GAYWY4GI.js} +6190 -28615
  23. package/dist/chunk-HKMPVPO7.js +55 -0
  24. package/dist/{chunk-43J3M6KB.js → chunk-IJBVPLZZ.js} +91 -39
  25. package/dist/{chunk-PMAMOSTP.js → chunk-ILEA3YHM.js} +85 -25
  26. package/dist/{chunk-MXB5VB4Y.js → chunk-IRCBTGWF.js} +6 -6
  27. package/dist/{chunk-OUUINEYB.js → chunk-JKIWMRFH.js} +1 -1
  28. package/dist/{chunk-FA2BOX4O.js → chunk-JPR2WHHF.js} +1 -1
  29. package/dist/{chunk-IASWYRVG.js → chunk-K35RW5HP.js} +4 -1
  30. package/dist/{chunk-ZMYPLFG3.js → chunk-M3SQ4YGD.js} +1 -1
  31. package/dist/{chunk-4NLKY4WR.js → chunk-NALVDFIS.js} +2 -2
  32. package/dist/{chunk-AUFG23AA.js → chunk-OD2VTIHN.js} +19 -47
  33. package/dist/{chunk-JWOQNNN2.js → chunk-OEK4JEB3.js} +33 -4
  34. package/dist/chunk-PTW6W5NR.js +127 -0
  35. package/dist/{chunk-3DD6AOWC.js → chunk-PWBILI5P.js} +28 -3
  36. package/dist/{chunk-B2G7JHHB.js → chunk-R7STSBLC.js} +1 -1
  37. package/dist/{chunk-YYYXRJUW.js → chunk-TI7B7UEO.js} +1 -1
  38. package/dist/{chunk-Z62HNKEJ.js → chunk-TTNFGMHO.js} +72 -54
  39. package/dist/{chunk-RBOMWNOG.js → chunk-TUUMW4TC.js} +2 -2
  40. package/dist/{chunk-774HEQL6.js → chunk-U3GOI2B4.js} +91 -8
  41. package/dist/{chunk-F6GFZXZ3.js → chunk-UXYLWVTQ.js} +1 -18
  42. package/dist/{chunk-WO2BXCTQ.js → chunk-VXLJORHL.js} +1 -1
  43. package/dist/{chunk-CBPPJSAR.js → chunk-WVGSB3Z2.js} +2 -2
  44. package/dist/chunk-YNJEOGDL.js +22434 -0
  45. package/dist/{chunk-W4SQCOZU.js → chunk-ZOAEPWGL.js} +1 -1
  46. package/dist/{chunk-3DEVYTNQ.js → chunk-ZYHKJKSU.js} +1 -1
  47. package/dist/{chunk-WIOZAJ2W.js → chunk-ZZXLM64C.js} +1 -1
  48. package/dist/{cli-7WCNPBYC.js → cli-AOJPAVFX.js} +109 -81
  49. package/dist/{command-ARFK3OYF.js → command-SY3SVLAI.js} +67 -27
  50. package/dist/config-Z3USDBLU.js +11 -0
  51. package/dist/{connect-cmd-PCESZLTR.js → connect-cmd-PNZ6IJLJ.js} +4 -4
  52. package/dist/{context-adopt-G5JAELE7.js → context-adopt-CE4KDXS3.js} +5 -4
  53. package/dist/{context-materialize-3HLPZAF6.js → context-materialize-QL33HDJN.js} +5 -4
  54. package/dist/{dist-RDLP4GGJ.js → dist-3ZTZF2TP.js} +2 -1
  55. package/dist/docker-676FD3CQ.js +15 -0
  56. package/dist/{doctor-VOMYOHQU.js → doctor-WR6TRPSU.js} +49 -19
  57. package/dist/{emit-artifact-cmd-TBF46VXZ.js → emit-artifact-cmd-AVM4YXMQ.js} +11 -4
  58. package/dist/{emit-cmd-DCERAU27.js → emit-cmd-2QRMYPYO.js} +4 -3
  59. package/dist/{emit-criteria-cmd-3427XKFW.js → emit-criteria-cmd-LDBE4YDS.js} +10 -1
  60. package/dist/{emit-finding-cmd-GSI75K36.js → emit-finding-cmd-6R5WBPCH.js} +12 -1
  61. package/dist/{emit-result-cmd-3PRQKB55.js → emit-result-cmd-2DMN6O6U.js} +13 -6
  62. package/dist/{exec-client-4XGXV3XN.js → exec-client-NPYNUAF3.js} +4 -3
  63. package/dist/{guest-init-GENRHNP7.js → guest-init-JWNNAH2H.js} +4 -3
  64. package/dist/{hook-fast-NJBBZQ5Q.js → hook-fast-XBOZ3N6L.js} +1 -1
  65. package/dist/{import-capture-cmd-AVZ4VIVJ.js → import-capture-cmd-T4556ELW.js} +3 -2
  66. package/dist/{import-ruler-cmd-GUHL6IY5.js → import-ruler-cmd-SG3UVGI7.js} +2 -1
  67. package/dist/index.js +3 -3
  68. package/dist/init-KDK5QLQZ.js +46 -0
  69. package/dist/{init-OHYFIKZD.js → init-R2FJ5KNS.js} +35 -21
  70. package/dist/{init-cmd-HMFNIFPI.js → init-cmd-BYZYAJ5P.js} +10 -9
  71. package/dist/{install-DSUZE2TJ.js → install-FHTVJZS6.js} +7 -5
  72. package/dist/{install-cmd-453C7IZT.js → install-cmd-DCF7TVXD.js} +3 -2
  73. package/dist/{logs-7RYUH5A6.js → logs-H55NWW4H.js} +1 -0
  74. package/dist/{loop-UAJB34KA.js → loop-AG3PLXPO.js} +35 -26
  75. package/dist/machine-LV6WBRRK.js +45 -0
  76. package/dist/{session-sender-ED26V45B.js → session-sender-Q5HAMOL2.js} +6 -5
  77. package/dist/{starter-pack-LIZYMKYQ.js → starter-pack-MUXMAU4S.js} +4 -3
  78. package/dist/{status-6WNXX2O7.js → status-HH4OSOHR.js} +29 -20
  79. package/dist/{sync-cmd-YONVGUYS.js → sync-cmd-CAEPJR7B.js} +5 -4
  80. package/dist/{watch-HHKAF26L.js → watch-LAAZFBHH.js} +5 -4
  81. package/dist/{work-claim-cmd-LXBZTMJA.js → work-claim-cmd-ARIYZARE.js} +15 -7
  82. package/dist/{work-cmd-OVNF44FY.js → work-cmd-B3FOXZAL.js} +9 -3
  83. package/dist/{work-comment-cmd-6DGQNTAH.js → work-comment-cmd-O5WGRI6K.js} +9 -3
  84. package/dist/{work-open-pr-cmd-FCZHFHZS.js → work-open-pr-cmd-M6CQW7KW.js} +10 -4
  85. package/dist/{work-pr-cmd-QJJVHZDC.js → work-pr-cmd-C4YEGKD6.js} +9 -2
  86. package/package.json +1 -1
  87. package/skill-pack/template/build/scripts/record-criteria.mjs +2 -1
  88. package/dist/chunk-7DBI6SNT.js +0 -55
  89. package/dist/config-HL2AIR3K.js +0 -9
  90. package/dist/docker-NEGDLU6D.js +0 -7
  91. package/dist/init-WVR4BZQ4.js +0 -36
  92. package/dist/machine-2JOWGRSZ.js +0 -35
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { isInLayout, packageRootIn, versionDir, switchCurrent, execStartLine } from './chunk-YYYXRJUW.js';
3
+ import { writeLauncher, LauncherWriteError, pathNotes, launcherPath } from './chunk-PTW6W5NR.js';
4
+ import { isInLayout, packageRootIn, versionDir, switchCurrent, currentLink, execStartLine } from './chunk-TI7B7UEO.js';
4
5
  import { RUNNER_DIR_MODE } from './chunk-ZM2IMMYN.js';
5
6
  import { realpathSync, readFileSync, mkdirSync, rmSync, cpSync, existsSync } from 'fs';
6
7
  import { dirname, sep, join } from 'path';
@@ -31,7 +32,7 @@ function runRunnerInstall(args) {
31
32
  if (manifest.name !== "@outerlayer/cli") throw new RunnerInstallError(`${packageRoot} is not the @outerlayer/cli package`);
32
33
  const version = manifest.version;
33
34
  if (typeof version !== "string" || version === "") throw new RunnerInstallError(`${packageRoot}/package.json has no version`);
34
- if (!isInLayout(configPath, entry)) {
35
+ if (!isInLayout(configPath, entry) && !sameBuildInstalled(packageRoot, packageRootIn(versionDir(configPath, version)))) {
35
36
  const target = packageRootIn(versionDir(configPath, version));
36
37
  mkdirSync(dirname(target), { recursive: true, mode: RUNNER_DIR_MODE });
37
38
  rmSync(target, { recursive: true, force: true });
@@ -39,7 +40,31 @@ function runRunnerInstall(args) {
39
40
  carryHoistedDevcontainerCli(packageRoot, target);
40
41
  }
41
42
  switchCurrent(configPath, version);
42
- return { version, execStart: execStartLine(configPath, nodePath) };
43
+ let written;
44
+ try {
45
+ written = writeLauncher({ home: args.home, configPath, nodePath, replace: args.replace });
46
+ } catch (error) {
47
+ if (error instanceof LauncherWriteError) throw new RunnerInstallError(error.message);
48
+ throw error;
49
+ }
50
+ return {
51
+ version,
52
+ execStart: execStartLine(configPath, nodePath),
53
+ launcher: {
54
+ path: launcherPath(args.home),
55
+ state: written.state,
56
+ follows: currentLink(configPath),
57
+ notes: pathNotes({ home: args.home, PATH: args.PATH, state: written.state, launcher: written.path })
58
+ }
59
+ };
60
+ }
61
+ function sameBuildInstalled(source, target) {
62
+ try {
63
+ const wanted = readFileSync(join(source, "dist", "build-info.json"), "utf8");
64
+ return wanted !== "" && wanted === readFileSync(join(target, "dist", "build-info.json"), "utf8");
65
+ } catch {
66
+ return false;
67
+ }
43
68
  }
44
69
  function carryHoistedDevcontainerCli(packageRoot, target) {
45
70
  const dependency = join("@devcontainers", "cli");
@@ -50,5 +75,9 @@ function carryHoistedDevcontainerCli(packageRoot, target) {
50
75
  mkdirSync(dirname(carried), { recursive: true, mode: RUNNER_DIR_MODE });
51
76
  cpSync(hoisted, carried, { recursive: true });
52
77
  }
78
+ function launcherSummary(launcher) {
79
+ if (launcher.state === "foreign") return void 0;
80
+ return `${launcher.state === "unchanged" ? "outerlayer command already at" : "wrote the outerlayer command at"} ${launcher.path}, which runs the CLI that ${launcher.follows} names`;
81
+ }
53
82
 
54
- export { RunnerInstallError, runRunnerInstall };
83
+ export { RunnerInstallError, launcherSummary, runRunnerInstall };
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { currentLink } from './chunk-TI7B7UEO.js';
4
+ import { readFileSync, statSync, chmodSync, mkdirSync, writeFileSync, renameSync, rmSync, existsSync, lstatSync, accessSync, constants, realpathSync } from 'fs';
5
+ import { randomBytes } from 'crypto';
6
+ import { join, dirname, delimiter, resolve } from 'path';
7
+
8
+ createRequire(import.meta.url);
9
+ var LAUNCHER_MARKER = "# outerlayer-runner-launcher";
10
+ var LauncherWriteError = class extends Error {
11
+ };
12
+ var PATH_LINE = 'export PATH="$HOME/.local/bin:$PATH"';
13
+ var ADD_TO_PATH = `${PATH_LINE} (in ~/.profile, ~/.bashrc or your shell's startup file; the runner never edits those)`;
14
+ function launcherPath(home) {
15
+ return join(home, ".local", "bin", "outerlayer");
16
+ }
17
+ function quoted(text) {
18
+ return `'${text.replace(/'/g, `'\\''`)}'`;
19
+ }
20
+ function launcherText(configPath, nodePath) {
21
+ const entry = join(currentLink(configPath), "node_modules", "@outerlayer", "cli", "dist", "index.js");
22
+ return [
23
+ "#!/bin/sh",
24
+ LAUNCHER_MARKER,
25
+ "# Written by `outerlayer runner install`. Runs the CLI the runner's `current` link names, so a self-update changes",
26
+ "# what this runs without changing this file.",
27
+ `exec ${quoted(nodePath)} ${quoted(entry)} "$@"`,
28
+ ""
29
+ ].join("\n");
30
+ }
31
+ function ownerOf(path) {
32
+ try {
33
+ lstatSync(path);
34
+ } catch {
35
+ return "absent";
36
+ }
37
+ try {
38
+ return readFileSync(path, "utf8").includes(LAUNCHER_MARKER) ? "ours" : "foreign";
39
+ } catch {
40
+ return "foreign";
41
+ }
42
+ }
43
+ function writeLauncher(input) {
44
+ const path = launcherPath(input.home);
45
+ const text = launcherText(input.configPath, input.nodePath);
46
+ const owner = ownerOf(path);
47
+ if (owner === "foreign" && input.replace !== true) return { path, state: "foreign" };
48
+ if (owner === "ours" && readFileSync(path, "utf8") === text) {
49
+ if ((statSync(path).mode & 493) !== 493) chmodSync(path, 493);
50
+ return { path, state: "unchanged" };
51
+ }
52
+ try {
53
+ mkdirSync(dirname(path), { recursive: true });
54
+ const temporary = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.new`;
55
+ try {
56
+ writeFileSync(temporary, text, { mode: 493, flag: "wx" });
57
+ chmodSync(temporary, 493);
58
+ renameSync(temporary, path);
59
+ } catch (error) {
60
+ rmSync(temporary, { force: true });
61
+ throw error;
62
+ }
63
+ } catch (error) {
64
+ throw new LauncherWriteError(`cannot write the outerlayer command at ${path}: ${error instanceof Error ? error.message : String(error)}`);
65
+ }
66
+ return { path, state: "written" };
67
+ }
68
+ function firstOnPath(PATH) {
69
+ for (const dir of (PATH ?? "").split(delimiter)) {
70
+ if (dir === "") continue;
71
+ const candidate = join(dir, "outerlayer");
72
+ try {
73
+ if (!statSync(candidate).isFile()) continue;
74
+ accessSync(candidate, constants.X_OK);
75
+ return candidate;
76
+ } catch {
77
+ continue;
78
+ }
79
+ }
80
+ return void 0;
81
+ }
82
+ function sameFile(a, b) {
83
+ try {
84
+ return realpathSync(a) === realpathSync(b);
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ function onPath(PATH, dir) {
90
+ return (PATH ?? "").split(delimiter).some((entry) => entry !== "" && resolve(entry) === resolve(dir));
91
+ }
92
+ function pathNotes(input) {
93
+ const notes = [];
94
+ if (input.state === "foreign") {
95
+ notes.push(`${input.launcher} exists and the runner did not write it, so it was left alone. Run \`outerlayer runner install --replace\` to replace it`);
96
+ return notes;
97
+ }
98
+ if (!onPath(input.PATH, dirname(input.launcher))) {
99
+ notes.push(`${dirname(input.launcher)} is not on PATH, so a shell will not find the launcher. Add it with: ${ADD_TO_PATH}`);
100
+ return notes;
101
+ }
102
+ const first = firstOnPath(input.PATH);
103
+ if (first !== void 0 && !sameFile(first, input.launcher)) {
104
+ notes.push(`${first} comes before ${input.launcher} on PATH, so a shell runs that one instead. Put the launcher's directory first with: ${ADD_TO_PATH}`);
105
+ }
106
+ return notes;
107
+ }
108
+ function launcherCheck(input) {
109
+ const name = "outerlayer on PATH";
110
+ const launcher = launcherPath(input.home);
111
+ if (!existsSync(launcher) || ownerOf(launcher) !== "ours") return void 0;
112
+ const first = firstOnPath(input.PATH);
113
+ if (first === void 0) {
114
+ return { name, status: "warn", detail: `no outerlayer is on PATH; the launcher is at ${launcher}`, fix: `add its directory to PATH: ${ADD_TO_PATH}` };
115
+ }
116
+ if (sameFile(first, launcher)) {
117
+ return { name, status: "pass", detail: `${launcher} is the first outerlayer on PATH, so it runs the version the runner runs` };
118
+ }
119
+ return {
120
+ name,
121
+ status: "warn",
122
+ detail: `${first} comes before ${launcher} on PATH, so a shell runs that one, which may not be the version the runner runs`,
123
+ fix: `remove ${first}, or put the launcher's directory first: ${ADD_TO_PATH}`
124
+ };
125
+ }
126
+
127
+ export { LauncherWriteError, launcherCheck, launcherPath, pathNotes, writeLauncher };
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { parseSize, MAX_ALLOW_HOSTS, allowHostProblem } from './chunk-F6GFZXZ3.js';
4
- import { isRefusedInConfig, resolveDestinations, isReservedVariable } from './chunk-AUFG23AA.js';
3
+ import { MAX_ALLOW_HOSTS, allowHostProblem } from './chunk-UXYLWVTQ.js';
4
+ import { parseSize } from './chunk-7RQ73DLO.js';
5
+ import { isRefusedInConfig, resolveDestinations, isReservedVariable } from './chunk-OD2VTIHN.js';
5
6
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
6
7
  import { existsSync, statSync, accessSync, constants, readFileSync } from 'fs';
7
8
  import { hostname } from 'os';
@@ -30,6 +31,9 @@ var DEFAULT_CONCURRENCY = 1;
30
31
  var DEFAULT_POLL_SECONDS = 60;
31
32
  var DEFAULT_TIME_LIMIT_MINUTES = 480;
32
33
  var DEFAULT_IDLE_LIMIT_MINUTES = 30;
34
+ var DEFAULT_CHECK_TIME_LIMIT_MINUTES = 10;
35
+ var CHECKS_KEYS = ["timeLimitMinutes"];
36
+ var MAX_CHECK_TIME_LIMIT_MINUTES = 240;
33
37
  var DEFAULT_BUILD = { memory: "8g", cpus: 4, pids: 4096, disk: "40g", variables: [] };
34
38
  var BUILD_KEYS = ["memory", "cpus", "pids", "disk", "variables", "destinations", "allowHosts"];
35
39
  var HOUSEKEEPING_KEYS = ["jobsKeepDays", "buildCacheMax", "minFreeDisk"];
@@ -81,6 +85,7 @@ var RUNNER_KEYS = [
81
85
  "hooks",
82
86
  "build",
83
87
  "housekeeping",
88
+ "checks",
84
89
  "autoUpdate"
85
90
  ];
86
91
  function runnerNumbers() {
@@ -199,6 +204,7 @@ function loadRunnerConfig(configPath) {
199
204
  checkBuiltinHooks(configPath, runner.hooks);
200
205
  const build = resolveBuild(configPath, runner.build);
201
206
  const housekeeping = resolveHousekeeping(configPath, runner.housekeeping, build.disk);
207
+ const checks = resolveChecks(configPath, runner.checks);
202
208
  if (config.scrub?.literals !== void 0 && !Array.isArray(config.scrub.literals)) {
203
209
  fail(configPath, "scrub.literals", "must be a list of strings");
204
210
  }
@@ -226,6 +232,7 @@ function loadRunnerConfig(configPath) {
226
232
  },
227
233
  build,
228
234
  housekeeping,
235
+ checks,
229
236
  // A host with executables of its own for provision and cleanup chose
230
237
  // its own environment, and an update could change what its hooks run
231
238
  // under; it opts in. The built-in hooks are the runner's own. Provision
@@ -275,6 +282,24 @@ function resolveHousekeeping(configPath, block, buildDisk) {
275
282
  if (minFreeDisk === void 0) fail(configPath, "runner.housekeeping.minFreeDisk", 'must be a size such as "40g"');
276
283
  return { jobsKeepDays: keys.jobsKeepDays ?? DEFAULT_JOBS_KEEP_DAYS, buildCacheMax, minFreeDisk };
277
284
  }
285
+ function resolveChecks(configPath, block) {
286
+ const given = block ?? {};
287
+ if (typeof given !== "object" || Array.isArray(given)) fail(configPath, "runner.checks", "must be an object");
288
+ const keys = given;
289
+ for (const key of Object.keys(keys)) {
290
+ if (CHECKS_KEYS.includes(key)) continue;
291
+ const near = nearestKey(key, CHECKS_KEYS);
292
+ fail(configPath, `runner.checks.${key}`, near ? `is not a key this block accepts; did you mean ${near}?` : "is not a key this block accepts");
293
+ }
294
+ const limit = keys.timeLimitMinutes === void 0 ? DEFAULT_CHECK_TIME_LIMIT_MINUTES : keys.timeLimitMinutes;
295
+ if (!Number.isInteger(limit) || limit < 1) {
296
+ fail(configPath, "runner.checks.timeLimitMinutes", "must be a whole number of at least 1");
297
+ }
298
+ if (limit > MAX_CHECK_TIME_LIMIT_MINUTES) {
299
+ fail(configPath, "runner.checks.timeLimitMinutes", `must be at most ${MAX_CHECK_TIME_LIMIT_MINUTES}`);
300
+ }
301
+ return { timeLimitMinutes: limit };
302
+ }
278
303
  function wholeBytes(bytes) {
279
304
  if (bytes === void 0) return void 0;
280
305
  const whole = Math.floor(bytes);
@@ -334,4 +359,4 @@ function resolveBuild(configPath, block) {
334
359
  };
335
360
  }
336
361
 
337
- export { RunnerConfigError, builtinKind, checkHooks, defaultRunnerConfigPath, hasRunnerBlock, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, loadRunnerConfig, resolveBuild, runnerVmOf };
362
+ export { DEFAULT_CHECK_TIME_LIMIT_MINUTES, RunnerConfigError, builtinKind, checkHooks, defaultRunnerConfigPath, hasRunnerBlock, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, loadRunnerConfig, resolveBuild, resolveHousekeeping, runnerVmOf };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { RELAY_PORT, GATEWAY_SOCKET, GATEWAY_PORT, CLAUDE_SOCKET, CLAUDE_PORT, DESTINATION_BASE_PORT } from './chunk-AUFG23AA.js';
3
+ import { RELAY_PORT, GATEWAY_SOCKET, GATEWAY_PORT, CLAUDE_SOCKET, CLAUDE_PORT, DESTINATION_BASE_PORT } from './chunk-OD2VTIHN.js';
4
4
  import { symlinkSync, unlinkSync } from 'fs';
5
5
  import { join } from 'path';
6
6
 
@@ -98,4 +98,4 @@ function execStartLine(configPath, nodePath) {
98
98
  return `ExecStart=${execStartCommand(configPath, nodePath)}`;
99
99
  }
100
100
 
101
- export { cleanVersions, currentVersion, entryIn, execStartCommand, execStartLine, isInLayout, packageRootIn, readMarker, removeMarker, stagingDir, startedThroughCurrent, switchCurrent, versionDir, writeMarker };
101
+ export { cleanVersions, currentLink, currentVersion, entryIn, execStartCommand, execStartLine, isInLayout, packageRootIn, readMarker, removeMarker, stagingDir, startedThroughCurrent, switchCurrent, versionDir, writeMarker };
@@ -2,6 +2,8 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { startTimeOf, claimPidFile, RunnerAlreadyRunningError, releasePidFile } from './chunk-N5FOE5PS.js';
4
4
  import { RecipeError, locateRecipe, readRecipe, assertBuildable, writeSanitizedRecipe, classifyProperties, SANITIZED_NAME } from './chunk-QOZKPLVJ.js';
5
+ import { CLAUDE_CODE_VERSION, NODE_IMAGE, DEFAULT_BASE_IMAGE, TINI_VERSION, GH_VERSION, GH_SHA256, TINI_SHA256 } from './chunk-CX22S5BA.js';
6
+ import { withGitConfig } from './chunk-HKMPVPO7.js';
5
7
  import { RUNNER_DIR_MODE, repoCacheDir, imagesDir, attemptStamp, runnerDir, rootfsPathFor, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
6
8
  import { mkdirSync, rmSync, existsSync, mkdtempSync, cpSync, readFileSync, writeFileSync, readdirSync, lstatSync, readlinkSync } from 'fs';
7
9
  import { tmpdir } from 'os';
@@ -20,10 +22,10 @@ function tokenHeader(token) {
20
22
  return `Authorization: Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`;
21
23
  }
22
24
  function tokenSettings(remote, token) {
23
- return { GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: `http.${remote}.extraHeader`, GIT_CONFIG_VALUE_0: tokenHeader(token) };
25
+ return (env) => withGitConfig(env, `http.${remote}.extraHeader`, tokenHeader(token));
24
26
  }
25
- function git(cwd, args, timeoutMs, extraEnv = {}) {
26
- const env = { ...gitEnvironment(process.env), ...extraEnv };
27
+ function git(cwd, args, timeoutMs, extend = (env) => env) {
28
+ const env = extend(gitEnvironment(process.env));
27
29
  return new Promise((resolve, reject) => {
28
30
  execFile(
29
31
  "git",
@@ -44,7 +46,7 @@ function defaultBranchOf(lsRemote) {
44
46
  async function fetchDefaultBranch(opts) {
45
47
  const created = !existsSync(opts.dir);
46
48
  mkdirSync(opts.dir, { recursive: true, mode: RUNNER_DIR_MODE });
47
- const credential = opts.token === void 0 ? {} : tokenSettings(opts.remote, opts.token);
49
+ const credential = opts.token === void 0 ? void 0 : tokenSettings(opts.remote, opts.token);
48
50
  const over = (args) => git(opts.dir, args, void 0, credential);
49
51
  try {
50
52
  const defaultBranch = defaultBranchOf(await over(["ls-remote", "--symref", opts.remote, "HEAD"]));
@@ -84,6 +86,31 @@ async function readDefaultBranchHead(remote, dir) {
84
86
  }
85
87
  return out.match(/^([0-9a-f]{40}|[0-9a-f]{64})\tHEAD$/m)?.[1];
86
88
  }
89
+ async function readBranchHead(input) {
90
+ const credential = input.token === void 0 ? void 0 : tokenSettings(input.remote, input.token);
91
+ mkdirSync(input.dir, { recursive: true, mode: RUNNER_DIR_MODE });
92
+ let out;
93
+ try {
94
+ out = await git(input.dir, ["ls-remote", "--heads", input.remote, `refs/heads/${input.branch}`], HEAD_TIMEOUT_MS, credential);
95
+ } catch (err) {
96
+ return { kind: "unreadable", reason: (err instanceof Error ? err.message : String(err)).replace(/^git [\w-]+ failed: /, "").split("\n")[0] };
97
+ }
98
+ const sha = out.match(/^([0-9a-f]{40}|[0-9a-f]{64})\trefs\/heads\//m)?.[1];
99
+ return sha === void 0 ? { kind: "absent" } : { kind: "found", sha };
100
+ }
101
+ async function fetchCommit(input) {
102
+ const credential = input.token === void 0 ? void 0 : tokenSettings(input.remote, input.token);
103
+ try {
104
+ mkdirSync(input.dir, { recursive: true, mode: RUNNER_DIR_MODE });
105
+ await git(input.dir, ["init", "--quiet"]);
106
+ await git(input.dir, ["fetch", "--quiet", "--depth", "1", "--no-tags", input.remote, input.commit], CLONE_TIMEOUT_MS, credential);
107
+ await git(input.dir, ["checkout", "--quiet", "--detach", "--force", "FETCH_HEAD"]);
108
+ return { ok: true };
109
+ } catch (err) {
110
+ const detail = (err instanceof Error ? err.message : String(err)).replace(/^git [\w-]+ failed: /, "").split("\n")[0];
111
+ return { ok: false, reason: `the fetch of ${input.commit.slice(0, 8)} from ${input.remote} failed: ${detail}` };
112
+ }
113
+ }
87
114
  var CLONE_TIMEOUT_MS = 6e5;
88
115
  async function cloneRepository(remote, dest, timeoutMs = CLONE_TIMEOUT_MS) {
89
116
  mkdirSync(dirname(dest), { recursive: true, mode: RUNNER_DIR_MODE });
@@ -97,44 +124,6 @@ async function cloneRepository(remote, dest, timeoutMs = CLONE_TIMEOUT_MS) {
97
124
  }
98
125
  }
99
126
 
100
- // src/runner/image/pins.ts
101
- var CLAUDE_CODE_VERSION = "2.1.284";
102
- var DEFAULT_BASE_IMAGE = "ubuntu:24.04@sha256:008173c23f95b170204355c12626cb5a965d779a7e1283b09e9cffbb1bf33ca3";
103
- var NODE_IMAGE = "node:22-bookworm-slim@sha256:43ac6c60b8f89723f746e8a92ce91abd5017e627ce1ddfe4238355d3a30b772c";
104
- var GH_VERSION = "2.101.0";
105
- var GH_SHA256 = {
106
- amd64: "9bca2d1c16825f109907a23307628a2f0698fbf99662b73a5cf0b020293072b8",
107
- arm64: "b57e8063f18862647c9d22727c32e9da1b963f8bf9db648fe123a6975695640f"
108
- };
109
- var TINI_VERSION = "0.19.0";
110
- var TINI_SHA256 = {
111
- amd64: "c5b0666b4cb676901f90dfcb37106783c5fe2077b04590973b885950611b30ee",
112
- arm64: "eae1d3aa50c48fb23b8cbdf4e369d0910dfc538566bfd09df89a774aa84a48b9"
113
- };
114
- var MICROVM_ARCHES = ["x86_64"];
115
- function firecrackerArch(nodeArch) {
116
- return nodeArch === "x64" ? "x86_64" : nodeArch === "arm64" ? "aarch64" : nodeArch;
117
- }
118
- var FIRECRACKER_VERSION = "1.13.1";
119
- var FIRECRACKER_SHA256 = {
120
- x86_64: "59450b9171ff2ebdf2f9a25be3a248a7ba79fb6371aec51a9d6d8eefca7b4faf"
121
- };
122
- var FIRECRACKER_BINARY_SHA256 = {
123
- x86_64: "7d3924e0c8761edafbf85cb2a94c268ffad249644a40fd02d4e15cde4c206f6e"
124
- };
125
- var GUEST_ASSETS_VERSION = "1";
126
- var GUEST_ASSETS_BASE_URL = `https://github.com/outerlayerai/guest-assets/releases/download/guest-${GUEST_ASSETS_VERSION}`;
127
- var GUEST_KERNEL_SHA256 = {
128
- x86_64: "5a2d716d301615c69fefc2f9574954e838269f14d5e8b0d1bad88b680cd372ee"
129
- };
130
- var GUEST_HELPER_SHA256 = {
131
- x86_64: "4175e89ae8cb633113b5f69ef8b6e206093954411fe53edf86357bc1b7e822f9"
132
- };
133
- function guestAssetsPinned(arch) {
134
- const zero = "0".repeat(64);
135
- return GUEST_KERNEL_SHA256[arch] !== zero && GUEST_HELPER_SHA256[arch] !== zero;
136
- }
137
-
138
127
  // src/runner/image/dockerfiles.ts
139
128
  function plain(name, value, allowed) {
140
129
  if (!allowed.test(value)) throw new Error(`${name} holds a character a Dockerfile line cannot carry safely: ${JSON.stringify(value)}`);
@@ -281,6 +270,7 @@ var KEEP_BUILD_IMAGES = 3;
281
270
  var TAG_HOST = "outerlayer.invalid";
282
271
  var KEY_PREFIX = 12;
283
272
  var LOCK_NAME = "build.lock";
273
+ var LAYOUTS_DIR = "layouts";
284
274
  function splitRepository(repository) {
285
275
  const full = repository.toLowerCase();
286
276
  const parts = full.split("/");
@@ -362,33 +352,56 @@ async function buildRepositoryImage(opts, ports) {
362
352
  (i) => i.labels[KIND2] === "build" && i.labels[RECIPE_KEY2] === rKey && i.labels[TOOLS_KEY] === tKey
363
353
  );
364
354
  const failed = (what, status) => new RecipeError("build_failed", `${what} failed (exit ${status}); the build log is at ${logPath}`, logPath);
355
+ let builderReady = false;
356
+ const ensureBuilder = async () => {
357
+ if (builderReady) return;
358
+ const builder = await host.ensureBuilder();
359
+ opts.log?.(`image build in builder ${builder.name} at ${builder.memory}`);
360
+ builderReady = true;
361
+ };
362
+ const keptLayout = join(repoRecords, LAYOUTS_DIR, rKey.slice(0, KEY_PREFIX));
363
+ const layoutKept = () => existsSync(join(keptLayout, "index.json"));
365
364
  let recipeImage = recipeImageBefore;
366
365
  let recipeBuilt = false;
367
366
  if (recipeImage === void 0 && buildImageBefore === void 0) {
367
+ await ensureBuilder();
368
+ mkdirSync(join(repoRecords, LAYOUTS_DIR), { recursive: true, mode: RUNNER_DIR_MODE });
368
369
  const labels = { [REPOSITORY]: repository, [KIND2]: "recipe", [RECIPE_KEY2]: rKey };
369
370
  if (location.kind === "default") {
370
371
  const context = mkdtempSync(join(scratchRoot, "ol-image-default-"));
371
372
  try {
372
- const status = await host.buildDockerfile({ dockerfile: defaultText, context, tag: recipeTag, labels, logPath });
373
+ const status = await host.buildDockerfile({ dockerfile: defaultText, context, tag: recipeTag, labels, logPath, layoutOut: keptLayout });
373
374
  if (status !== 0) throw failed("the docker build of the default image", status);
374
375
  } finally {
375
376
  rmSync(context, { recursive: true, force: true });
376
377
  }
377
378
  } else {
378
379
  const config = writeSanitizedRecipe(checkout.dir, location, recipe);
380
+ const scratchLayouts = mkdtempSync(join(scratchRoot, "ol-image-layout-"));
381
+ const scratchLayout = join(scratchLayouts, "layout");
379
382
  try {
380
- const status = await host.devcontainerBuild({ workspace: checkout.dir, config, imageName: recipeTag, labels, logPath });
383
+ const status = await host.devcontainerBuild({ workspace: checkout.dir, config, imageName: recipeTag, labels, logPath, layoutOut: scratchLayout });
381
384
  if (status !== 0) throw failed("devcontainer build", status);
385
+ if (!existsSync(join(scratchLayout, "index.json"))) throw failed("devcontainer build, which left no image to label,", 0);
386
+ const context = mkdtempSync(join(scratchRoot, "ol-image-label-"));
387
+ try {
388
+ const relabel = await host.buildDockerfile({
389
+ dockerfile: `FROM ${recipeTag}
390
+ `,
391
+ context,
392
+ tag: recipeTag,
393
+ labels,
394
+ logPath,
395
+ base: { ref: recipeTag, layout: scratchLayout },
396
+ layoutOut: keptLayout
397
+ });
398
+ if (relabel !== 0) throw failed("labelling the recipe image", relabel);
399
+ } finally {
400
+ rmSync(context, { recursive: true, force: true });
401
+ }
382
402
  } finally {
383
403
  rmSync(config, { force: true });
384
- }
385
- const context = mkdtempSync(join(scratchRoot, "ol-image-label-"));
386
- try {
387
- const status = await host.buildDockerfile({ dockerfile: `FROM ${recipeTag}
388
- `, context, tag: recipeTag, labels, logPath });
389
- if (status !== 0) throw failed("labelling the recipe image", status);
390
- } finally {
391
- rmSync(context, { recursive: true, force: true });
404
+ rmSync(scratchLayouts, { recursive: true, force: true });
392
405
  }
393
406
  }
394
407
  recipeImage = await host.inspect(recipeTag) ?? void 0;
@@ -397,6 +410,8 @@ async function buildRepositoryImage(opts, ports) {
397
410
  }
398
411
  let buildImage = buildImageBefore;
399
412
  if (buildImage === void 0) {
413
+ await ensureBuilder();
414
+ if (!layoutKept()) await host.exportLayout(recipeTag, keptLayout);
400
415
  const context = mkdtempSync(join(scratchRoot, "ol-image-tools-"));
401
416
  try {
402
417
  cpSync(opts.cli.distDir, join(context, "dist"), { recursive: true });
@@ -407,7 +422,7 @@ async function buildRepositoryImage(opts, ports) {
407
422
  }
408
423
  const dockerfile = toolsDockerfile({ recipeImage: recipeTag, nodeImage, claudeCodeVersion, buildId: opts.cli.buildId, guestHelper: opts.guestHelper !== void 0 });
409
424
  const labels = { [REPOSITORY]: repository, [KIND2]: "build", [RECIPE_KEY2]: rKey, [TOOLS_KEY]: tKey };
410
- const status = await host.buildDockerfile({ dockerfile, context, tag: buildTag, labels, logPath });
425
+ const status = await host.buildDockerfile({ dockerfile, context, tag: buildTag, labels, logPath, base: { ref: recipeTag, layout: keptLayout } });
411
426
  if (status !== 0) throw failed("the docker build of the tools layer", status);
412
427
  } finally {
413
428
  rmSync(context, { recursive: true, force: true });
@@ -460,6 +475,9 @@ async function prune(host, repository, repoRecords, keep, runnerDirPath) {
460
475
  rmSync(rootfsPathFor(runnerDirPath, image.id), { force: true });
461
476
  const recipeKeyLabel = image.labels[RECIPE_KEY2];
462
477
  const toolsKeyLabel = image.labels[TOOLS_KEY];
478
+ if (image.labels[KIND2] === "recipe" && recipeKeyLabel !== void 0) {
479
+ rmSync(join(repoRecords, LAYOUTS_DIR, recipeKeyLabel.slice(0, KEY_PREFIX)), { recursive: true, force: true });
480
+ }
463
481
  if (image.labels[KIND2] === "build" && recipeKeyLabel !== void 0 && toolsKeyLabel !== void 0) {
464
482
  rmSync(join(repoRecords, `${recipeKeyLabel.slice(0, KEY_PREFIX)}-${toolsKeyLabel.slice(0, KEY_PREFIX)}`), { recursive: true, force: true });
465
483
  }
@@ -467,4 +485,4 @@ async function prune(host, repository, repoRecords, keep, runnerDirPath) {
467
485
  return removed;
468
486
  }
469
487
 
470
- export { CLAUDE_CODE_VERSION, DEFAULT_BASE_IMAGE, FIRECRACKER_BINARY_SHA256, FIRECRACKER_SHA256, FIRECRACKER_VERSION, GUEST_ASSETS_BASE_URL, GUEST_ASSETS_VERSION, GUEST_HELPER_SHA256, GUEST_KERNEL_SHA256, MICROVM_ARCHES, buildRepositoryImage, cloneRepository, firecrackerArch, guestAssetsPinned, readDefaultBranchHead };
488
+ export { buildRepositoryImage, cloneRepository, fetchCommit, readBranchHead, readDefaultBranchHead };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { MIN_ENGINE_MAJOR } from './chunk-RZSF5B46.js';
4
- import { firecrackerArch, MICROVM_ARCHES, guestAssetsPinned } from './chunk-Z62HNKEJ.js';
3
+ import { MIN_ENGINE_MAJOR } from './chunk-CM5QTKM4.js';
4
+ import { firecrackerArch, MICROVM_ARCHES, guestAssetsPinned } from './chunk-CX22S5BA.js';
5
5
  import { spawnSync } from 'child_process';
6
6
  import { existsSync, accessSync, constants, statSync, readFileSync } from 'fs';
7
7
 
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { RecipeError } from './chunk-QOZKPLVJ.js';
4
+ import { trackImageBuild, isOutOfMemory, ensureBuilder } from './chunk-5TNS3PL7.js';
4
5
  import { spawn } from 'child_process';
5
- import { existsSync, mkdirSync, createWriteStream, readFileSync } from 'fs';
6
+ import { existsSync, mkdirSync, createWriteStream, rmSync, readFileSync } from 'fs';
6
7
  import { join, dirname } from 'path';
7
8
  import { createRequire as createRequire$1 } from 'module';
8
9
  import { fileURLToPath } from 'url';
@@ -48,7 +49,9 @@ function devcontainerBuildInvocation(cli, req) {
48
49
  req.imageName,
49
50
  ...Object.entries(req.labels).flatMap(([key, value]) => ["--label", `${key}=${value}`]),
50
51
  "--log-format",
51
- "text"
52
+ "text",
53
+ // Written as an OCI layout instead of loaded into Docker: the runner's builder cannot read Docker's image store, so the next layer starts FROM the layout.
54
+ ...req.layoutOut === void 0 ? [] : ["--output", `type=oci,dest=${req.layoutOut},tar=false`]
52
55
  ]
53
56
  };
54
57
  }
@@ -69,6 +72,14 @@ var CHILD_ENVIRONMENT_NAMES = [
69
72
  "DOCKER_TLS_VERIFY",
70
73
  "DOCKER_API_VERSION",
71
74
  "DOCKER_BUILDKIT",
75
+ // `docker buildx build` fetches registry tokens in the client and hands them to the builder, so a host that reaches
76
+ // registries through a proxy needs it here as well as in the builder.
77
+ "HTTP_PROXY",
78
+ "HTTPS_PROXY",
79
+ "NO_PROXY",
80
+ "http_proxy",
81
+ "https_proxy",
82
+ "no_proxy",
72
83
  "XDG_RUNTIME_DIR",
73
84
  "XDG_CONFIG_HOME",
74
85
  "XDG_DATA_HOME",
@@ -103,6 +114,7 @@ var spawnRun = (command, args, opts = {}) => new Promise((resolve) => {
103
114
  if (extra) log.write(extra);
104
115
  log.end(() => resolve(result));
105
116
  };
117
+ let timer;
106
118
  const child = spawn(command, args, {
107
119
  env: { ...childEnvironment(process.env), ...opts.env },
108
120
  stdio: [opts.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
@@ -115,9 +127,20 @@ var spawnRun = (command, args, opts = {}) => new Promise((resolve) => {
115
127
  stderr += chunk.toString();
116
128
  log?.write(chunk);
117
129
  });
130
+ if (opts.timeoutMs !== void 0) {
131
+ timer = setTimeout(() => {
132
+ child.kill("SIGKILL");
133
+ finish(124, `${command}: no answer within ${Math.round(opts.timeoutMs / 1e3)}s
134
+ `);
135
+ }, opts.timeoutMs);
136
+ timer.unref();
137
+ }
118
138
  child.on("error", (err) => finish(127, `${command}: ${err.message}
119
139
  `));
120
- child.on("close", (code, signal) => finish(code ?? (signal ? 128 : 1)));
140
+ child.on("close", (code, signal) => {
141
+ if (timer !== void 0) clearTimeout(timer);
142
+ finish(code ?? (signal ? 128 : 1));
143
+ });
121
144
  if (opts.input !== void 0) {
122
145
  child.stdin.on("error", () => void 0);
123
146
  child.stdin.end(opts.input);
@@ -127,9 +150,28 @@ function toInfo(row) {
127
150
  return { id: row.Id, createdAt: row.Created, labels: row.Config?.Labels ?? {} };
128
151
  }
129
152
  var lines = (text) => [...new Set(text.split("\n").map((l) => l.trim()).filter(Boolean))];
153
+ function layoutDigest(layout) {
154
+ const index = JSON.parse(readFileSync(join(layout, "index.json"), "utf8"));
155
+ const digest = index.manifests?.[0]?.digest;
156
+ if (digest === void 0) throw new Error(`${join(layout, "index.json")} lists no image`);
157
+ return digest;
158
+ }
159
+ function outOfMemory(builder, logPath) {
160
+ return new RecipeError(
161
+ "image_build_out_of_memory",
162
+ `the image build ran out of memory: the kernel stopped a build step at the limit of runner.build.memory (${builder.memory}). Raise runner.build.memory, or lower what the recipe's build steps use; the build log is at ${logPath}`,
163
+ logPath
164
+ );
165
+ }
130
166
  function createDockerHost(deps = {}) {
131
167
  const run = deps.run ?? spawnRun;
132
168
  const cli = deps.cli ?? resolveDevcontainerCli;
169
+ const builderFor = () => {
170
+ if (deps.builder === void 0) {
171
+ throw new Error("no image builder is configured, so the image build was not started: every image build runs in the runner's builder, which carries runner.build.memory");
172
+ }
173
+ return deps.builder;
174
+ };
133
175
  const inspectMany = async (refs) => {
134
176
  const result = await run("docker", ["image", "inspect", ...refs]);
135
177
  return result.status === 0 ? JSON.parse(result.stdout).map(toInfo) : [];
@@ -156,17 +198,58 @@ function createDockerHost(deps = {}) {
156
198
  if (containers.length === 0) return [];
157
199
  return lines((await run("docker", ["inspect", "--format", "{{.Image}}", ...containers])).stdout);
158
200
  },
201
+ async ensureBuilder() {
202
+ const builder = builderFor();
203
+ const result = await ensureBuilder(run, builder, { waitForIdle: true, replaceStale: deps.replaceStaleBuilder });
204
+ if (!result.ok) throw new RecipeError("image_builder_unavailable", `the image builder is unavailable: ${result.problem}`);
205
+ return { name: builder.name, memory: builder.memory };
206
+ },
207
+ async exportLayout(ref, dir) {
208
+ rmSync(dir, { recursive: true, force: true });
209
+ mkdirSync(dir, { recursive: true });
210
+ const archive = join(dir, ".image.tar");
211
+ const saved = await run("docker", ["save", "--output", archive, ref]);
212
+ if (saved.status !== 0) throw new Error(saved.stderr.trim() || `docker save ${ref} exited ${saved.status}`);
213
+ const unpacked = await run("tar", ["-xf", archive, "-C", dir]);
214
+ rmSync(archive, { force: true });
215
+ if (unpacked.status !== 0) throw new Error(unpacked.stderr.trim() || `tar exited ${unpacked.status}`);
216
+ },
159
217
  async buildDockerfile(req) {
218
+ const builder = builderFor();
219
+ if (req.layoutOut !== void 0) {
220
+ rmSync(req.layoutOut, { recursive: true, force: true });
221
+ }
160
222
  const labels = Object.entries(req.labels).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
161
- const result = await run("docker", ["build", "--tag", req.tag, ...labels, "--file", "-", req.context], {
162
- logPath: req.logPath,
163
- input: req.dockerfile
164
- });
223
+ const args = [
224
+ "buildx",
225
+ "build",
226
+ "--builder",
227
+ builder.name,
228
+ "--load",
229
+ ...req.layoutOut === void 0 ? [] : ["--output", `type=oci,dest=${req.layoutOut},tar=false`],
230
+ ...req.base === void 0 ? [] : ["--build-context", `${req.base.ref}=oci-layout://${req.base.layout}@${layoutDigest(req.base.layout)}`],
231
+ "--tag",
232
+ req.tag,
233
+ ...labels,
234
+ "--file",
235
+ "-",
236
+ req.context
237
+ ];
238
+ const result = await trackImageBuild(() => run("docker", args, { logPath: req.logPath, input: req.dockerfile }));
239
+ if (result.status !== 0 && isOutOfMemory(`${result.stdout}
240
+ ${result.stderr}`)) throw outOfMemory(builder, req.logPath);
165
241
  return result.status;
166
242
  },
167
243
  async devcontainerBuild(req) {
244
+ const builder = builderFor();
168
245
  const { command, args } = devcontainerBuildInvocation(cli(), req);
169
- return (await run(command, args, { logPath: req.logPath })).status;
246
+ if (req.layoutOut !== void 0) {
247
+ rmSync(req.layoutOut, { recursive: true, force: true });
248
+ }
249
+ const result = await trackImageBuild(() => run(command, args, { logPath: req.logPath, env: { BUILDX_BUILDER: builder.name } }));
250
+ if (result.status !== 0 && isOutOfMemory(`${result.stdout}
251
+ ${result.stderr}`)) throw outOfMemory(builder, req.logPath);
252
+ return result.status;
170
253
  },
171
254
  async remove(id) {
172
255
  const result = await run("docker", ["rmi", id]);