@outerlayer/cli 0.20.0 → 0.22.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 +26 -0
- package/README.md +15 -5
- package/dist/{agent-setup-AM42SA2D.js → agent-setup-XAJH6MPW.js} +13 -12
- package/dist/build-GTMEADQD.js +12 -0
- package/dist/build-command-ND2ENDBS.js +50 -0
- package/dist/build-info.json +1 -1
- package/dist/{builder-C4QNTYMS.js → builder-OAUSTZHL.js} +7 -5
- package/dist/{check-BR5D27CX.js → check-3YHMJQ3P.js} +48 -50
- package/dist/{chunk-AD3EYI7P.js → chunk-22DYXLU7.js} +259 -56
- package/dist/{chunk-P53VEAOF.js → chunk-2CMJLYV6.js} +1 -1
- package/dist/{chunk-FBTGMSQF.js → chunk-2HT2XGJX.js} +16 -9
- package/dist/{chunk-S5PCA3SN.js → chunk-2J5JGLW5.js} +1 -1
- package/dist/{chunk-R22NZSGA.js → chunk-34XORKFD.js} +9 -9
- package/dist/{chunk-QOZKPLVJ.js → chunk-3W333NFO.js} +40 -1
- package/dist/{chunk-HOKXGFDR.js → chunk-4JHVPFH6.js} +22 -16
- package/dist/{chunk-AMFWYESY.js → chunk-4NM3I3VV.js} +1 -1
- package/dist/{chunk-PIZRPNKR.js → chunk-4QKLMNI3.js} +5 -5
- package/dist/{chunk-NNLA7LLQ.js → chunk-5OCIHFGE.js} +29 -5
- package/dist/{chunk-PSN33ERW.js → chunk-6IO62VJS.js} +3 -3
- package/dist/{chunk-7YP7T6GV.js → chunk-7V7KG3YM.js} +2 -2
- package/dist/{chunk-QNLX477Q.js → chunk-AEKSX4EY.js} +165 -72
- package/dist/{chunk-JHGEBPLE.js → chunk-ATRIVTRE.js} +3 -3
- package/dist/{chunk-X5H2WYMB.js → chunk-BLW6GPBZ.js} +1 -1
- package/dist/{chunk-MLAAT4S6.js → chunk-C3HKR67W.js} +3 -3
- package/dist/{chunk-XZ4H4X5F.js → chunk-C3OVGKVJ.js} +15 -8
- package/dist/{chunk-BM63HB2M.js → chunk-EAQSOE62.js} +2 -2
- package/dist/{chunk-LW7G5TJB.js → chunk-EXV37PKS.js} +2 -2
- package/dist/{chunk-B6SCN5NY.js → chunk-FGW2774C.js} +175 -44
- package/dist/{chunk-KYS3TQAG.js → chunk-G2WME4RG.js} +4 -4
- package/dist/{chunk-LRG3ZMZD.js → chunk-G6DZCKA6.js} +4 -4
- package/dist/chunk-GISNHSYL.js +41 -0
- package/dist/{chunk-G2YNJIGF.js → chunk-HCVLP7XA.js} +10 -1
- package/dist/{chunk-QEFKA2QD.js → chunk-I66MQDXH.js} +2 -2
- package/dist/{chunk-3CWUOFV6.js → chunk-IRBNIRQJ.js} +12 -7
- package/dist/{chunk-CG36IONK.js → chunk-JRCSET55.js} +2 -2
- package/dist/{chunk-CGGPS7SI.js → chunk-JVB63NIB.js} +5 -5
- package/dist/{chunk-M2ZWWTWZ.js → chunk-KDHYLBRR.js} +1 -1
- package/dist/{chunk-N2PXGQRL.js → chunk-KIGSGZBB.js} +1 -1
- package/dist/{chunk-EAEELQVQ.js → chunk-KOQELWQ6.js} +5 -5
- package/dist/{chunk-MTP2QEVO.js → chunk-KPBQYEND.js} +1 -1
- package/dist/{chunk-VYAL7PUE.js → chunk-LOB2SUM3.js} +1 -1
- package/dist/{chunk-GGOMT6KF.js → chunk-PHHBYAL6.js} +1 -1
- package/dist/{chunk-IF2OSI5T.js → chunk-QOALB6ZD.js} +2 -2
- package/dist/{chunk-ZX4I2MEK.js → chunk-TPKYTORP.js} +1 -1
- package/dist/{chunk-XNGMQ27R.js → chunk-UD3EXMKB.js} +2 -2
- package/dist/{chunk-7UWVLZXD.js → chunk-USWVXQIH.js} +1 -1
- package/dist/{chunk-BQXFBI7T.js → chunk-V7MQKOZY.js} +32 -7
- package/dist/{chunk-6X4SQ5MY.js → chunk-V7SAQ645.js} +1 -1
- package/dist/{chunk-TWF3RICI.js → chunk-VSUXCMMH.js} +110 -11
- package/dist/{chunk-FVMYKL6L.js → chunk-VWLOQ6FI.js} +1 -1
- package/dist/{chunk-3ZR7Y23Y.js → chunk-VX74ND77.js} +650 -70
- package/dist/{chunk-Z7K3L4FC.js → chunk-W3SWJ2TD.js} +2 -2
- package/dist/chunk-WERZMIKA.js +718 -0
- package/dist/{chunk-GEMQLBMW.js → chunk-WMHYIBJW.js} +6 -6
- package/dist/{chunk-5VML45HH.js → chunk-XCRZTXRL.js} +2 -2
- package/dist/{chunk-ECODQ7II.js → chunk-ZFLOGMUC.js} +1 -1
- package/dist/chunk-ZNPE7FW5.js +137 -0
- package/dist/claude-launcher-4BQNXYAF.js +267 -0
- package/dist/{cli-MKF7TU7X.js → cli-NMGVZQPB.js} +161 -120
- package/dist/{command-ZQNEPDS6.js → command-GE62AJZY.js} +32 -34
- package/dist/{config-CLSGABD5.js → config-7Q52COZ3.js} +3 -2
- package/dist/{connect-cmd-UNYKGDAL.js → connect-cmd-ZFOK6HEL.js} +5 -5
- package/dist/{context-cleanup-CHEWNYU6.js → context-cleanup-DSA3HGQU.js} +1 -1
- package/dist/{control-plane-launch-34LNRJWR.js → control-plane-launch-SZTE4TUS.js} +62 -28
- package/dist/{dist-WO7NOJNG.js → dist-7IH56ELA.js} +1 -1
- package/dist/{docker-BFU3AQGY.js → docker-2OVBKVRK.js} +7 -7
- package/dist/{doctor-DHUM5DBB.js → doctor-3SQGZVYP.js} +35 -36
- package/dist/{emit-artifact-cmd-XBHM647X.js → emit-artifact-cmd-R42BE6ID.js} +19 -18
- package/dist/{emit-cmd-W55AEBCA.js → emit-cmd-3W5IO5ET.js} +3 -3
- package/dist/{emit-criteria-cmd-UNMWHOUY.js → emit-criteria-cmd-MQZE7W4Y.js} +13 -12
- package/dist/{emit-finding-cmd-7JHVRYOP.js → emit-finding-cmd-ZS5JEGGM.js} +13 -12
- package/dist/{emit-result-cmd-5NZMLCJK.js → emit-result-cmd-FUE3DXBW.js} +19 -18
- package/dist/{emit-step-cmd-347XNGPQ.js → emit-step-cmd-T7PRA44X.js} +14 -13
- package/dist/{exec-client-7UMESIE2.js → exec-client-YIDTWXMZ.js} +4 -3
- package/dist/{governing-source-HAA4OW57.js → governing-source-TORXL4I4.js} +19 -18
- package/dist/{guest-init-KPBQOFE5.js → guest-init-TO6HAWIT.js} +4 -3
- package/dist/{hook-fast-RL5E5OXE.js → hook-fast-WFB2ZMRB.js} +2 -2
- package/dist/host-key-2LXQDSBO.js +9 -0
- package/dist/{import-capture-cmd-TK4XY7RD.js → import-capture-cmd-Q56DDZVO.js} +1 -1
- package/dist/index.js +5 -5
- package/dist/init-2CRAVIUU.js +52 -0
- package/dist/{init-V6FOMD2V.js → init-AHDZCMDC.js} +32 -33
- package/dist/{init-cmd-Z4O4PPLZ.js → init-cmd-IXDL2ZSL.js} +25 -23
- package/dist/{init-update-7DVAKWW3.js → init-update-YB3PODDH.js} +69 -24
- package/dist/{install-4SEFIIW2.js → install-CZUIW2AF.js} +5 -4
- package/dist/{install-cmd-QDOTR4OM.js → install-cmd-Q7RJGFUO.js} +6 -5
- package/dist/{login-browser-DX35MARZ.js → login-browser-BN5UKPHS.js} +3 -3
- package/dist/{logout-cmd-5V5MMBSY.js → logout-cmd-RZFWTNYD.js} +2 -2
- package/dist/{logs-AMLKC3VP.js → logs-RJPYTVQE.js} +3 -2
- package/dist/loop-ZDW6TRAA.js +75 -0
- package/dist/machine-JCOHLASP.js +47 -0
- package/dist/{mcp-install-cmd-GGWB3JJ2.js → mcp-install-cmd-GD4QUP7Z.js} +2 -2
- package/dist/{mcp-serve-cmd-N3UTY6EE.js → mcp-serve-cmd-IULQPVHL.js} +6 -5
- package/dist/{paths-7XP63OAX.js → paths-N3LU4O2Z.js} +3 -2
- package/dist/pending-GRCV3ZR4.js +9 -0
- package/dist/{pidfile-3DM4OBKO.js → pidfile-IF3TCL5A.js} +4 -3
- package/dist/receive-files-T2BIUBQL.js +254 -0
- package/dist/{record-3WP6V2K5.js → record-73PQE77B.js} +4 -3
- package/dist/relay-PSZ4LGMW.js +210 -0
- package/dist/relay-protocol-K7KP2SPI.js +6 -0
- package/dist/{runner-env-FRZFFHDF.js → runner-env-LDOBJESN.js} +6 -5
- package/dist/runner-identity-RWCBTY2A.js +14 -0
- package/dist/{runner-key-5FOWCW7J.js → runner-key-HMYLP6WN.js} +32 -33
- package/dist/{session-sender-U2BBUWNO.js → session-sender-2G25JYHI.js} +20 -19
- package/dist/{spec-cmd-FVLJOFGC.js → spec-cmd-LBVXM6XS.js} +13 -12
- package/dist/{starter-pack-6LQVLBIA.js → starter-pack-5EQYGSJF.js} +2 -2
- package/dist/{status-BINH7MBG.js → status-QZB2GWXJ.js} +36 -38
- package/dist/{statusline-fast-KN3BX67H.js → statusline-fast-2OK3DVAA.js} +2 -2
- package/dist/{sync-cmd-VXOUFS4I.js → sync-cmd-TQQIR4PN.js} +19 -18
- package/dist/{watch-OH645CV2.js → watch-4JES4PCS.js} +19 -18
- package/dist/{work-claim-cmd-DRQCHCOK.js → work-claim-cmd-XG6O7B63.js} +17 -16
- package/dist/{work-cmd-5I3J2XQQ.js → work-cmd-L6WMPAA2.js} +13 -12
- package/dist/{work-comment-cmd-UCCPWUSR.js → work-comment-cmd-SNTDCPGH.js} +13 -12
- package/dist/{work-logs-cmd-7352CJPG.js → work-logs-cmd-3S7IAVII.js} +13 -12
- package/dist/{work-open-pr-cmd-LSH4VHJM.js → work-open-pr-cmd-E3WMO5XT.js} +15 -14
- package/dist/{work-pr-cmd-H4OQR26N.js → work-pr-cmd-22T6FG4T.js} +14 -13
- package/dist/{work-stop-cmd-CRDHXQVW.js → work-stop-cmd-WR7ESS4Z.js} +13 -12
- package/package.json +2 -2
- package/skill-pack/maintained/outerlayer-build/SKILL.md +347 -0
- package/skill-pack/{template/build → maintained/outerlayer-build}/references/evidence.md +2 -2
- package/skill-pack/{template/build → maintained/outerlayer-build}/references/release.md +4 -4
- package/skill-pack/{template/build → maintained/outerlayer-build}/references/review-loop.md +3 -1
- package/skill-pack/shipped.json +2 -0
- package/skill-pack/template/build/SKILL.md +4 -253
- package/skill-pack/validators/acceptance-criteria.yaml +1 -1
- package/dist/build-TQ6X5PYN.js +0 -12
- package/dist/chunk-3UZTIRLF.js +0 -320
- package/dist/chunk-CX22S5BA.js +0 -45
- package/dist/chunk-LLM3I5YU.js +0 -89
- package/dist/chunk-SADUQSO4.js +0 -50
- package/dist/chunk-VMJPFQNE.js +0 -261
- package/dist/claude-launcher-J5M44PFS.js +0 -115
- package/dist/host-key-U4B5QETR.js +0 -8
- package/dist/init-XG47KCI4.js +0 -53
- package/dist/loop-EAZQMAE2.js +0 -77
- package/dist/machine-TRHBT5NE.js +0 -48
- package/dist/pending-532ABZYC.js +0 -8
- package/dist/relay-L76HDX72.js +0 -46
- package/dist/{chunk-YVZJ6GKF.js → chunk-DIB2WLBL.js} +1 -1
- package/dist/{chunk-GOSFARMS.js → chunk-F35VMF5M.js} +1 -1
- package/dist/{chunk-DSF2FWAZ.js → chunk-IZSVXRDR.js} +1 -1
- /package/skill-pack/{template/build → maintained/outerlayer-build}/references/agent-briefs.md +0 -0
- /package/skill-pack/{template/build → maintained/outerlayer-build}/references/criteria-judge.md +0 -0
- /package/skill-pack/{template/build → maintained/outerlayer-build}/references/review-briefs.md +0 -0
- /package/skill-pack/{template/build → maintained/outerlayer-build}/scripts/criterion-citations.mjs +0 -0
- /package/skill-pack/{template/build → maintained/outerlayer-build}/scripts/record-criteria.mjs +0 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { RecipeError, BUILDKIT_IMAGE } from './chunk-3W333NFO.js';
|
|
4
|
+
import { parseSize } from './chunk-7RQ73DLO.js';
|
|
5
|
+
import { compareVersions } from './chunk-AEKSX4EY.js';
|
|
6
|
+
import { ensureHostKey } from './chunk-KDHYLBRR.js';
|
|
7
|
+
import { RUNNER_DIR_MODE, RUNNER_FILE_MODE, runnerDir, jobsDir } from './chunk-KIGSGZBB.js';
|
|
8
|
+
import { CLAUDE_PORT, JOB_MOUNT, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_SOCKET, RELAY_PORT, DESTINATION_BASE_PORT } from './chunk-V7MQKOZY.js';
|
|
9
|
+
import { JOIN_DIR, RELAY_ID, JOIN_SOCKET, TARGET_GATEWAY, TARGET_CLAUDE, destinationTarget, RELAY_USER } from './chunk-GISNHSYL.js';
|
|
10
|
+
import { StringDecoder } from 'string_decoder';
|
|
11
|
+
import { spawn } from 'child_process';
|
|
12
|
+
import { existsSync, readFileSync, mkdirSync, createWriteStream, rmSync, chmodSync, writeFileSync, realpathSync, readdirSync, renameSync } from 'fs';
|
|
13
|
+
import { dirname, join, resolve } from 'path';
|
|
14
|
+
import { createHash } from 'crypto';
|
|
15
|
+
import { createRequire as createRequire$1 } from 'module';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
|
|
18
|
+
createRequire(import.meta.url);
|
|
19
|
+
var ITEM_KEY_FILE = "item-key";
|
|
20
|
+
function itemKeyPath(jobPath) {
|
|
21
|
+
return join(dirname(jobPath), ITEM_KEY_FILE);
|
|
22
|
+
}
|
|
23
|
+
function withItemKey(jobPath, job) {
|
|
24
|
+
let key;
|
|
25
|
+
try {
|
|
26
|
+
key = readFileSync(itemKeyPath(jobPath), "utf8").trim();
|
|
27
|
+
} catch {
|
|
28
|
+
return job;
|
|
29
|
+
}
|
|
30
|
+
return key === "" ? job : { ...job, itemKey: key };
|
|
31
|
+
}
|
|
32
|
+
function writeOwnerOnly(path, content) {
|
|
33
|
+
if (existsSync(path)) chmodSync(path, RUNNER_FILE_MODE);
|
|
34
|
+
writeFileSync(path, content, { mode: RUNNER_FILE_MODE });
|
|
35
|
+
}
|
|
36
|
+
function readJobFile(path) {
|
|
37
|
+
if (!existsSync(path)) return null;
|
|
38
|
+
try {
|
|
39
|
+
return withItemKey(path, JSON.parse(readFileSync(path, "utf8")));
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function readJobFileChecked(path) {
|
|
45
|
+
if (!existsSync(path)) return { kind: "missing" };
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
48
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return { kind: "unreadable" };
|
|
49
|
+
return { kind: "ok", job: withItemKey(path, parsed) };
|
|
50
|
+
} catch {
|
|
51
|
+
return { kind: "unreadable" };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function writeJobFile(path, job) {
|
|
55
|
+
mkdirSync(dirname(path), { recursive: true, mode: RUNNER_DIR_MODE });
|
|
56
|
+
const { itemKey, ...rest } = job;
|
|
57
|
+
if (itemKey !== void 0) writeOwnerOnly(itemKeyPath(path), itemKey);
|
|
58
|
+
writeOwnerOnly(path, JSON.stringify(rest, null, 2));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/runner/runner-identity.ts
|
|
62
|
+
var RUNNER_LABEL = "ai.outerlayer.runner";
|
|
63
|
+
var IDENTITY_FILE = "identity.json";
|
|
64
|
+
function identityPath(configPath) {
|
|
65
|
+
return join(runnerDir(configPath), IDENTITY_FILE);
|
|
66
|
+
}
|
|
67
|
+
function resolvedRunnerDir(configPath) {
|
|
68
|
+
try {
|
|
69
|
+
return realpathSync(runnerDir(configPath));
|
|
70
|
+
} catch {
|
|
71
|
+
try {
|
|
72
|
+
return join(realpathSync(dirname(resolve(configPath))), "runner");
|
|
73
|
+
} catch {
|
|
74
|
+
return resolve(runnerDir(configPath));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function shortHash(text) {
|
|
79
|
+
return createHash("sha256").update(text).digest("hex").slice(0, 12);
|
|
80
|
+
}
|
|
81
|
+
function pathLabelValue(configPath) {
|
|
82
|
+
return shortHash(resolvedRunnerDir(configPath));
|
|
83
|
+
}
|
|
84
|
+
function builderNameFor(label) {
|
|
85
|
+
return `outerlayer-${label}`;
|
|
86
|
+
}
|
|
87
|
+
function readRunnerIdentity(configPath) {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(readFileSync(identityPath(configPath), "utf8"));
|
|
90
|
+
return typeof parsed.label === "string" && /^[0-9a-f]{12}$/.test(parsed.label) && typeof parsed.builder === "string" && parsed.builder !== "" ? { label: parsed.label, builder: parsed.builder } : void 0;
|
|
91
|
+
} catch {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function dockerIdentity(run = spawnRun) {
|
|
96
|
+
return {
|
|
97
|
+
async holdsObjects(label, builder) {
|
|
98
|
+
const filter = `label=${RUNNER_LABEL}=${label}`;
|
|
99
|
+
for (const args of [["ps", "--all", "--quiet", "--filter", filter], ["volume", "ls", "--quiet", "--filter", filter]]) {
|
|
100
|
+
const listed = await run("docker", args);
|
|
101
|
+
if (listed.status !== 0) return void 0;
|
|
102
|
+
if (listed.stdout.trim() !== "") return true;
|
|
103
|
+
}
|
|
104
|
+
const inspected = await run("docker", ["buildx", "inspect", builder]);
|
|
105
|
+
if (inspected.status === 0) return true;
|
|
106
|
+
return /no builder .* found/i.test(inspected.stderr) ? false : void 0;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function folderHoldsContainerBuilds(configPath) {
|
|
111
|
+
const root = jobsDir(configPath);
|
|
112
|
+
if (!existsSync(root)) return false;
|
|
113
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
114
|
+
if (!entry.isDirectory()) continue;
|
|
115
|
+
const job = readJobFile(join(root, entry.name, "job.json"));
|
|
116
|
+
if (job?.container !== void 0 || job?.checkContainer !== void 0) return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
function writeIdentity(configPath, identity) {
|
|
121
|
+
mkdirSync(runnerDir(configPath), { recursive: true, mode: RUNNER_DIR_MODE });
|
|
122
|
+
const target = identityPath(configPath);
|
|
123
|
+
const partial = `${target}.partial`;
|
|
124
|
+
writeFileSync(partial, `${JSON.stringify(identity)}
|
|
125
|
+
`, { mode: RUNNER_FILE_MODE });
|
|
126
|
+
renameSync(partial, target);
|
|
127
|
+
}
|
|
128
|
+
async function ensureRunnerIdentity(configPath, deps = {}) {
|
|
129
|
+
const stored = readRunnerIdentity(configPath);
|
|
130
|
+
if (stored !== void 0) return stored;
|
|
131
|
+
const legacyLabel = pathLabelValue(configPath);
|
|
132
|
+
const legacy = { label: legacyLabel, builder: builderNameFor(legacyLabel) };
|
|
133
|
+
let keepLegacy = folderHoldsContainerBuilds(configPath);
|
|
134
|
+
if (!keepLegacy) {
|
|
135
|
+
const held = await (deps.docker ?? dockerIdentity()).holdsObjects(legacy.label, legacy.builder);
|
|
136
|
+
if (held === void 0) {
|
|
137
|
+
deps.log?.("Docker could not be asked what it holds under this runner's names \xB7 using the names its folder's path gives");
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
keepLegacy = held;
|
|
141
|
+
}
|
|
142
|
+
if (keepLegacy) {
|
|
143
|
+
writeIdentity(configPath, legacy);
|
|
144
|
+
return legacy;
|
|
145
|
+
}
|
|
146
|
+
const key = await ensureHostKey(configPath);
|
|
147
|
+
const label = shortHash(key.fingerprint);
|
|
148
|
+
const identity = { label, builder: builderNameFor(label) };
|
|
149
|
+
writeIdentity(configPath, identity);
|
|
150
|
+
return identity;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/runner/judge-command.ts
|
|
154
|
+
var JUDGE_DIR = "/outerlayer/judge";
|
|
155
|
+
var JUDGE_HOME = `${JUDGE_DIR}/home`;
|
|
156
|
+
var JUDGE_TREE = `${JUDGE_DIR}/tree`;
|
|
157
|
+
var JUDGE_REPORT = `${JUDGE_DIR}/report.html`;
|
|
158
|
+
var JUDGE_PROGRAM = "/opt/outerlayer/claude/bin/claude";
|
|
159
|
+
var MAX_REPORT_BYTES = 5 * 1024 * 1024;
|
|
160
|
+
var JUDGE_MAX_TURNS = 60;
|
|
161
|
+
var JUDGE_PATH = "/opt/outerlayer/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
162
|
+
var JUDGE_TOOLS = "Read,Grep,Glob,Write";
|
|
163
|
+
function judgeCommand(input) {
|
|
164
|
+
const env = {
|
|
165
|
+
HOME: JUDGE_HOME,
|
|
166
|
+
TMPDIR: `${JUDGE_HOME}/tmp`,
|
|
167
|
+
PATH: JUDGE_PATH,
|
|
168
|
+
ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLAUDE_PORT}`,
|
|
169
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
170
|
+
DISABLE_AUTOUPDATER: "1",
|
|
171
|
+
...input.credential === void 0 ? {} : { [input.credential.variable]: input.credential.placeholder }
|
|
172
|
+
};
|
|
173
|
+
const argv = [
|
|
174
|
+
JUDGE_PROGRAM,
|
|
175
|
+
"-p",
|
|
176
|
+
"--output-format",
|
|
177
|
+
"json",
|
|
178
|
+
"--setting-sources",
|
|
179
|
+
"user",
|
|
180
|
+
"--strict-mcp-config",
|
|
181
|
+
"--disable-slash-commands",
|
|
182
|
+
"--no-session-persistence",
|
|
183
|
+
"--tools",
|
|
184
|
+
JUDGE_TOOLS,
|
|
185
|
+
"--allowedTools",
|
|
186
|
+
// A path that starts with two slashes is absolute in a permission rule.
|
|
187
|
+
`Read(/${JUDGE_DIR}/**),Edit(/${JUDGE_REPORT})`,
|
|
188
|
+
"--add-dir",
|
|
189
|
+
JUDGE_TREE,
|
|
190
|
+
"--permission-mode",
|
|
191
|
+
"dontAsk",
|
|
192
|
+
"--max-turns",
|
|
193
|
+
String(JUDGE_MAX_TURNS),
|
|
194
|
+
// After the separator, so a brief that starts with a dash is not read as an option.
|
|
195
|
+
"--",
|
|
196
|
+
input.brief
|
|
197
|
+
];
|
|
198
|
+
return { argv, env, cwd: JUDGE_DIR };
|
|
199
|
+
}
|
|
200
|
+
var HASH_SCRIPT = 'const fs=require("fs"),c=require("crypto");const p=fs.realpathSync(process.argv[1]);process.stdout.write(c.createHash("sha256").update(fs.readFileSync(p)).digest("hex")+" "+p+"\\n")';
|
|
201
|
+
var HASH_ARGV = ["/opt/outerlayer/bin/node", "-e", HASH_SCRIPT, JUDGE_PROGRAM];
|
|
202
|
+
function parseProgramHash(stdout) {
|
|
203
|
+
return /^([0-9a-f]{64}) /.exec(stdout.trim())?.[1];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/runner/container/run-args.ts
|
|
207
|
+
var COMMAND_DIR = "/build/.command";
|
|
208
|
+
var BUILD_LABEL = "ai.outerlayer.build";
|
|
209
|
+
function runnerLabelValue(configPath) {
|
|
210
|
+
return readRunnerIdentity(configPath)?.label ?? pathLabelValue(configPath);
|
|
211
|
+
}
|
|
212
|
+
function hardened(input) {
|
|
213
|
+
const { memory, cpus, pids, cpuset } = input.limits;
|
|
214
|
+
return [
|
|
215
|
+
"--label",
|
|
216
|
+
`${BUILD_LABEL}=${input.buildId}`,
|
|
217
|
+
"--label",
|
|
218
|
+
`${RUNNER_LABEL}=${input.runnerId}`,
|
|
219
|
+
"--network",
|
|
220
|
+
"none",
|
|
221
|
+
"--cap-drop",
|
|
222
|
+
"ALL",
|
|
223
|
+
"--security-opt",
|
|
224
|
+
"no-new-privileges",
|
|
225
|
+
"--read-only",
|
|
226
|
+
"--user",
|
|
227
|
+
input.user,
|
|
228
|
+
// The same figure for swap as for memory leaves the container none.
|
|
229
|
+
"--memory",
|
|
230
|
+
memory,
|
|
231
|
+
"--memory-swap",
|
|
232
|
+
memory,
|
|
233
|
+
"--cpus",
|
|
234
|
+
String(cpus),
|
|
235
|
+
...cpuset === void 0 ? [] : ["--cpuset-cpus", cpuset],
|
|
236
|
+
"--pids-limit",
|
|
237
|
+
String(pids)
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
function mountPath(path) {
|
|
241
|
+
if (/[",]/.test(path)) throw new Error(`${path} holds a comma or a quote, which a Docker mount cannot carry; move the runner's config file`);
|
|
242
|
+
return path;
|
|
243
|
+
}
|
|
244
|
+
function mountedDir(dir, what) {
|
|
245
|
+
if (dir === void 0) throw new Error(`${what} is required for a build whose files are not copied`);
|
|
246
|
+
return dir;
|
|
247
|
+
}
|
|
248
|
+
function containerRunArgs(input) {
|
|
249
|
+
const exec = input.transport === "exec";
|
|
250
|
+
const copied = input.jobFiles === "copied";
|
|
251
|
+
if (copied && !exec) throw new Error("a build's files are copied in only through docker exec");
|
|
252
|
+
return [
|
|
253
|
+
"--detach",
|
|
254
|
+
"--name",
|
|
255
|
+
input.container,
|
|
256
|
+
...hardened(exec ? { ...input, user: RELAY_USER } : input),
|
|
257
|
+
// Only the relay's user can open the folder the joining socket is in.
|
|
258
|
+
...exec ? ["--tmpfs", `${JOIN_DIR}:mode=0700,uid=${RELAY_ID},gid=${RELAY_ID}`] : [],
|
|
259
|
+
"--mount",
|
|
260
|
+
`type=volume,src=${input.volume},dst=/build`,
|
|
261
|
+
// `/tmp` is a directory of the same volume, so it is measured and limited with the rest of the build.
|
|
262
|
+
"--mount",
|
|
263
|
+
`type=volume,src=${input.volume},dst=/tmp,volume-subpath=tmp`,
|
|
264
|
+
...copied ? [
|
|
265
|
+
// Owned by the relay's user, who writes the files in; the agent can read them and cannot change them. `exec` so the commit hooks can run.
|
|
266
|
+
"--tmpfs",
|
|
267
|
+
`${JOB_MOUNT}:exec,mode=0755,uid=${RELAY_ID},gid=${RELAY_ID}`,
|
|
268
|
+
// The judge runs in the build's own container, so only a container that has the built-in services has the folder.
|
|
269
|
+
...input.builtIns === true ? ["--tmpfs", `${JUDGE_DIR}:mode=0755,uid=${RELAY_ID},gid=${RELAY_ID}`] : []
|
|
270
|
+
] : [
|
|
271
|
+
"--mount",
|
|
272
|
+
`type=bind,src=${mountPath(mountedDir(input.buildDir, "buildDir"))},dst=${JOB_MOUNT},readonly`,
|
|
273
|
+
...input.judgeDir === void 0 ? [] : ["--mount", `type=bind,src=${mountPath(input.judgeDir)},dst=${JUDGE_DIR}`]
|
|
274
|
+
],
|
|
275
|
+
"--entrypoint",
|
|
276
|
+
"/opt/outerlayer/bin/tini",
|
|
277
|
+
input.image,
|
|
278
|
+
"--",
|
|
279
|
+
"/opt/outerlayer/bin/node",
|
|
280
|
+
"/opt/outerlayer/cli/dist/index.js",
|
|
281
|
+
"broker-relay",
|
|
282
|
+
"--port",
|
|
283
|
+
String(RELAY_PORT),
|
|
284
|
+
// With `--lease-watchdog` the relay stops the container when the runner stops renewing the build's lease.
|
|
285
|
+
...exec ? ["--join", JOIN_SOCKET, "--lease-watchdog"] : ["--socket", `${JOB_MOUNT}/tunnel.sock`],
|
|
286
|
+
...Array.from({ length: input.destinations ?? 0 }, (_unused, index) => [
|
|
287
|
+
"--forward",
|
|
288
|
+
`${DESTINATION_BASE_PORT + index}=${exec ? destinationTarget(index) : `${JOB_MOUNT}/dest-${index}.sock`}`
|
|
289
|
+
]).flat(),
|
|
290
|
+
...input.builtIns === true ? [
|
|
291
|
+
"--forward",
|
|
292
|
+
`${GATEWAY_PORT}=${exec ? TARGET_GATEWAY : `${JOB_MOUNT}/${GATEWAY_SOCKET}`}`,
|
|
293
|
+
"--forward",
|
|
294
|
+
`${CLAUDE_PORT}=${exec ? TARGET_CLAUDE : `${JOB_MOUNT}/${CLAUDE_SOCKET}`}`
|
|
295
|
+
] : []
|
|
296
|
+
];
|
|
297
|
+
}
|
|
298
|
+
function prepareRunArgs(input) {
|
|
299
|
+
return [
|
|
300
|
+
"--rm",
|
|
301
|
+
...hardened(input),
|
|
302
|
+
"--mount",
|
|
303
|
+
`type=volume,src=${input.volume},dst=/build`,
|
|
304
|
+
"--entrypoint",
|
|
305
|
+
"mkdir",
|
|
306
|
+
input.image,
|
|
307
|
+
"-p",
|
|
308
|
+
"-m",
|
|
309
|
+
"1777",
|
|
310
|
+
"/build/tmp",
|
|
311
|
+
"/build/home",
|
|
312
|
+
"/build/work",
|
|
313
|
+
COMMAND_DIR
|
|
314
|
+
];
|
|
315
|
+
}
|
|
316
|
+
function containerExecArgs(input) {
|
|
317
|
+
const literals = Object.keys(input.env.literals).sort().flatMap((name) => ["-e", `${name}=${input.env.literals[name]}`]);
|
|
318
|
+
const passed = Object.keys(input.env.passed).filter((name) => input.env.passed[name] !== void 0).sort().flatMap((name) => ["-e", name]);
|
|
319
|
+
return [
|
|
320
|
+
...input.detached === true ? ["-d"] : [],
|
|
321
|
+
...input.interactive === true || input.stdin !== void 0 ? ["-i"] : [],
|
|
322
|
+
"-u",
|
|
323
|
+
input.user,
|
|
324
|
+
...input.workdir === void 0 ? [] : ["-w", input.workdir],
|
|
325
|
+
...literals,
|
|
326
|
+
...passed,
|
|
327
|
+
input.container,
|
|
328
|
+
...input.argv
|
|
329
|
+
];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/runner/image/builder.ts
|
|
333
|
+
var MIN_BUILDX_VERSION = "0.18.0";
|
|
334
|
+
var INSTALL_HINT = "install Docker's Buildx plugin (docker-buildx-plugin on Debian and Ubuntu with Docker's apt repository, docker-buildx on Ubuntu's own packages)";
|
|
335
|
+
function builderName(configPath) {
|
|
336
|
+
return readRunnerIdentity(configPath)?.builder ?? builderNameFor(runnerLabelValue(configPath));
|
|
337
|
+
}
|
|
338
|
+
function proxyEnvironment(env) {
|
|
339
|
+
const found = {};
|
|
340
|
+
for (const name of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
341
|
+
const value = env[name];
|
|
342
|
+
if (value !== void 0 && value !== "") found[name] = value;
|
|
343
|
+
}
|
|
344
|
+
return found;
|
|
345
|
+
}
|
|
346
|
+
function builderSpec(configPath, memory, env) {
|
|
347
|
+
return { name: builderName(configPath), memory, env: proxyEnvironment(env) };
|
|
348
|
+
}
|
|
349
|
+
function driverOpt(pair) {
|
|
350
|
+
return /[",]/.test(pair) ? `"${pair.replace(/"/g, '""')}"` : pair;
|
|
351
|
+
}
|
|
352
|
+
async function buildxState(run) {
|
|
353
|
+
const result = await run("docker", ["buildx", "version"]);
|
|
354
|
+
if (result.status !== 0) {
|
|
355
|
+
const said = (result.stderr.trim() || result.stdout.trim()).split("\n")[0] ?? "";
|
|
356
|
+
return { problem: `docker buildx is not available${said === "" ? "" : ` (${said})`}, so image builds cannot be limited; ${INSTALL_HINT}` };
|
|
357
|
+
}
|
|
358
|
+
const version = /\bv?(\d+\.\d+\.\d+)/.exec(result.stdout)?.[1];
|
|
359
|
+
if (version !== void 0 && compareVersions(version, MIN_BUILDX_VERSION) < 0) {
|
|
360
|
+
return { problem: `docker buildx ${version} is too old: the runner's image builder needs Buildx ${MIN_BUILDX_VERSION} or later; update ${INSTALL_HINT}` };
|
|
361
|
+
}
|
|
362
|
+
return "usable";
|
|
363
|
+
}
|
|
364
|
+
function isOutOfMemory(output) {
|
|
365
|
+
if (/cannot allocate memory/i.test(output)) return true;
|
|
366
|
+
if (/did not complete successfully: (exit code: 137|signal: killed)/i.test(output)) return true;
|
|
367
|
+
return /ResourceExhausted/i.test(output) && !/larger than max|message size|too large/i.test(output);
|
|
368
|
+
}
|
|
369
|
+
var running = 0;
|
|
370
|
+
async function trackImageBuild(work) {
|
|
371
|
+
running++;
|
|
372
|
+
try {
|
|
373
|
+
return await work();
|
|
374
|
+
} finally {
|
|
375
|
+
running--;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
var queue = Promise.resolve();
|
|
379
|
+
function serialized(work) {
|
|
380
|
+
const next = queue.then(work, work);
|
|
381
|
+
queue = next.catch(() => void 0);
|
|
382
|
+
return next;
|
|
383
|
+
}
|
|
384
|
+
function readExisting(inspectOutput) {
|
|
385
|
+
const options = /^Driver Options:\s*(.*)$/m.exec(inspectOutput)?.[1] ?? "";
|
|
386
|
+
const option = (key) => new RegExp(`(?:^|\\s)${key}="([^"]*)"`).exec(options)?.[1];
|
|
387
|
+
return { driver: /^Driver:\s*(\S+)/m.exec(inspectOutput)?.[1], memory: option("memory"), image: option("image") };
|
|
388
|
+
}
|
|
389
|
+
function sameMemory(found, wanted) {
|
|
390
|
+
return (parseSize(found) ?? found) === (parseSize(wanted) ?? wanted);
|
|
391
|
+
}
|
|
392
|
+
var IDLE_POLL_MS = 1e3;
|
|
393
|
+
var failures = 0;
|
|
394
|
+
function ensureFailureCount() {
|
|
395
|
+
return failures;
|
|
396
|
+
}
|
|
397
|
+
function ensureBuilder(rawRun, spec, options = {}) {
|
|
398
|
+
const run = (command, args, opts) => rawRun(command, args, { ...opts, timeoutMs: args.includes("--bootstrap") ? 15 * 6e4 : 2 * 6e4 });
|
|
399
|
+
const result = serialized(async () => {
|
|
400
|
+
const state = await buildxState(run);
|
|
401
|
+
if (state !== "usable") return { ok: false, problem: state.problem };
|
|
402
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
403
|
+
const inspected = await run("docker", ["buildx", "inspect", spec.name]);
|
|
404
|
+
let create = inspected.status !== 0;
|
|
405
|
+
if (!create) {
|
|
406
|
+
const found = readExisting(inspected.stdout);
|
|
407
|
+
const current = found.driver === "docker-container" && sameMemory(found.memory ?? "", spec.memory) && found.image === BUILDKIT_IMAGE;
|
|
408
|
+
if (!current && options.replaceStale === false) {
|
|
409
|
+
return {
|
|
410
|
+
ok: false,
|
|
411
|
+
problem: `the image builder ${spec.name} was made with other settings than runner.build.memory (${spec.memory}) and the pinned BuildKit image; the runner service replaces it when no build is running, or remove it with docker buildx rm ${spec.name}`
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
if (!current) {
|
|
415
|
+
if (options.waitForIdle === true) while (running > 0) await sleep(IDLE_POLL_MS);
|
|
416
|
+
if (running === 0) {
|
|
417
|
+
const removed = await run("docker", ["buildx", "rm", spec.name]);
|
|
418
|
+
if (removed.status !== 0) return { ok: false, problem: `could not replace the image builder ${spec.name}: ${removed.stderr.trim() || `docker exited ${removed.status}`}` };
|
|
419
|
+
create = true;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (create) {
|
|
424
|
+
const opts = [
|
|
425
|
+
`memory=${spec.memory}`,
|
|
426
|
+
`memory-swap=${spec.memory}`,
|
|
427
|
+
`image=${BUILDKIT_IMAGE}`,
|
|
428
|
+
...Object.entries(spec.env ?? {}).map(([name, value]) => `env.${name}=${value}`),
|
|
429
|
+
...spec.driverOpts ?? []
|
|
430
|
+
];
|
|
431
|
+
const created = await run("docker", [
|
|
432
|
+
"buildx",
|
|
433
|
+
"create",
|
|
434
|
+
"--name",
|
|
435
|
+
spec.name,
|
|
436
|
+
"--driver",
|
|
437
|
+
"docker-container",
|
|
438
|
+
...opts.flatMap((opt) => ["--driver-opt", driverOpt(opt)])
|
|
439
|
+
]);
|
|
440
|
+
if (created.status !== 0) {
|
|
441
|
+
const said = created.stderr.trim() || `docker exited ${created.status}`;
|
|
442
|
+
if (/failed to find driver/i.test(said)) {
|
|
443
|
+
return { ok: false, problem: `this Docker's Buildx has no docker-container driver, so image builds cannot be limited; ${INSTALL_HINT}` };
|
|
444
|
+
}
|
|
445
|
+
return { ok: false, problem: `could not create the image builder ${spec.name}: ${said}` };
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (options.bootstrap === true) {
|
|
449
|
+
const started = await run("docker", ["buildx", "inspect", "--bootstrap", spec.name]);
|
|
450
|
+
if (started.status !== 0) {
|
|
451
|
+
return { ok: false, problem: `the image builder ${spec.name} did not start: ${started.stderr.trim() || `docker exited ${started.status}`}` };
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return { ok: true, changed: create };
|
|
455
|
+
});
|
|
456
|
+
return result.then(
|
|
457
|
+
(outcome) => {
|
|
458
|
+
if (!outcome.ok) failures++;
|
|
459
|
+
return outcome;
|
|
460
|
+
},
|
|
461
|
+
(err) => {
|
|
462
|
+
failures++;
|
|
463
|
+
throw err;
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
var PACKAGE = "@devcontainers/cli";
|
|
468
|
+
function buildRootFromDist(moduleDir = dirname(fileURLToPath(import.meta.url))) {
|
|
469
|
+
try {
|
|
470
|
+
const info = JSON.parse(readFileSync(join(moduleDir, "build-info.json"), "utf8"));
|
|
471
|
+
return typeof info.root === "string" ? info.root : void 0;
|
|
472
|
+
} catch {
|
|
473
|
+
return void 0;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function resolveDevcontainerCli(opts = {}) {
|
|
477
|
+
const resolve2 = opts.resolve ?? createRequire$1(import.meta.url).resolve;
|
|
478
|
+
try {
|
|
479
|
+
const bin = join(dirname(resolve2(`${PACKAGE}/package.json`)), "devcontainer.js");
|
|
480
|
+
if (existsSync(bin)) return { command: process.execPath, args: [bin] };
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
const buildRoot = opts.buildRoot ?? buildRootFromDist();
|
|
484
|
+
const fallback = buildRoot === void 0 ? void 0 : join(buildRoot, "node_modules", PACKAGE);
|
|
485
|
+
if (fallback !== void 0 && existsSync(join(fallback, "devcontainer.js"))) {
|
|
486
|
+
return { command: process.execPath, args: [join(fallback, "devcontainer.js")] };
|
|
487
|
+
}
|
|
488
|
+
throw new RecipeError(
|
|
489
|
+
"devcontainer_cli_missing",
|
|
490
|
+
`the devcontainer CLI is not installed: looked beside this package and in ${fallback ?? "the checkout this build came from (unknown)"}. Run yarn install in that checkout.`
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
function devcontainerBuildInvocation(cli, req) {
|
|
494
|
+
return {
|
|
495
|
+
command: cli.command,
|
|
496
|
+
args: [
|
|
497
|
+
...cli.args,
|
|
498
|
+
"build",
|
|
499
|
+
"--workspace-folder",
|
|
500
|
+
req.workspace,
|
|
501
|
+
"--config",
|
|
502
|
+
req.config,
|
|
503
|
+
"--image-name",
|
|
504
|
+
req.imageName,
|
|
505
|
+
...Object.entries(req.labels).flatMap(([key, value]) => ["--label", `${key}=${value}`]),
|
|
506
|
+
"--log-format",
|
|
507
|
+
"text",
|
|
508
|
+
// 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.
|
|
509
|
+
...req.layoutOut === void 0 ? [] : ["--output", `type=oci,dest=${req.layoutOut},tar=false`]
|
|
510
|
+
]
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/runner/image/docker.ts
|
|
515
|
+
var CHILD_ENVIRONMENT_NAMES = [
|
|
516
|
+
"PATH",
|
|
517
|
+
"HOME",
|
|
518
|
+
"USER",
|
|
519
|
+
"LOGNAME",
|
|
520
|
+
"TMPDIR",
|
|
521
|
+
"LANG",
|
|
522
|
+
"LC_ALL",
|
|
523
|
+
"DOCKER_HOST",
|
|
524
|
+
"DOCKER_CONFIG",
|
|
525
|
+
"DOCKER_CONTEXT",
|
|
526
|
+
"DOCKER_CERT_PATH",
|
|
527
|
+
"DOCKER_TLS_VERIFY",
|
|
528
|
+
"DOCKER_API_VERSION",
|
|
529
|
+
"DOCKER_BUILDKIT",
|
|
530
|
+
// `docker buildx build` fetches registry tokens in the client and hands them to the builder, so a host that reaches
|
|
531
|
+
// registries through a proxy needs it here as well as in the builder.
|
|
532
|
+
"HTTP_PROXY",
|
|
533
|
+
"HTTPS_PROXY",
|
|
534
|
+
"NO_PROXY",
|
|
535
|
+
"http_proxy",
|
|
536
|
+
"https_proxy",
|
|
537
|
+
"no_proxy",
|
|
538
|
+
"XDG_RUNTIME_DIR",
|
|
539
|
+
"XDG_CONFIG_HOME",
|
|
540
|
+
"XDG_DATA_HOME",
|
|
541
|
+
"XDG_CACHE_HOME",
|
|
542
|
+
"XDG_STATE_HOME"
|
|
543
|
+
];
|
|
544
|
+
function childEnvironment(base) {
|
|
545
|
+
const env = {};
|
|
546
|
+
for (const name of CHILD_ENVIRONMENT_NAMES) {
|
|
547
|
+
const value = base[name];
|
|
548
|
+
if (value !== void 0) env[name] = value;
|
|
549
|
+
}
|
|
550
|
+
return env;
|
|
551
|
+
}
|
|
552
|
+
var spawnRun = (command, args, opts = {}) => new Promise((resolve2) => {
|
|
553
|
+
let log;
|
|
554
|
+
if (opts.logPath !== void 0) {
|
|
555
|
+
mkdirSync(dirname(opts.logPath), { recursive: true });
|
|
556
|
+
log = createWriteStream(opts.logPath, { flags: "a" });
|
|
557
|
+
}
|
|
558
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
559
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
560
|
+
let stdout = "";
|
|
561
|
+
let stderr = "";
|
|
562
|
+
let settled = false;
|
|
563
|
+
const finish = (status, extra = "") => {
|
|
564
|
+
if (settled) return;
|
|
565
|
+
settled = true;
|
|
566
|
+
const result = { status, stdout: stdout + stdoutDecoder.end(), stderr: stderr + stderrDecoder.end() + extra };
|
|
567
|
+
if (log === void 0) {
|
|
568
|
+
resolve2(result);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (extra) log.write(extra);
|
|
572
|
+
log.end(() => resolve2(result));
|
|
573
|
+
};
|
|
574
|
+
let timer;
|
|
575
|
+
const child = spawn(command, args, {
|
|
576
|
+
env: { ...childEnvironment(process.env), ...opts.env },
|
|
577
|
+
stdio: [opts.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
|
|
578
|
+
});
|
|
579
|
+
child.stdout.on("data", (chunk) => {
|
|
580
|
+
stdout += stdoutDecoder.write(chunk);
|
|
581
|
+
log?.write(chunk);
|
|
582
|
+
});
|
|
583
|
+
child.stderr.on("data", (chunk) => {
|
|
584
|
+
stderr += stderrDecoder.write(chunk);
|
|
585
|
+
log?.write(chunk);
|
|
586
|
+
});
|
|
587
|
+
if (opts.timeoutMs !== void 0) {
|
|
588
|
+
timer = setTimeout(() => {
|
|
589
|
+
child.kill("SIGKILL");
|
|
590
|
+
finish(124, `${command}: no answer within ${Math.round(opts.timeoutMs / 1e3)}s
|
|
591
|
+
`);
|
|
592
|
+
}, opts.timeoutMs);
|
|
593
|
+
timer.unref();
|
|
594
|
+
}
|
|
595
|
+
child.on("error", (err) => finish(127, `${command}: ${err.message}
|
|
596
|
+
`));
|
|
597
|
+
child.on("close", (code, signal) => {
|
|
598
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
599
|
+
finish(code ?? (signal ? 128 : 1));
|
|
600
|
+
});
|
|
601
|
+
if (opts.input !== void 0) {
|
|
602
|
+
child.stdin.on("error", () => void 0);
|
|
603
|
+
child.stdin.end(opts.input);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
function toInfo(row) {
|
|
607
|
+
return { id: row.Id, createdAt: row.Created, labels: row.Config?.Labels ?? {} };
|
|
608
|
+
}
|
|
609
|
+
var lines = (text) => [...new Set(text.split("\n").map((l) => l.trim()).filter(Boolean))];
|
|
610
|
+
function layoutDigest(layout) {
|
|
611
|
+
const index = JSON.parse(readFileSync(join(layout, "index.json"), "utf8"));
|
|
612
|
+
const digest = index.manifests?.[0]?.digest;
|
|
613
|
+
if (digest === void 0) throw new Error(`${join(layout, "index.json")} lists no image`);
|
|
614
|
+
return digest;
|
|
615
|
+
}
|
|
616
|
+
function outOfMemory(builder, logPath) {
|
|
617
|
+
return new RecipeError(
|
|
618
|
+
"image_build_out_of_memory",
|
|
619
|
+
`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}`,
|
|
620
|
+
logPath
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
function createDockerHost(deps = {}) {
|
|
624
|
+
const run = deps.run ?? spawnRun;
|
|
625
|
+
const cli = deps.cli ?? resolveDevcontainerCli;
|
|
626
|
+
const builderFor = () => {
|
|
627
|
+
if (deps.builder === void 0) {
|
|
628
|
+
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");
|
|
629
|
+
}
|
|
630
|
+
return deps.builder;
|
|
631
|
+
};
|
|
632
|
+
const inspectMany = async (refs) => {
|
|
633
|
+
const result = await run("docker", ["image", "inspect", ...refs]);
|
|
634
|
+
return result.status === 0 ? JSON.parse(result.stdout).map(toInfo) : [];
|
|
635
|
+
};
|
|
636
|
+
return {
|
|
637
|
+
async inspect(ref) {
|
|
638
|
+
return (await inspectMany([ref]))[0] ?? null;
|
|
639
|
+
},
|
|
640
|
+
async list(repository) {
|
|
641
|
+
const ids = await run("docker", [
|
|
642
|
+
"image",
|
|
643
|
+
"ls",
|
|
644
|
+
"--all",
|
|
645
|
+
"--no-trunc",
|
|
646
|
+
"--quiet",
|
|
647
|
+
"--filter",
|
|
648
|
+
`label=ai.outerlayer.repository=${repository}`
|
|
649
|
+
]);
|
|
650
|
+
const found = lines(ids.stdout);
|
|
651
|
+
return found.length === 0 ? [] : inspectMany(found);
|
|
652
|
+
},
|
|
653
|
+
async inUse() {
|
|
654
|
+
const containers = lines((await run("docker", ["ps", "--all", "--no-trunc", "--quiet"])).stdout);
|
|
655
|
+
if (containers.length === 0) return [];
|
|
656
|
+
return lines((await run("docker", ["inspect", "--format", "{{.Image}}", ...containers])).stdout);
|
|
657
|
+
},
|
|
658
|
+
async ensureBuilder() {
|
|
659
|
+
const builder = builderFor();
|
|
660
|
+
const result = await ensureBuilder(run, builder, { waitForIdle: true, replaceStale: deps.replaceStaleBuilder });
|
|
661
|
+
if (!result.ok) throw new RecipeError("image_builder_unavailable", `the image builder is unavailable: ${result.problem}`);
|
|
662
|
+
return { name: builder.name, memory: builder.memory };
|
|
663
|
+
},
|
|
664
|
+
async exportLayout(ref, dir) {
|
|
665
|
+
rmSync(dir, { recursive: true, force: true });
|
|
666
|
+
mkdirSync(dir, { recursive: true });
|
|
667
|
+
const archive = join(dir, ".image.tar");
|
|
668
|
+
const saved = await run("docker", ["save", "--output", archive, ref]);
|
|
669
|
+
if (saved.status !== 0) throw new Error(saved.stderr.trim() || `docker save ${ref} exited ${saved.status}`);
|
|
670
|
+
const unpacked = await run("tar", ["-xf", archive, "-C", dir]);
|
|
671
|
+
rmSync(archive, { force: true });
|
|
672
|
+
if (unpacked.status !== 0) throw new Error(unpacked.stderr.trim() || `tar exited ${unpacked.status}`);
|
|
673
|
+
},
|
|
674
|
+
async buildDockerfile(req) {
|
|
675
|
+
const builder = builderFor();
|
|
676
|
+
if (req.layoutOut !== void 0) {
|
|
677
|
+
rmSync(req.layoutOut, { recursive: true, force: true });
|
|
678
|
+
}
|
|
679
|
+
const labels = Object.entries(req.labels).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
|
|
680
|
+
const args = [
|
|
681
|
+
"buildx",
|
|
682
|
+
"build",
|
|
683
|
+
"--builder",
|
|
684
|
+
builder.name,
|
|
685
|
+
"--load",
|
|
686
|
+
...req.layoutOut === void 0 ? [] : ["--output", `type=oci,dest=${req.layoutOut},tar=false`],
|
|
687
|
+
...req.base === void 0 ? [] : ["--build-context", `${req.base.ref}=oci-layout://${req.base.layout}@${layoutDigest(req.base.layout)}`],
|
|
688
|
+
"--tag",
|
|
689
|
+
req.tag,
|
|
690
|
+
...labels,
|
|
691
|
+
"--file",
|
|
692
|
+
"-",
|
|
693
|
+
req.context
|
|
694
|
+
];
|
|
695
|
+
const result = await trackImageBuild(() => run("docker", args, { logPath: req.logPath, input: req.dockerfile }));
|
|
696
|
+
if (result.status !== 0 && isOutOfMemory(`${result.stdout}
|
|
697
|
+
${result.stderr}`)) throw outOfMemory(builder, req.logPath);
|
|
698
|
+
return result.status;
|
|
699
|
+
},
|
|
700
|
+
async devcontainerBuild(req) {
|
|
701
|
+
const builder = builderFor();
|
|
702
|
+
const { command, args } = devcontainerBuildInvocation(cli(), req);
|
|
703
|
+
if (req.layoutOut !== void 0) {
|
|
704
|
+
rmSync(req.layoutOut, { recursive: true, force: true });
|
|
705
|
+
}
|
|
706
|
+
const result = await trackImageBuild(() => run(command, args, { logPath: req.logPath, env: { BUILDX_BUILDER: builder.name } }));
|
|
707
|
+
if (result.status !== 0 && isOutOfMemory(`${result.stdout}
|
|
708
|
+
${result.stderr}`)) throw outOfMemory(builder, req.logPath);
|
|
709
|
+
return result.status;
|
|
710
|
+
},
|
|
711
|
+
async remove(id) {
|
|
712
|
+
const result = await run("docker", ["rmi", id]);
|
|
713
|
+
if (result.status !== 0) throw new Error(result.stderr.trim() || `docker rmi ${id} exited ${result.status}`);
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export { BUILD_LABEL, COMMAND_DIR, HASH_ARGV, JUDGE_DIR, JUDGE_MAX_TURNS, JUDGE_PROGRAM, JUDGE_REPORT, MAX_REPORT_BYTES, MIN_BUILDX_VERSION, RUNNER_LABEL, builderName, builderNameFor, builderSpec, buildxState, childEnvironment, containerExecArgs, containerRunArgs, createDockerHost, dockerIdentity, ensureBuilder, ensureFailureCount, ensureRunnerIdentity, isOutOfMemory, judgeCommand, parseProgramHash, pathLabelValue, prepareRunArgs, proxyEnvironment, readJobFile, readJobFileChecked, readRunnerIdentity, resolveDevcontainerCli, runnerLabelValue, spawnRun, trackImageBuild, writeJobFile };
|