@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.
- package/CHANGELOG.md +34 -0
- package/README.md +15 -5
- package/dist/{agent-setup-USZ2SAXC.js → agent-setup-C7PLRXZO.js} +7 -6
- package/dist/{build-PAEKVYVK.js → build-MS34XEGN.js} +3 -1
- package/dist/build-info.json +1 -1
- package/dist/builder-G52A6GW2.js +13 -0
- package/dist/{check-CCVPBN7A.js → check-FOF3PUKI.js} +35 -26
- package/dist/{chunk-ZILGTTY6.js → chunk-3S4P55NE.js} +3 -3
- package/dist/{chunk-MIGEDH5P.js → chunk-4A5E2G4E.js} +9 -9
- package/dist/chunk-4ACZCY4I.js +39 -0
- package/dist/{chunk-OIRNH6UO.js → chunk-5FWNEQNZ.js} +3 -3
- package/dist/{chunk-BNMCXPG7.js → chunk-5QLNZGAQ.js} +12 -3
- package/dist/chunk-5TNS3PL7.js +263 -0
- package/dist/chunk-7RQ73DLO.js +23 -0
- package/dist/{chunk-VMJ3MMBX.js → chunk-7VNURP6M.js} +538 -46
- package/dist/{chunk-EMY4I27X.js → chunk-7ZR5UPMB.js} +555 -5
- package/dist/{chunk-UMFWVGJO.js → chunk-AIL2R4SI.js} +4 -3
- package/dist/{chunk-NQXFAGHU.js → chunk-AX23QKHS.js} +7 -3
- package/dist/{chunk-RZSF5B46.js → chunk-CM5QTKM4.js} +152 -158
- package/dist/chunk-CX22S5BA.js +45 -0
- package/dist/{chunk-H5NCNVDA.js → chunk-EV3M3EH7.js} +2 -2
- package/dist/{chunk-ANK2B36Y.js → chunk-GAYWY4GI.js} +6190 -28615
- package/dist/chunk-HKMPVPO7.js +55 -0
- package/dist/{chunk-43J3M6KB.js → chunk-IJBVPLZZ.js} +91 -39
- package/dist/{chunk-PMAMOSTP.js → chunk-ILEA3YHM.js} +85 -25
- package/dist/{chunk-MXB5VB4Y.js → chunk-IRCBTGWF.js} +6 -6
- package/dist/{chunk-OUUINEYB.js → chunk-JKIWMRFH.js} +1 -1
- package/dist/{chunk-FA2BOX4O.js → chunk-JPR2WHHF.js} +1 -1
- package/dist/{chunk-IASWYRVG.js → chunk-K35RW5HP.js} +4 -1
- package/dist/{chunk-ZMYPLFG3.js → chunk-M3SQ4YGD.js} +1 -1
- package/dist/{chunk-4NLKY4WR.js → chunk-NALVDFIS.js} +2 -2
- package/dist/{chunk-AUFG23AA.js → chunk-OD2VTIHN.js} +19 -47
- package/dist/{chunk-JWOQNNN2.js → chunk-OEK4JEB3.js} +33 -4
- package/dist/chunk-PTW6W5NR.js +127 -0
- package/dist/{chunk-3DD6AOWC.js → chunk-PWBILI5P.js} +28 -3
- package/dist/{chunk-B2G7JHHB.js → chunk-R7STSBLC.js} +1 -1
- package/dist/{chunk-YYYXRJUW.js → chunk-TI7B7UEO.js} +1 -1
- package/dist/{chunk-Z62HNKEJ.js → chunk-TTNFGMHO.js} +72 -54
- package/dist/{chunk-RBOMWNOG.js → chunk-TUUMW4TC.js} +2 -2
- package/dist/{chunk-774HEQL6.js → chunk-U3GOI2B4.js} +91 -8
- package/dist/{chunk-F6GFZXZ3.js → chunk-UXYLWVTQ.js} +1 -18
- package/dist/{chunk-WO2BXCTQ.js → chunk-VXLJORHL.js} +1 -1
- package/dist/{chunk-CBPPJSAR.js → chunk-WVGSB3Z2.js} +2 -2
- package/dist/chunk-YNJEOGDL.js +22434 -0
- package/dist/{chunk-W4SQCOZU.js → chunk-ZOAEPWGL.js} +1 -1
- package/dist/{chunk-3DEVYTNQ.js → chunk-ZYHKJKSU.js} +1 -1
- package/dist/{chunk-WIOZAJ2W.js → chunk-ZZXLM64C.js} +1 -1
- package/dist/{cli-7WCNPBYC.js → cli-AOJPAVFX.js} +109 -81
- package/dist/{command-ARFK3OYF.js → command-SY3SVLAI.js} +67 -27
- package/dist/config-Z3USDBLU.js +11 -0
- package/dist/{connect-cmd-PCESZLTR.js → connect-cmd-PNZ6IJLJ.js} +4 -4
- package/dist/{context-adopt-G5JAELE7.js → context-adopt-CE4KDXS3.js} +5 -4
- package/dist/{context-materialize-3HLPZAF6.js → context-materialize-QL33HDJN.js} +5 -4
- package/dist/{dist-RDLP4GGJ.js → dist-3ZTZF2TP.js} +2 -1
- package/dist/docker-676FD3CQ.js +15 -0
- package/dist/{doctor-VOMYOHQU.js → doctor-WR6TRPSU.js} +49 -19
- package/dist/{emit-artifact-cmd-TBF46VXZ.js → emit-artifact-cmd-AVM4YXMQ.js} +11 -4
- package/dist/{emit-cmd-DCERAU27.js → emit-cmd-2QRMYPYO.js} +4 -3
- package/dist/{emit-criteria-cmd-3427XKFW.js → emit-criteria-cmd-LDBE4YDS.js} +10 -1
- package/dist/{emit-finding-cmd-GSI75K36.js → emit-finding-cmd-6R5WBPCH.js} +12 -1
- package/dist/{emit-result-cmd-3PRQKB55.js → emit-result-cmd-2DMN6O6U.js} +13 -6
- package/dist/{exec-client-4XGXV3XN.js → exec-client-NPYNUAF3.js} +4 -3
- package/dist/{guest-init-GENRHNP7.js → guest-init-JWNNAH2H.js} +4 -3
- package/dist/{hook-fast-NJBBZQ5Q.js → hook-fast-XBOZ3N6L.js} +1 -1
- package/dist/{import-capture-cmd-AVZ4VIVJ.js → import-capture-cmd-T4556ELW.js} +3 -2
- package/dist/{import-ruler-cmd-GUHL6IY5.js → import-ruler-cmd-SG3UVGI7.js} +2 -1
- package/dist/index.js +3 -3
- package/dist/init-KDK5QLQZ.js +46 -0
- package/dist/{init-OHYFIKZD.js → init-R2FJ5KNS.js} +35 -21
- package/dist/{init-cmd-HMFNIFPI.js → init-cmd-BYZYAJ5P.js} +10 -9
- package/dist/{install-DSUZE2TJ.js → install-FHTVJZS6.js} +7 -5
- package/dist/{install-cmd-453C7IZT.js → install-cmd-DCF7TVXD.js} +3 -2
- package/dist/{logs-7RYUH5A6.js → logs-H55NWW4H.js} +1 -0
- package/dist/{loop-UAJB34KA.js → loop-AG3PLXPO.js} +35 -26
- package/dist/machine-LV6WBRRK.js +45 -0
- package/dist/{session-sender-ED26V45B.js → session-sender-Q5HAMOL2.js} +6 -5
- package/dist/{starter-pack-LIZYMKYQ.js → starter-pack-MUXMAU4S.js} +4 -3
- package/dist/{status-6WNXX2O7.js → status-HH4OSOHR.js} +29 -20
- package/dist/{sync-cmd-YONVGUYS.js → sync-cmd-CAEPJR7B.js} +5 -4
- package/dist/{watch-HHKAF26L.js → watch-LAAZFBHH.js} +5 -4
- package/dist/{work-claim-cmd-LXBZTMJA.js → work-claim-cmd-ARIYZARE.js} +15 -7
- package/dist/{work-cmd-OVNF44FY.js → work-cmd-B3FOXZAL.js} +9 -3
- package/dist/{work-comment-cmd-6DGQNTAH.js → work-comment-cmd-O5WGRI6K.js} +9 -3
- package/dist/{work-open-pr-cmd-FCZHFHZS.js → work-open-pr-cmd-M6CQW7KW.js} +10 -4
- package/dist/{work-pr-cmd-QJJVHZDC.js → work-pr-cmd-C4YEGKD6.js} +9 -2
- package/package.json +1 -1
- package/skill-pack/template/build/scripts/record-criteria.mjs +2 -1
- package/dist/chunk-7DBI6SNT.js +0 -55
- package/dist/config-HL2AIR3K.js +0 -9
- package/dist/docker-NEGDLU6D.js +0 -7
- package/dist/init-WVR4BZQ4.js +0 -36
- 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 {
|
|
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
|
-
|
|
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 {
|
|
4
|
-
import {
|
|
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-
|
|
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
|
|
25
|
+
return (env) => withGitConfig(env, `http.${remote}.extraHeader`, tokenHeader(token));
|
|
24
26
|
}
|
|
25
|
-
function git(cwd, args, timeoutMs,
|
|
26
|
-
const env =
|
|
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 ?
|
|
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 {
|
|
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-
|
|
4
|
-
import { firecrackerArch, MICROVM_ARCHES, guestAssetsPinned } from './chunk-
|
|
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) =>
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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]);
|