@outerlayer/cli 0.2.0 → 0.4.1
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 +132 -0
- package/README.md +229 -60
- package/dist/agent-setup-IFH3FB5X.js +197 -0
- package/dist/build-TZUUTRGH.js +9 -0
- package/dist/build-info.json +1 -1
- package/dist/check-7MFH6HNC.js +94 -0
- package/dist/chunk-3TFPDVTI.js +94 -0
- package/dist/{chunk-4TNZMO7V.js → chunk-4C4THABW.js} +5566 -754
- package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
- package/dist/chunk-5Y3QQRUH.js +300 -0
- package/dist/chunk-774HEQL6.js +178 -0
- package/dist/{work-pr-cmd-AZQPWH4H.js → chunk-7WERLFVR.js} +9 -26
- package/dist/chunk-ABIWDUMS.js +104 -0
- package/dist/chunk-AUFG23AA.js +534 -0
- package/dist/chunk-B2G7JHHB.js +42 -0
- package/dist/chunk-BCJNHMZT.js +34 -0
- package/dist/chunk-BCLJSQCV.js +56 -0
- package/dist/chunk-BFESLKPP.js +61 -0
- package/dist/chunk-BJ3KTMDH.js +63 -0
- package/dist/chunk-BKO6JEZI.js +47 -0
- package/dist/chunk-BOLTI6LR.js +37 -0
- package/dist/chunk-CBPPJSAR.js +89 -0
- package/dist/chunk-DM3VFDS3.js +1461 -0
- package/dist/{chunk-65WXAANR.js → chunk-DVDEBNQJ.js} +17 -2
- package/dist/{chunk-TFUIDMOB.js → chunk-EABW6AJQ.js} +12 -1
- package/dist/{chunk-KFYJV2ZG.js → chunk-EMY4I27X.js} +1 -1
- package/dist/{chunk-NTTPJV35.js → chunk-F47JBIAW.js} +3 -1
- package/dist/chunk-F6GFZXZ3.js +60 -0
- package/dist/{chunk-YOOSBOKS.js → chunk-F7CU5ABH.js} +1 -1
- package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
- package/dist/chunk-HZRNMGLF.js +2233 -0
- package/dist/chunk-LF6MLJCY.js +38 -0
- package/dist/{chunk-U32VSRLO.js → chunk-LT5TZHYH.js} +95 -634
- package/dist/chunk-LYCDNMOH.js +83 -0
- package/dist/chunk-M5POMKKW.js +1051 -0
- package/dist/{mcp-install-cmd-FDQH6SEN.js → chunk-MQ3IPHIZ.js} +35 -18
- package/dist/{chunk-D77LS3UI.js → chunk-N5FOE5PS.js} +33 -4
- package/dist/chunk-N6LURUEF.js +64 -0
- package/dist/chunk-OGFGZQA3.js +23 -0
- package/dist/chunk-QDQEUVUF.js +9 -0
- package/dist/chunk-QOZKPLVJ.js +226 -0
- package/dist/chunk-QY22NZUW.js +1764 -0
- package/dist/chunk-RA3O54FC.js +30 -0
- package/dist/chunk-RFUPN6KX.js +71 -0
- package/dist/chunk-SY4GK4SP.js +55 -0
- package/dist/chunk-TBT347UY.js +41 -0
- package/dist/chunk-TWNWNS2Q.js +1084 -0
- package/dist/{chunk-NXLLURA4.js → chunk-UFFXNXLQ.js} +115 -71
- package/dist/chunk-USO2DKBD.js +421 -0
- package/dist/chunk-W4SQCOZU.js +206 -0
- package/dist/{chunk-Q6CL5THG.js → chunk-W5FVUZXV.js} +208 -61
- package/dist/chunk-WFJ65NUD.js +383 -0
- package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
- package/dist/chunk-WO2BXCTQ.js +83 -0
- package/dist/{chunk-I3ETLSNE.js → chunk-XCCLFVXM.js} +120 -11
- package/dist/{context-materialize-6WKT3RBQ.js → chunk-XDDW4FRS.js} +134 -26
- package/dist/chunk-Y7KJLXYN.js +232 -0
- package/dist/chunk-YFHIIH4B.js +102 -0
- package/dist/chunk-YYYXRJUW.js +101 -0
- package/dist/chunk-ZM2IMMYN.js +76 -0
- package/dist/chunk-ZMYPLFG3.js +71 -0
- package/dist/chunk-ZNA27WEV.js +470 -0
- package/dist/{cli-G7DILYJY.js → cli-W62AFRSW.js} +616 -878
- package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
- package/dist/config-XVJYZ4IQ.js +9 -0
- package/dist/connect-cmd-ZTJONP37.js +16 -0
- package/dist/context-adopt-UMA4O5NY.js +105 -0
- package/dist/context-materialize-G2FUFC72.js +19 -0
- package/dist/dist-44CQVWGT.js +6 -0
- package/dist/docker-NEGDLU6D.js +7 -0
- package/dist/doctor-53F2PYY7.js +43 -0
- package/dist/doctor-ZLGZYMDK.js +426 -0
- package/dist/{emit-artifact-cmd-UQVT3OKW.js → emit-artifact-cmd-AKZP7TMF.js} +87 -23
- package/dist/emit-cmd-DCERAU27.js +9 -0
- package/dist/emit-criteria-cmd-7MXQVCKD.js +166 -0
- package/dist/{emit-finding-cmd-FDOU2OPD.js → emit-finding-cmd-N2FTGCVC.js} +34 -31
- package/dist/{emit-result-cmd-H2T4C2K3.js → emit-result-cmd-4CPDBMN2.js} +34 -62
- package/dist/exec-client-4XGXV3XN.js +8 -0
- package/dist/guest-init-GENRHNP7.js +8 -0
- package/dist/hook-fast-EYENPK6F.js +9 -0
- package/dist/{hook-wrap-fast-KXYNX3AD.js → hook-wrap-fast-NPLHJXFK.js} +2 -2
- package/dist/host-key-KDYZ5ECC.js +7 -0
- package/dist/import-capture-cmd-AVZ4VIVJ.js +68 -0
- package/dist/{import-ruler-cmd-7LH2QOMG.js → import-ruler-cmd-GUHL6IY5.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/init-55Y4LHWF.js +36 -0
- package/dist/init-XXEKWZQP.js +221 -0
- package/dist/init-cmd-WLST2F7G.js +135 -0
- package/dist/install-cmd-7WXOIFDO.js +55 -0
- package/dist/lima-XN65D7GN.js +55 -0
- package/dist/login-browser-7XGSV7MA.js +10 -0
- package/dist/{config-POF7DEQW.js → logout-cmd-QAUFDJ6J.js} +3 -1
- package/dist/{logs-TRNPQM42.js → logs-7RYUH5A6.js} +1 -1
- package/dist/loop-JFIBP4B7.js +43 -0
- package/dist/machine-ZBPT2J3R.js +35 -0
- package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
- package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-N6UD2KBX.js} +20 -9
- package/dist/paths-OYKMVYJP.js +6 -0
- package/dist/{pidfile-PTW76F56.js → pidfile-UZRH774M.js} +2 -3
- package/dist/real-deps-SU24ZA2K.js +21 -0
- package/dist/relay-L76HDX72.js +46 -0
- package/dist/settings-J2652U5N.js +7 -0
- package/dist/starter-pack-LIZYMKYQ.js +10 -0
- package/dist/{status-I27IST4L.js → status-UJXWZRN5.js} +30 -13
- package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
- package/dist/sync-cmd-4G4A7EVN.js +28 -0
- package/dist/version-BWM6VLDI.js +6 -0
- package/dist/{watch-3DTPJETH.js → watch-7SKJKGAA.js} +19 -8
- package/dist/{work-claim-cmd-5AUNCLS2.js → work-claim-cmd-5F7VZ42N.js} +34 -21
- package/dist/work-cmd-QAUZ7MTD.js +17 -0
- package/dist/work-comment-cmd-BNCSBTLH.js +106 -0
- package/dist/{work-launch-LT663PB3.js → work-launch-YI4CJDAP.js} +1 -1
- package/dist/work-open-pr-cmd-VFYZHIIW.js +156 -0
- package/dist/work-pr-cmd-PV32ZLSI.js +16 -0
- package/package.json +13 -3
- package/skill-pack/maintained/amend/SKILL.md +104 -0
- package/skill-pack/maintained/emitting-evidence/SKILL.md +108 -0
- package/skill-pack/maintained/emitting-evidence/references/agents-snippet.md +20 -0
- package/skill-pack/maintained/outerlayer/SKILL.md +55 -0
- package/skill-pack/maintained/reporting-findings/SKILL.md +148 -0
- package/skill-pack/template/build/SKILL.md +193 -0
- package/skill-pack/template/build/references/agent-briefs.md +243 -0
- package/skill-pack/template/build/references/criteria-judge.md +91 -0
- package/skill-pack/template/build/references/evidence.md +42 -0
- package/skill-pack/template/build/references/release.md +74 -0
- package/skill-pack/template/build/references/review-briefs.md +275 -0
- package/skill-pack/template/build/references/review-loop.md +158 -0
- package/skill-pack/template/build/scripts/record-criteria.mjs +235 -0
- package/skill-pack/template/spec/SKILL.md +84 -0
- package/skill-pack/template/writing-specs/SKILL.md +134 -0
- package/dist/chunk-DCNOXRMV.js +0 -589
- package/dist/chunk-JJP7YLMN.js +0 -25
- package/dist/chunk-OZ7C3XUE.js +0 -34
- package/dist/chunk-WQ6VGRGZ.js +0 -150
- package/dist/hook-fast-J5LCDHUJ.js +0 -8
- package/dist/import-capture-cmd-EUMBGIV3.js +0 -176
- package/dist/init-PTBITAUO.js +0 -103
- package/dist/login-cmd-IRX6LZT7.js +0 -62
- package/dist/loop-ASZRX3CZ.js +0 -648
- package/dist/sync-cmd-BBAUZ5JD.js +0 -17
- package/dist/work-cmd-2P4BVX47.js +0 -18
|
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, mkdirSync, copyFileSync, writeFileSync } from
|
|
|
5
5
|
import { join, dirname, isAbsolute } from 'path';
|
|
6
6
|
|
|
7
7
|
createRequire(import.meta.url);
|
|
8
|
-
var REGISTERED_EVENTS = ["SessionStart", "SessionEnd", "Stop"];
|
|
8
|
+
var REGISTERED_EVENTS = ["SessionStart", "SessionEnd", "Stop", "PostToolUse"];
|
|
9
9
|
var MARKER = "_outerlayer";
|
|
10
10
|
function settingsPath(scope, opts = {}) {
|
|
11
11
|
const home = opts.home ?? homeFromEnv();
|
|
@@ -43,6 +43,12 @@ function tokenizeCommand(command) {
|
|
|
43
43
|
function quoteBin(bin) {
|
|
44
44
|
return /\s/.test(bin) ? `"${bin}"` : bin;
|
|
45
45
|
}
|
|
46
|
+
function isScriptBin(bin) {
|
|
47
|
+
return /\.[cm]?js$/i.test(bin);
|
|
48
|
+
}
|
|
49
|
+
function cliCommand(cliBin) {
|
|
50
|
+
return isScriptBin(cliBin) ? `node ${quoteBin(cliBin)}` : quoteBin(cliBin);
|
|
51
|
+
}
|
|
46
52
|
function isOurBin(bin, cliBin) {
|
|
47
53
|
if (cliBin !== void 0 && normalizeBinPath(bin) === normalizeBinPath(cliBin)) return true;
|
|
48
54
|
const parts = bin.replace(/\\/g, "/").split("/");
|
|
@@ -63,30 +69,40 @@ function splitCliInvocation(command, cliBin) {
|
|
|
63
69
|
if (typeof command !== "string") return null;
|
|
64
70
|
const tokens = tokenizeCommand(command.trim());
|
|
65
71
|
if (!tokens) return null;
|
|
66
|
-
let i = 0;
|
|
67
72
|
const launcher = (tokens[0] ?? "").replace(/\\/g, "/").split("/").pop() ?? "";
|
|
68
|
-
|
|
73
|
+
const viaNode = launcher === "node" || launcher === "node.exe";
|
|
74
|
+
const viaNpx = launcher === "npx";
|
|
75
|
+
const i = viaNode || viaNpx ? 1 : 0;
|
|
69
76
|
const bin = tokens[i];
|
|
70
77
|
if (!bin || !isOurBin(bin, cliBin)) return null;
|
|
71
|
-
return { bin, args: tokens.slice(i + 1), viaNpx
|
|
78
|
+
return { bin, args: tokens.slice(i + 1), viaNpx, viaNode };
|
|
72
79
|
}
|
|
73
80
|
function parseCliInvocation(command, cliBin) {
|
|
74
81
|
return splitCliInvocation(command, cliBin)?.args ?? null;
|
|
75
82
|
}
|
|
76
83
|
function ourCommandBins(settings) {
|
|
77
|
-
|
|
84
|
+
return unique(ourCommandLaunches(settings).map((l) => l.bin));
|
|
85
|
+
}
|
|
86
|
+
function ourDirectScriptBins(settings) {
|
|
87
|
+
return unique(ourCommandLaunches(settings).filter((l) => !l.viaNode && isScriptBin(l.bin)).map((l) => l.bin));
|
|
88
|
+
}
|
|
89
|
+
function ourCommandLaunches(settings) {
|
|
90
|
+
const launches = [];
|
|
78
91
|
const add = (command) => {
|
|
79
92
|
const parsed = splitCliInvocation(command);
|
|
80
93
|
if (!parsed || parsed.viaNpx) return;
|
|
81
94
|
if (!["hook", "hook-wrap", "statusline"].includes(parsed.args[0] ?? "")) return;
|
|
82
95
|
if (parsed.bin.includes("$") || !isAbsolute(parsed.bin)) return;
|
|
83
|
-
|
|
96
|
+
launches.push({ bin: parsed.bin, viaNode: parsed.viaNode });
|
|
84
97
|
};
|
|
85
98
|
for (const matchers of Object.values(settings?.hooks ?? {})) {
|
|
86
99
|
for (const matcher of matchers ?? []) for (const h of matcher.hooks ?? []) add(h.command);
|
|
87
100
|
}
|
|
88
101
|
add(settings?.statusLine?.command);
|
|
89
|
-
return
|
|
102
|
+
return launches;
|
|
103
|
+
}
|
|
104
|
+
function unique(values) {
|
|
105
|
+
return [...new Set(values)];
|
|
90
106
|
}
|
|
91
107
|
function parseLifecycleHookCommand(command, cliBin) {
|
|
92
108
|
const args = parseCliInvocation(command, cliBin);
|
|
@@ -142,7 +158,7 @@ var SettingsParseError = class extends Error {
|
|
|
142
158
|
};
|
|
143
159
|
function ourEntry(cliBin, event) {
|
|
144
160
|
return {
|
|
145
|
-
hooks: [{ type: "command", command: `${
|
|
161
|
+
hooks: [{ type: "command", command: `${cliCommand(cliBin)} hook ${event}`, [MARKER]: true }]
|
|
146
162
|
};
|
|
147
163
|
}
|
|
148
164
|
function pruneOurHooks(blocks, cliBin) {
|
|
@@ -229,7 +245,7 @@ function wrapHookCommand(cliBin, h, opts = {}) {
|
|
|
229
245
|
if (isWrapped(h, cliBin)) return h;
|
|
230
246
|
const id = encodeWrappedCommand(h.command);
|
|
231
247
|
const flag = opts.captureOutput ? " --capture-output" : "";
|
|
232
|
-
return { type: "command", command: `${
|
|
248
|
+
return { type: "command", command: `${cliCommand(cliBin)} hook-wrap --id ${id}${flag}`, [WRAP_MARKER]: h.command };
|
|
233
249
|
}
|
|
234
250
|
function unwrapHookCommand(h, cliBin) {
|
|
235
251
|
const original = wrappedOriginalCommand(h, cliBin);
|
|
@@ -280,11 +296,11 @@ function repointWrappedHooks(settings, cliBin) {
|
|
|
280
296
|
const h = matcher.hooks[hi];
|
|
281
297
|
const parsed = splitCliInvocation(h.command, cliBin);
|
|
282
298
|
if (!parsed || parsed.args[0] !== "hook-wrap") continue;
|
|
283
|
-
if (normalizeBinPath(parsed.bin) === normalizeBinPath(cliBin)) continue;
|
|
299
|
+
if (normalizeBinPath(parsed.bin) === normalizeBinPath(cliBin) && parsed.viaNode === isScriptBin(cliBin)) continue;
|
|
284
300
|
const original = wrappedOriginalCommand(h, cliBin);
|
|
285
301
|
if (original === null) continue;
|
|
286
302
|
const flag = parsed.args.includes("--capture-output") ? " --capture-output" : "";
|
|
287
|
-
const desired = `${
|
|
303
|
+
const desired = `${cliCommand(cliBin)} hook-wrap --id ${encodeWrappedCommand(original)}${flag}`;
|
|
288
304
|
matcher.hooks[hi] = { ...h, command: desired, [WRAP_MARKER]: original };
|
|
289
305
|
changed = true;
|
|
290
306
|
}
|
|
@@ -293,10 +309,10 @@ function repointWrappedHooks(settings, cliBin) {
|
|
|
293
309
|
return { next, changed };
|
|
294
310
|
}
|
|
295
311
|
function statuslineCommand(cliBin) {
|
|
296
|
-
return `${
|
|
312
|
+
return `${cliCommand(cliBin)} statusline`;
|
|
297
313
|
}
|
|
298
314
|
function statuslineWrapCommand(cliBin, original) {
|
|
299
|
-
return `${
|
|
315
|
+
return `${cliCommand(cliBin)} statusline --wrap-id ${encodeWrappedCommand(original)}`;
|
|
300
316
|
}
|
|
301
317
|
function isOurStatuslineSlot(slot, cliBin) {
|
|
302
318
|
if (slot[MARKER] === true) return true;
|
|
@@ -377,4 +393,4 @@ function unwrapHooks(settings, cliBin) {
|
|
|
377
393
|
return { next, changed, unwrapped };
|
|
378
394
|
}
|
|
379
395
|
|
|
380
|
-
export { AUTO_WRAP_EVENTS, REGISTERED_EVENTS, SettingsParseError, cliBinResolvable, decodeWrappedCommand, isOurHookCommand, isOurStatuslineSlot, listHookCandidates, mergeHooks, mergeStatusline, ourCommandBins, readSettings, removeHooks, removeStatusline, repointWrappedHooks, settingsPath, statuslineWrappedOriginal, unwrapHooks, wrapHooks, wrappedOriginalCommand, writeSettings };
|
|
396
|
+
export { AUTO_WRAP_EVENTS, REGISTERED_EVENTS, SettingsParseError, cliBinResolvable, cliCommand, decodeWrappedCommand, encodeWrappedCommand, isOurHookCommand, isOurHookCommandFor, isOurStatuslineSlot, isWrapped, listHookCandidates, mergeHooks, mergeStatusline, ourCommandBins, ourDirectScriptBins, parseLifecycleHookCommand, readSettings, removeHooks, removeStatusline, repointWrappedHooks, settingsPath, statuslineCommand, statuslineWrappedOriginal, unwrapHooks, wrapHooks, wrappedOriginalCommand, writeSettings };
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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';
|
|
5
|
+
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
6
|
+
import { existsSync, statSync, accessSync, constants, readFileSync } from 'fs';
|
|
7
|
+
import { hostname } from 'os';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
|
|
10
|
+
createRequire(import.meta.url);
|
|
11
|
+
var BUILTIN_CONTAINER = "builtin:container";
|
|
12
|
+
var BUILTIN_PROCESS = "builtin:process";
|
|
13
|
+
var BUILTIN_VM = "builtin:vm";
|
|
14
|
+
function isBuiltinContainer(hook) {
|
|
15
|
+
return hook === BUILTIN_CONTAINER;
|
|
16
|
+
}
|
|
17
|
+
function isBuiltinProcess(hook) {
|
|
18
|
+
return hook === BUILTIN_PROCESS;
|
|
19
|
+
}
|
|
20
|
+
function isBuiltinVm(hook) {
|
|
21
|
+
return hook === BUILTIN_VM;
|
|
22
|
+
}
|
|
23
|
+
function builtinKind(hook) {
|
|
24
|
+
return isBuiltinContainer(hook) ? "container" : isBuiltinProcess(hook) ? "process" : isBuiltinVm(hook) ? "vm" : "own";
|
|
25
|
+
}
|
|
26
|
+
var RUNNER_QUEUES = ["implement", "amend"];
|
|
27
|
+
var RunnerConfigError = class extends Error {
|
|
28
|
+
};
|
|
29
|
+
var DEFAULT_CONCURRENCY = 1;
|
|
30
|
+
var DEFAULT_POLL_SECONDS = 60;
|
|
31
|
+
var DEFAULT_TIME_LIMIT_MINUTES = 480;
|
|
32
|
+
var DEFAULT_IDLE_LIMIT_MINUTES = 30;
|
|
33
|
+
var DEFAULT_BUILD = { memory: "8g", cpus: 4, pids: 4096, disk: "40g", variables: [] };
|
|
34
|
+
var BUILD_KEYS = ["memory", "cpus", "pids", "disk", "variables", "destinations", "allowHosts"];
|
|
35
|
+
var VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,254}$/;
|
|
36
|
+
var MAX_VARIABLES = 100;
|
|
37
|
+
function defaultRunnerConfigPath(home = homeFromEnv()) {
|
|
38
|
+
return join(home, ".outerlayer", "config.json");
|
|
39
|
+
}
|
|
40
|
+
function fail(configPath, key, reason) {
|
|
41
|
+
throw new RunnerConfigError(`${configPath}: ${key} ${reason}`);
|
|
42
|
+
}
|
|
43
|
+
function readConfigFile(configPath) {
|
|
44
|
+
if (!existsSync(configPath)) return {};
|
|
45
|
+
const text = readFileSync(configPath, "utf8");
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(text);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new RunnerConfigError(
|
|
50
|
+
`${configPath}: is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function hasRunnerBlock(configPath) {
|
|
55
|
+
try {
|
|
56
|
+
const runner = readConfigFile(configPath).runner;
|
|
57
|
+
return typeof runner === "object" && runner !== null;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function runnerVmOf(configPath) {
|
|
63
|
+
try {
|
|
64
|
+
const vm = readConfigFile(configPath).runnerVm;
|
|
65
|
+
return typeof vm?.instance === "string" && typeof vm.since === "string" ? { instance: vm.instance, since: vm.since } : void 0;
|
|
66
|
+
} catch {
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
var RUNNER_KEYS = [
|
|
71
|
+
"host",
|
|
72
|
+
"queues",
|
|
73
|
+
"concurrency",
|
|
74
|
+
"pollSeconds",
|
|
75
|
+
"timeLimitMinutes",
|
|
76
|
+
"idleLimitMinutes",
|
|
77
|
+
"commands",
|
|
78
|
+
"hooks",
|
|
79
|
+
"build",
|
|
80
|
+
"autoUpdate"
|
|
81
|
+
];
|
|
82
|
+
function runnerNumbers() {
|
|
83
|
+
return [
|
|
84
|
+
{ key: "concurrency", min: 1 },
|
|
85
|
+
// Below five seconds a runner is asking the gateway a question it has
|
|
86
|
+
// just been answered. Above one day the gateway refuses every heartbeat
|
|
87
|
+
// this runner sends (`POST /v1/hosts/heartbeat` caps `pollSeconds` at
|
|
88
|
+
// 86400), so the host would poll and never appear in `GET /v1/hosts`.
|
|
89
|
+
{ key: "pollSeconds", min: 5, max: 86400 },
|
|
90
|
+
// A build's item key expires 24 hours after its lease was created, and
|
|
91
|
+
// the time limit is counted from job start, after the claim and the
|
|
92
|
+
// provision hook. The runner still syncs and releases once the limit
|
|
93
|
+
// fires, so the ceiling leaves two hours of the key's life for the
|
|
94
|
+
// provision hook, the final sync and the release.
|
|
95
|
+
{ key: "timeLimitMinutes", min: 1, max: 1320 },
|
|
96
|
+
{ key: "idleLimitMinutes", min: 1 }
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
function nearestKey(unknown, accepted) {
|
|
100
|
+
const lower = unknown.toLowerCase();
|
|
101
|
+
let best;
|
|
102
|
+
for (const key of accepted) {
|
|
103
|
+
const distance = editDistance(lower, key.toLowerCase());
|
|
104
|
+
if (distance <= Math.max(1, Math.floor(key.length / 4)) && (!best || distance < best.distance)) {
|
|
105
|
+
best = { key, distance };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return best?.key;
|
|
109
|
+
}
|
|
110
|
+
function editDistance(a, b) {
|
|
111
|
+
const rows = Array.from({ length: a.length + 1 }, (_unused, i) => [i, ...Array(b.length).fill(0)]);
|
|
112
|
+
for (let j = 0; j <= b.length; j++) rows[0][j] = j;
|
|
113
|
+
for (let i = 1; i <= a.length; i++) {
|
|
114
|
+
for (let j = 1; j <= b.length; j++) {
|
|
115
|
+
rows[i][j] = Math.min(
|
|
116
|
+
rows[i - 1][j] + 1,
|
|
117
|
+
rows[i][j - 1] + 1,
|
|
118
|
+
rows[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return rows[a.length][b.length];
|
|
123
|
+
}
|
|
124
|
+
function checkHooks(config, env = process.env) {
|
|
125
|
+
const named = [
|
|
126
|
+
["runner.hooks.provision", config.hooks.provision],
|
|
127
|
+
["runner.hooks.cleanup", config.hooks.cleanup],
|
|
128
|
+
["runner.hooks.report", config.hooks.report]
|
|
129
|
+
];
|
|
130
|
+
for (const [key, path] of named) {
|
|
131
|
+
if (path === void 0) continue;
|
|
132
|
+
if (isBuiltinContainer(path) || isBuiltinProcess(path) || isBuiltinVm(path)) continue;
|
|
133
|
+
if (path.includes("$") || path.startsWith("~")) {
|
|
134
|
+
return `${key}: ${path} \u2014 hooks are executable paths, not shell lines; $HOME and ~ are not expanded`;
|
|
135
|
+
}
|
|
136
|
+
if (!existsSync(path)) return `${key}: ${path} does not exist`;
|
|
137
|
+
if (!statSync(path).isFile()) return `${key}: ${path} is not a file`;
|
|
138
|
+
try {
|
|
139
|
+
accessSync(path, constants.X_OK);
|
|
140
|
+
} catch {
|
|
141
|
+
return `${key}: ${path} is not executable`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return checkDestinationVariables(config, env);
|
|
145
|
+
}
|
|
146
|
+
function checkDestinationVariables(config, env) {
|
|
147
|
+
const destinations = config.build?.destinations ?? [];
|
|
148
|
+
for (const [index, destination] of destinations.entries()) {
|
|
149
|
+
for (const name of destination.header.variables) {
|
|
150
|
+
if (env[name] === void 0 || env[name] === "") {
|
|
151
|
+
return `runner.build.destinations[${index}].header: ${name} is not set in the runner's environment`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
function loadRunnerConfig(configPath) {
|
|
158
|
+
const config = readConfigFile(configPath);
|
|
159
|
+
if (!config.url || !config.apiKey || !config.appId) {
|
|
160
|
+
fail(configPath, "url/apiKey/appId", "missing \u2014 pipe a factory key into `outerlayer login` for this config file first; a runner uses a factory key, never a person's login");
|
|
161
|
+
}
|
|
162
|
+
const runner = config.runner;
|
|
163
|
+
if (!runner) {
|
|
164
|
+
fail(configPath, "runner", "block is missing");
|
|
165
|
+
}
|
|
166
|
+
for (const key of Object.keys(runner)) {
|
|
167
|
+
if (RUNNER_KEYS.includes(key)) continue;
|
|
168
|
+
const near = nearestKey(key, RUNNER_KEYS);
|
|
169
|
+
fail(configPath, `runner.${key}`, near ? `is not a key this block accepts; did you mean ${near}?` : "is not a key this block accepts");
|
|
170
|
+
}
|
|
171
|
+
for (const { key, min, max = Number.POSITIVE_INFINITY } of runnerNumbers()) {
|
|
172
|
+
const value = runner[key];
|
|
173
|
+
if (value === void 0) continue;
|
|
174
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min) {
|
|
175
|
+
fail(configPath, `runner.${key}`, `must be a whole number of at least ${min}`);
|
|
176
|
+
}
|
|
177
|
+
if (value > max) {
|
|
178
|
+
fail(configPath, `runner.${key}`, `must be at most ${max}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (runner.autoUpdate !== void 0 && typeof runner.autoUpdate !== "boolean") {
|
|
182
|
+
fail(configPath, "runner.autoUpdate", "must be true or false");
|
|
183
|
+
}
|
|
184
|
+
const queues = runner.queues && runner.queues.length > 0 ? runner.queues : [...RUNNER_QUEUES];
|
|
185
|
+
for (const queue of queues) {
|
|
186
|
+
if (!RUNNER_QUEUES.includes(queue)) {
|
|
187
|
+
fail(configPath, "runner.queues", `names an unknown queue "${queue}" \u2014 must be implement or amend`);
|
|
188
|
+
}
|
|
189
|
+
if (!runner.commands?.[queue]) {
|
|
190
|
+
fail(configPath, `runner.commands.${queue}`, "is required for an enabled queue");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!runner.hooks?.provision) fail(configPath, "runner.hooks.provision", "is required");
|
|
194
|
+
if (!runner.hooks?.cleanup) fail(configPath, "runner.hooks.cleanup", "is required");
|
|
195
|
+
checkBuiltinHooks(configPath, runner.hooks);
|
|
196
|
+
const build = resolveBuild(configPath, runner.build);
|
|
197
|
+
if (config.scrub?.literals !== void 0 && !Array.isArray(config.scrub.literals)) {
|
|
198
|
+
fail(configPath, "scrub.literals", "must be a list of strings");
|
|
199
|
+
}
|
|
200
|
+
if (config.scrub?.patterns !== void 0 && !Array.isArray(config.scrub.patterns)) {
|
|
201
|
+
fail(configPath, "scrub.patterns", "must be a list of {label, pattern} entries");
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
configPath,
|
|
205
|
+
url: config.url,
|
|
206
|
+
apiKey: config.apiKey,
|
|
207
|
+
appId: config.appId,
|
|
208
|
+
repos: config.repos,
|
|
209
|
+
scrub: config.scrub,
|
|
210
|
+
host: runner.host ?? hostname(),
|
|
211
|
+
queues,
|
|
212
|
+
concurrency: runner.concurrency ?? DEFAULT_CONCURRENCY,
|
|
213
|
+
pollSeconds: runner.pollSeconds ?? DEFAULT_POLL_SECONDS,
|
|
214
|
+
timeLimitMinutes: runner.timeLimitMinutes ?? DEFAULT_TIME_LIMIT_MINUTES,
|
|
215
|
+
idleLimitMinutes: runner.idleLimitMinutes ?? DEFAULT_IDLE_LIMIT_MINUTES,
|
|
216
|
+
commands: runner.commands ?? {},
|
|
217
|
+
hooks: {
|
|
218
|
+
provision: runner.hooks.provision,
|
|
219
|
+
cleanup: runner.hooks.cleanup,
|
|
220
|
+
report: runner.hooks.report
|
|
221
|
+
},
|
|
222
|
+
build,
|
|
223
|
+
// A host with executables of its own for provision and cleanup chose
|
|
224
|
+
// its own environment, and an update could change what its hooks run
|
|
225
|
+
// under; it opts in. The built-in hooks are the runner's own. Provision
|
|
226
|
+
// and cleanup are built-in together or not at all (checkBuiltinHooks), so
|
|
227
|
+
// provision alone tells which.
|
|
228
|
+
autoUpdate: runner.autoUpdate ?? builtinKind(runner.hooks.provision) !== "own"
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function checkBuiltinHooks(configPath, hooks) {
|
|
232
|
+
for (const key of ["provision", "cleanup", "report"]) {
|
|
233
|
+
const value = hooks[key];
|
|
234
|
+
if (value === void 0 || !value.startsWith("builtin:")) continue;
|
|
235
|
+
if (key === "report") fail(configPath, "runner.hooks.report", `cannot be ${value}: the report hook stays an executable`);
|
|
236
|
+
if (!isBuiltinContainer(value) && !isBuiltinProcess(value) && !isBuiltinVm(value)) {
|
|
237
|
+
fail(configPath, `runner.hooks.${key}`, `names an unknown built-in hook "${value}"; the ones that exist are ${BUILTIN_CONTAINER}, ${BUILTIN_PROCESS} and ${BUILTIN_VM}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const provision = hooks.provision;
|
|
241
|
+
const cleanup = hooks.cleanup;
|
|
242
|
+
if (isBuiltinContainer(provision) !== isBuiltinContainer(cleanup) || isBuiltinProcess(provision) !== isBuiltinProcess(cleanup) || isBuiltinVm(provision) !== isBuiltinVm(cleanup)) {
|
|
243
|
+
fail(configPath, "runner.hooks", `provision and cleanup must both be ${BUILTIN_CONTAINER}, both be ${BUILTIN_PROCESS}, both be ${BUILTIN_VM}, or neither`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function resolveBuild(configPath, block) {
|
|
247
|
+
if (block === void 0) return { ...DEFAULT_BUILD, variables: [] };
|
|
248
|
+
if (typeof block !== "object" || block === null || Array.isArray(block)) fail(configPath, "runner.build", "must be an object");
|
|
249
|
+
const given = block;
|
|
250
|
+
for (const key of Object.keys(given)) {
|
|
251
|
+
if (BUILD_KEYS.includes(key)) continue;
|
|
252
|
+
const near = nearestKey(key, BUILD_KEYS);
|
|
253
|
+
fail(configPath, `runner.build.${key}`, near ? `is not a key this block accepts; did you mean ${near}?` : "is not a key this block accepts");
|
|
254
|
+
}
|
|
255
|
+
for (const key of ["memory", "disk"]) {
|
|
256
|
+
const value = given[key];
|
|
257
|
+
if (value === void 0) continue;
|
|
258
|
+
if (typeof value !== "string" || parseSize(value) === void 0) fail(configPath, `runner.build.${key}`, 'must be a size such as "8g" or "512m"');
|
|
259
|
+
}
|
|
260
|
+
if (given.cpus !== void 0 && (typeof given.cpus !== "number" || !Number.isFinite(given.cpus) || given.cpus <= 0)) {
|
|
261
|
+
fail(configPath, "runner.build.cpus", "must be a number above 0");
|
|
262
|
+
}
|
|
263
|
+
if (given.pids !== void 0 && (typeof given.pids !== "number" || !Number.isInteger(given.pids) || given.pids < 1)) {
|
|
264
|
+
fail(configPath, "runner.build.pids", "must be a whole number of at least 1");
|
|
265
|
+
}
|
|
266
|
+
const variables = [];
|
|
267
|
+
if (given.variables !== void 0) {
|
|
268
|
+
if (!Array.isArray(given.variables)) fail(configPath, "runner.build.variables", "must be a list of variable names");
|
|
269
|
+
if (given.variables.length > MAX_VARIABLES) fail(configPath, "runner.build.variables", `names ${given.variables.length} variables; a release records at most ${MAX_VARIABLES}`);
|
|
270
|
+
for (const name of given.variables) {
|
|
271
|
+
if (typeof name !== "string" || !VARIABLE_NAME.test(name)) fail(configPath, "runner.build.variables", `names ${JSON.stringify(name)}, which is not a variable name`);
|
|
272
|
+
if (isRefusedInConfig(name)) fail(configPath, "runner.build.variables", `names ${name}, which the runner sets itself`);
|
|
273
|
+
if (!variables.includes(name)) variables.push(name);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const destinations = given.destinations === void 0 ? [] : resolveDestinations(given.destinations, variables, isReservedVariable, (key, reason) => fail(configPath, key, reason));
|
|
277
|
+
let allowHosts;
|
|
278
|
+
if (given.allowHosts !== void 0) {
|
|
279
|
+
if (!Array.isArray(given.allowHosts)) fail(configPath, "runner.build.allowHosts", "must be a list of host names");
|
|
280
|
+
if (given.allowHosts.length > MAX_ALLOW_HOSTS) fail(configPath, "runner.build.allowHosts", `names ${given.allowHosts.length} hosts; at most ${MAX_ALLOW_HOSTS}`);
|
|
281
|
+
allowHosts = [];
|
|
282
|
+
for (const entry of given.allowHosts) {
|
|
283
|
+
const problem = allowHostProblem(entry);
|
|
284
|
+
if (problem !== void 0) fail(configPath, "runner.build.allowHosts", `names ${JSON.stringify(entry)}, which ${problem}`);
|
|
285
|
+
const host = entry.toLowerCase();
|
|
286
|
+
if (!allowHosts.includes(host)) allowHosts.push(host);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
memory: given.memory ?? DEFAULT_BUILD.memory,
|
|
291
|
+
cpus: given.cpus ?? DEFAULT_BUILD.cpus,
|
|
292
|
+
pids: given.pids ?? DEFAULT_BUILD.pids,
|
|
293
|
+
disk: given.disk ?? DEFAULT_BUILD.disk,
|
|
294
|
+
variables,
|
|
295
|
+
...destinations.length === 0 ? {} : { destinations },
|
|
296
|
+
...allowHosts === void 0 ? {} : { allowHosts }
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export { RunnerConfigError, builtinKind, checkHooks, defaultRunnerConfigPath, hasRunnerBlock, isBuiltinContainer, isBuiltinProcess, isBuiltinVm, loadRunnerConfig, resolveBuild, runnerVmOf };
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { RecipeError } from './chunk-QOZKPLVJ.js';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { existsSync, mkdirSync, createWriteStream, readFileSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { createRequire as createRequire$1 } from 'module';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
createRequire(import.meta.url);
|
|
11
|
+
var PACKAGE = "@devcontainers/cli";
|
|
12
|
+
function buildRootFromDist(moduleDir = dirname(fileURLToPath(import.meta.url))) {
|
|
13
|
+
try {
|
|
14
|
+
const info = JSON.parse(readFileSync(join(moduleDir, "build-info.json"), "utf8"));
|
|
15
|
+
return typeof info.root === "string" ? info.root : void 0;
|
|
16
|
+
} catch {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function resolveDevcontainerCli(opts = {}) {
|
|
21
|
+
const resolve = opts.resolve ?? createRequire$1(import.meta.url).resolve;
|
|
22
|
+
try {
|
|
23
|
+
const bin = join(dirname(resolve(`${PACKAGE}/package.json`)), "devcontainer.js");
|
|
24
|
+
if (existsSync(bin)) return { command: process.execPath, args: [bin] };
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
const buildRoot = opts.buildRoot ?? buildRootFromDist();
|
|
28
|
+
const fallback = buildRoot === void 0 ? void 0 : join(buildRoot, "node_modules", PACKAGE);
|
|
29
|
+
if (fallback !== void 0 && existsSync(join(fallback, "devcontainer.js"))) {
|
|
30
|
+
return { command: process.execPath, args: [join(fallback, "devcontainer.js")] };
|
|
31
|
+
}
|
|
32
|
+
throw new RecipeError(
|
|
33
|
+
"devcontainer_cli_missing",
|
|
34
|
+
`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.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function devcontainerBuildInvocation(cli, req) {
|
|
38
|
+
return {
|
|
39
|
+
command: cli.command,
|
|
40
|
+
args: [
|
|
41
|
+
...cli.args,
|
|
42
|
+
"build",
|
|
43
|
+
"--workspace-folder",
|
|
44
|
+
req.workspace,
|
|
45
|
+
"--config",
|
|
46
|
+
req.config,
|
|
47
|
+
"--image-name",
|
|
48
|
+
req.imageName,
|
|
49
|
+
...Object.entries(req.labels).flatMap(([key, value]) => ["--label", `${key}=${value}`]),
|
|
50
|
+
"--log-format",
|
|
51
|
+
"text"
|
|
52
|
+
]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/runner/image/docker.ts
|
|
57
|
+
var CHILD_ENVIRONMENT_NAMES = [
|
|
58
|
+
"PATH",
|
|
59
|
+
"HOME",
|
|
60
|
+
"USER",
|
|
61
|
+
"LOGNAME",
|
|
62
|
+
"TMPDIR",
|
|
63
|
+
"LANG",
|
|
64
|
+
"LC_ALL",
|
|
65
|
+
"DOCKER_HOST",
|
|
66
|
+
"DOCKER_CONFIG",
|
|
67
|
+
"DOCKER_CONTEXT",
|
|
68
|
+
"DOCKER_CERT_PATH",
|
|
69
|
+
"DOCKER_TLS_VERIFY",
|
|
70
|
+
"DOCKER_API_VERSION",
|
|
71
|
+
"DOCKER_BUILDKIT",
|
|
72
|
+
"XDG_RUNTIME_DIR",
|
|
73
|
+
"XDG_CONFIG_HOME",
|
|
74
|
+
"XDG_DATA_HOME",
|
|
75
|
+
"XDG_CACHE_HOME",
|
|
76
|
+
"XDG_STATE_HOME"
|
|
77
|
+
];
|
|
78
|
+
function childEnvironment(base) {
|
|
79
|
+
const env = {};
|
|
80
|
+
for (const name of CHILD_ENVIRONMENT_NAMES) {
|
|
81
|
+
const value = base[name];
|
|
82
|
+
if (value !== void 0) env[name] = value;
|
|
83
|
+
}
|
|
84
|
+
return env;
|
|
85
|
+
}
|
|
86
|
+
var spawnRun = (command, args, opts = {}) => new Promise((resolve) => {
|
|
87
|
+
let log;
|
|
88
|
+
if (opts.logPath !== void 0) {
|
|
89
|
+
mkdirSync(dirname(opts.logPath), { recursive: true });
|
|
90
|
+
log = createWriteStream(opts.logPath, { flags: "a" });
|
|
91
|
+
}
|
|
92
|
+
let stdout = "";
|
|
93
|
+
let stderr = "";
|
|
94
|
+
let settled = false;
|
|
95
|
+
const finish = (status, extra = "") => {
|
|
96
|
+
if (settled) return;
|
|
97
|
+
settled = true;
|
|
98
|
+
const result = { status, stdout, stderr: stderr + extra };
|
|
99
|
+
if (log === void 0) {
|
|
100
|
+
resolve(result);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (extra) log.write(extra);
|
|
104
|
+
log.end(() => resolve(result));
|
|
105
|
+
};
|
|
106
|
+
const child = spawn(command, args, {
|
|
107
|
+
env: { ...childEnvironment(process.env), ...opts.env },
|
|
108
|
+
stdio: [opts.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
|
|
109
|
+
});
|
|
110
|
+
child.stdout.on("data", (chunk) => {
|
|
111
|
+
stdout += chunk.toString();
|
|
112
|
+
log?.write(chunk);
|
|
113
|
+
});
|
|
114
|
+
child.stderr.on("data", (chunk) => {
|
|
115
|
+
stderr += chunk.toString();
|
|
116
|
+
log?.write(chunk);
|
|
117
|
+
});
|
|
118
|
+
child.on("error", (err) => finish(127, `${command}: ${err.message}
|
|
119
|
+
`));
|
|
120
|
+
child.on("close", (code, signal) => finish(code ?? (signal ? 128 : 1)));
|
|
121
|
+
if (opts.input !== void 0) {
|
|
122
|
+
child.stdin.on("error", () => void 0);
|
|
123
|
+
child.stdin.end(opts.input);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
function toInfo(row) {
|
|
127
|
+
return { id: row.Id, createdAt: row.Created, labels: row.Config?.Labels ?? {} };
|
|
128
|
+
}
|
|
129
|
+
var lines = (text) => [...new Set(text.split("\n").map((l) => l.trim()).filter(Boolean))];
|
|
130
|
+
function createDockerHost(deps = {}) {
|
|
131
|
+
const run = deps.run ?? spawnRun;
|
|
132
|
+
const cli = deps.cli ?? resolveDevcontainerCli;
|
|
133
|
+
const inspectMany = async (refs) => {
|
|
134
|
+
const result = await run("docker", ["image", "inspect", ...refs]);
|
|
135
|
+
return result.status === 0 ? JSON.parse(result.stdout).map(toInfo) : [];
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
async inspect(ref) {
|
|
139
|
+
return (await inspectMany([ref]))[0] ?? null;
|
|
140
|
+
},
|
|
141
|
+
async list(repository) {
|
|
142
|
+
const ids = await run("docker", [
|
|
143
|
+
"image",
|
|
144
|
+
"ls",
|
|
145
|
+
"--all",
|
|
146
|
+
"--no-trunc",
|
|
147
|
+
"--quiet",
|
|
148
|
+
"--filter",
|
|
149
|
+
`label=ai.outerlayer.repository=${repository}`
|
|
150
|
+
]);
|
|
151
|
+
const found = lines(ids.stdout);
|
|
152
|
+
return found.length === 0 ? [] : inspectMany(found);
|
|
153
|
+
},
|
|
154
|
+
async inUse() {
|
|
155
|
+
const containers = lines((await run("docker", ["ps", "--all", "--no-trunc", "--quiet"])).stdout);
|
|
156
|
+
if (containers.length === 0) return [];
|
|
157
|
+
return lines((await run("docker", ["inspect", "--format", "{{.Image}}", ...containers])).stdout);
|
|
158
|
+
},
|
|
159
|
+
async buildDockerfile(req) {
|
|
160
|
+
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
|
+
});
|
|
165
|
+
return result.status;
|
|
166
|
+
},
|
|
167
|
+
async devcontainerBuild(req) {
|
|
168
|
+
const { command, args } = devcontainerBuildInvocation(cli(), req);
|
|
169
|
+
return (await run(command, args, { logPath: req.logPath })).status;
|
|
170
|
+
},
|
|
171
|
+
async remove(id) {
|
|
172
|
+
const result = await run("docker", ["rmi", id]);
|
|
173
|
+
if (result.status !== 0) throw new Error(result.stderr.trim() || `docker rmi ${id} exited ${result.status}`);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { childEnvironment, createDockerHost, resolveDevcontainerCli, spawnRun };
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { detectActiveSession } from './chunk-
|
|
4
|
-
import { resolveRepoIdentity } from './chunk-
|
|
5
|
-
import './chunk-YRYMNBGQ.js';
|
|
6
|
-
import './chunk-65WXAANR.js';
|
|
3
|
+
import { detectActiveSession } from './chunk-BFESLKPP.js';
|
|
4
|
+
import { resolveRepoIdentity } from './chunk-4C4THABW.js';
|
|
7
5
|
import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
10
|
-
import './chunk-I3ETLSNE.js';
|
|
11
|
-
import './chunk-YOOSBOKS.js';
|
|
12
|
-
import './chunk-R5KBGVII.js';
|
|
13
|
-
import { readCloudConfig } from './chunk-TFUIDMOB.js';
|
|
6
|
+
import { defaultFetch } from './chunk-BCLJSQCV.js';
|
|
7
|
+
import { unauthorizedMessage, resolveGatewayCredentials } from './chunk-BOLTI6LR.js';
|
|
14
8
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
15
|
-
import './chunk-Z5GEFMRW.js';
|
|
16
9
|
|
|
17
10
|
createRequire(import.meta.url);
|
|
18
11
|
|
|
@@ -23,19 +16,9 @@ var RESET = "\x1B[0m";
|
|
|
23
16
|
var WorkPrCommandError = class extends Error {
|
|
24
17
|
};
|
|
25
18
|
function resolveCredentials(opts) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const url = opts.url ?? env.OUTERLAYER_URL ?? fileConfig.url;
|
|
30
|
-
const apiKey = opts.apiKey ?? env.OUTERLAYER_API_KEY ?? fileConfig.apiKey;
|
|
31
|
-
const appId = opts.appId ?? env.OUTERLAYER_APP_ID ?? fileConfig.appId;
|
|
32
|
-
if (!url || !apiKey || !appId) {
|
|
33
|
-
const missing = [!url && "--url", !apiKey && "--api-key", !appId && "--app-id"].filter(Boolean).join(", ");
|
|
34
|
-
throw new WorkPrCommandError(
|
|
35
|
-
`missing ${missing}. Run \`outerlayer login --url <your gateway>\` and paste the key at the prompt, or set OUTERLAYER_URL / OUTERLAYER_API_KEY / OUTERLAYER_APP_ID. Mint a key in the dashboard: Settings \u2192 API keys.`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
return { url, apiKey, appId };
|
|
19
|
+
const resolved = resolveGatewayCredentials(opts);
|
|
20
|
+
if (!resolved.ok) throw new WorkPrCommandError(resolved.message);
|
|
21
|
+
return { url: resolved.url, apiKey: resolved.credential, appId: resolved.appId };
|
|
39
22
|
}
|
|
40
23
|
function resolveRepository(repoFlag, cwd) {
|
|
41
24
|
if (repoFlag) return repoFlag;
|
|
@@ -69,7 +52,7 @@ async function runWorkPr(opts) {
|
|
|
69
52
|
"no active session detected. `work pr` declares a pull request from inside the session that opened it."
|
|
70
53
|
);
|
|
71
54
|
}
|
|
72
|
-
const fetchImpl = opts.fetchImpl ??
|
|
55
|
+
const fetchImpl = opts.fetchImpl ?? defaultFetch;
|
|
73
56
|
const endpoint = new URL("/v1/work-items/pull-requests", creds.url).toString();
|
|
74
57
|
let response;
|
|
75
58
|
try {
|
|
@@ -87,7 +70,7 @@ async function runWorkPr(opts) {
|
|
|
87
70
|
}
|
|
88
71
|
if (response.status === 401) {
|
|
89
72
|
throw new WorkPrCommandError(
|
|
90
|
-
|
|
73
|
+
unauthorizedMessage(creds.apiKey)
|
|
91
74
|
);
|
|
92
75
|
}
|
|
93
76
|
let json;
|