@outerlayer/cli 0.34.0-next.20261024180339 → 0.34.0-next.20261024231715
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{agent-setup-FBAT5LGX.js → agent-setup-HEJM3D3V.js} +9 -9
- package/dist/build-info.json +1 -1
- package/dist/{check-HCKDHD2R.js → check-OOUGS65D.js} +20 -20
- package/dist/{chunk-SVLFZLTE.js → chunk-272AKVRP.js} +5 -5
- package/dist/{chunk-B5MSDF5B.js → chunk-2AIOOU3A.js} +4 -4
- package/dist/{chunk-BTHH5H5H.js → chunk-3IH6K7KG.js} +2 -2
- package/dist/{chunk-XFAJQZN2.js → chunk-4P6QGMPN.js} +2 -2
- package/dist/{chunk-43ZOV4OR.js → chunk-4Y54SQXH.js} +10 -10
- package/dist/{chunk-MHGIN2X6.js → chunk-5HZGLLNW.js} +2 -2
- package/dist/{chunk-74Q24R3B.js → chunk-5QBUX5YZ.js} +7 -7
- package/dist/{chunk-6AQLRSYU.js → chunk-62LXQM7U.js} +2 -2
- package/dist/{chunk-SHIGGHZO.js → chunk-65AKMFBH.js} +2 -2
- package/dist/{chunk-OHJDH5GV.js → chunk-6NUPXDAA.js} +1 -1
- package/dist/{chunk-ZADIXMKT.js → chunk-7WM7K2DY.js} +1 -1
- package/dist/{chunk-XLJHFZRL.js → chunk-ACRRO3MB.js} +7 -7
- package/dist/{chunk-OLCIPTST.js → chunk-AP2K4LPI.js} +4 -4
- package/dist/{chunk-YPCDDQFU.js → chunk-DQ6YO7FH.js} +1 -1
- package/dist/{chunk-5HLA7KPK.js → chunk-E4ASIOVL.js} +1 -1
- package/dist/{chunk-NVDPSH72.js → chunk-EE2ZZGQV.js} +1 -1
- package/dist/{chunk-GWF3ZRUX.js → chunk-EN3YWO7Q.js} +1 -1
- package/dist/{chunk-CQRPR4WZ.js → chunk-FGXT5OXS.js} +56 -126
- package/dist/{chunk-3KT2IWV3.js → chunk-FVKH3PMV.js} +49 -9
- package/dist/{chunk-UF2FU3CM.js → chunk-JWNH4ZTF.js} +2 -2
- package/dist/{chunk-2G7PJAJZ.js → chunk-K5Q6T7SE.js} +1 -1
- package/dist/{chunk-TIRHZC2M.js → chunk-NKOM4JKY.js} +2 -2
- package/dist/{chunk-B44BFR2Q.js → chunk-O6DYCIVW.js} +3 -3
- package/dist/{chunk-YREGBU3O.js → chunk-QDVGOMSC.js} +1 -1
- package/dist/{chunk-5XZ6BUPH.js → chunk-QMWUPRYQ.js} +1 -1
- package/dist/{chunk-HMCBX3PL.js → chunk-RCRE42V6.js} +1 -1
- package/dist/{chunk-R2CLQJA2.js → chunk-TDAQHYM4.js} +1 -1
- package/dist/{chunk-SMAZA7DM.js → chunk-XFFUXXI4.js} +2 -2
- package/dist/{chunk-TFKROYY2.js → chunk-YEVPRO7L.js} +3 -3
- package/dist/{chunk-HBVHRC3A.js → chunk-YGSY2NWU.js} +2 -2
- package/dist/{chunk-PBIYM2Z6.js → chunk-YIBFGAYI.js} +2 -2
- package/dist/{chunk-HIGP4WOC.js → chunk-Z4KWHSO4.js} +3 -3
- package/dist/{chunk-ZGB4ZJ5E.js → chunk-ZQSANKS4.js} +1 -1
- package/dist/{chunk-5WCBFLES.js → chunk-ZW6L275N.js} +7 -7
- package/dist/{claude-launcher-YXRS4EZE.js → claude-launcher-MORG66PJ.js} +4 -4
- package/dist/{cli-ADIMCYPF.js → cli-XI7RUSC5.js} +90 -94
- package/dist/{command-YQBQDIAM.js → command-BJSHY6OB.js} +10 -10
- package/dist/{connect-cmd-42JHZKWN.js → connect-cmd-YAUOCX4X.js} +4 -4
- package/dist/{container-start-W4VEGRL5.js → container-start-QADK3YGL.js} +17 -17
- package/dist/{context-cleanup-YGWYIFFV.js → context-cleanup-LUBEZB3L.js} +2 -2
- package/dist/{context-show-cmd-LIWHRWVT.js → context-show-cmd-VHY3OBIA.js} +5 -5
- package/dist/{control-plane-launch-AXBWT5CC.js → control-plane-launch-QSBWTA2V.js} +5 -5
- package/dist/{doctor-AZZJBJYO.js → doctor-UR76MCGI.js} +11 -11
- package/dist/{emit-artifact-cmd-NU5JURAA.js → emit-artifact-cmd-DP2EQ54N.js} +9 -9
- package/dist/{emit-cmd-A4CB7BEC.js → emit-cmd-7NMNKDK7.js} +5 -5
- package/dist/{emit-criteria-cmd-RAU3PDEK.js → emit-criteria-cmd-TTFBN6RK.js} +6 -6
- package/dist/{emit-demo-cmd-524A54D5.js → emit-demo-cmd-Z7YGLEBJ.js} +10 -10
- package/dist/{emit-finding-cmd-LE3HFQWL.js → emit-finding-cmd-IQRD3J2X.js} +6 -6
- package/dist/{emit-result-cmd-XT2VEPAJ.js → emit-result-cmd-GIWB6UHH.js} +8 -8
- package/dist/{emit-step-cmd-LXOKBFQE.js → emit-step-cmd-PU5TTU6C.js} +6 -6
- package/dist/{findings-cmd-O5HUK26I.js → findings-cmd-7J5SHCUO.js} +6 -6
- package/dist/{fleet-readiness-VAKMSSWI.js → fleet-readiness-G63D76EX.js} +6 -6
- package/dist/{governing-source-FNSH4V4A.js → governing-source-DLESDP6T.js} +9 -9
- package/dist/{hook-fast-WENTLDMA.js → hook-fast-VQXRDS6E.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/{init-RUIWHKLE.js → init-67R7LO4C.js} +10 -10
- package/dist/{init-LQDKX2XL.js → init-KSXU4PA7.js} +10 -10
- package/dist/{init-cmd-2OTTULTI.js → init-cmd-MNOVMADM.js} +14 -14
- package/dist/{init-update-TN2W4EN6.js → init-update-6KEXRCJH.js} +13 -13
- package/dist/{layout-NAPG4SX2.js → layout-7B3O3FT3.js} +3 -3
- package/dist/{loop-X3ANDLQU.js → loop-KUEOPBKX.js} +20 -20
- package/dist/{machine-IEYKRHRK.js → machine-EQEAPT76.js} +10 -10
- package/dist/{machine-skills-2TT3RXMJ.js → machine-skills-3FEGR376.js} +3 -3
- package/dist/{runner-key-QKZZTKLY.js → runner-key-ML7R33HF.js} +10 -10
- package/dist/{session-sender-IH44FURK.js → session-sender-A43GZQ7T.js} +9 -9
- package/dist/{spec-cmd-2LRTOTAO.js → spec-cmd-VVUJZSG2.js} +7 -7
- package/dist/{starter-pack-5D4BANFU.js → starter-pack-LEL3Q2DB.js} +4 -4
- package/dist/{status-6ISCZDBX.js → status-4G6K7I2C.js} +15 -15
- package/dist/{sync-cmd-MIID2ANV.js → sync-cmd-ZEC65ABJ.js} +8 -8
- package/dist/{update-steps-4M2GIB4A.js → update-steps-XLO7734I.js} +16 -16
- package/dist/{watch-K5UUKCPB.js → watch-QB4AMWR3.js} +8 -8
- package/dist/{work-claim-cmd-2LD2JQKI.js → work-claim-cmd-6W3GBHRS.js} +8 -8
- package/dist/{work-cmd-NR53YCXJ.js → work-cmd-LTZXOPPN.js} +6 -6
- package/dist/{work-comment-cmd-PC4HNM45.js → work-comment-cmd-LDZBUO6M.js} +7 -7
- package/dist/{work-demo-cmd-S5EJXBVH.js → work-demo-cmd-77LISWQ3.js} +6 -6
- package/dist/{work-logs-cmd-5F6MDBLQ.js → work-logs-cmd-FK7HE7PC.js} +6 -6
- package/dist/{work-open-pr-cmd-SWVFDSMY.js → work-open-pr-cmd-WVONEHZL.js} +7 -7
- package/dist/{work-pr-cmd-DXW4RXG4.js → work-pr-cmd-6IPXWJEH.js} +6 -6
- package/dist/{work-proofs-cmd-UVTSPFLL.js → work-proofs-cmd-TGIAP5IO.js} +7 -7
- package/dist/work-proofs-record-XPAU7N57.js +2382 -0
- package/dist/{work-stop-cmd-HL6FZL5X.js → work-stop-cmd-UJ45EV3A.js} +6 -6
- package/package.json +2 -1
- package/skill-pack/maintained/outerlayer-amend/SKILL.md +1 -2
- package/skill-pack/maintained/outerlayer-build/SKILL.md +4 -3
- package/skill-pack/maintained/outerlayer-build/references/agent-briefs.md +33 -28
- package/skill-pack/maintained/outerlayer-build/references/evidence.md +22 -20
- package/skill-pack/maintained/outerlayer-evidence/SKILL.md +1 -1
- package/dist/chunk-RT6VNM3S.js +0 -148
- package/dist/work-proofs-init-GNGI7PZU.js +0 -35
- package/dist/work-proofs-record-HTWSMEDG.js +0 -402
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { runWorkComment } from './chunk-GWF3ZRUX.js';
|
|
4
|
-
import { recipeKeyFor, ownPullRequest, otherPullRequestHeads, PROOFS_DIR, SETUP_FILE, proofsCommentId, planEntries, checkEmitPath, insideRoot, kindProblem, proofsCommentBody } from './chunk-RT6VNM3S.js';
|
|
5
|
-
import { resolveProofsItem, classifyProofsAt, runWorkProofs } from './chunk-3KT2IWV3.js';
|
|
6
|
-
import { runEmitDemo } from './chunk-BTHH5H5H.js';
|
|
7
|
-
import { readNewestDemo } from './chunk-ER6KQCVJ.js';
|
|
8
|
-
import { runEmitArtifact } from './chunk-MHGIN2X6.js';
|
|
9
|
-
import { runSync } from './chunk-HBVHRC3A.js';
|
|
10
|
-
import './chunk-5XZ6BUPH.js';
|
|
11
|
-
import './chunk-ESVW4MJY.js';
|
|
12
|
-
import './chunk-TBT347UY.js';
|
|
13
|
-
import './chunk-2PGI6RDE.js';
|
|
14
|
-
import './chunk-ES4ULBAL.js';
|
|
15
|
-
import { WorkCommandError, resolveCredentials, getJson } from './chunk-HIGP4WOC.js';
|
|
16
|
-
import './chunk-IP7BIBXJ.js';
|
|
17
|
-
import './chunk-OHJDH5GV.js';
|
|
18
|
-
import './chunk-NVDPSH72.js';
|
|
19
|
-
import './chunk-MECW77XP.js';
|
|
20
|
-
import { resolveRepoIdentity } from './chunk-6ZFY2L2T.js';
|
|
21
|
-
import './chunk-TIRHZC2M.js';
|
|
22
|
-
import './chunk-Y5YCX6AL.js';
|
|
23
|
-
import './chunk-G2JWZDFA.js';
|
|
24
|
-
import './chunk-ANPNT6PM.js';
|
|
25
|
-
import { defaultFetch } from './chunk-6RKD7YZ6.js';
|
|
26
|
-
import './chunk-PJDQPF5N.js';
|
|
27
|
-
import './chunk-SOI6K5DY.js';
|
|
28
|
-
import './chunk-PA6CK6WQ.js';
|
|
29
|
-
import './chunk-BALIE3BA.js';
|
|
30
|
-
import './chunk-SHIGGHZO.js';
|
|
31
|
-
import './chunk-ZJSNLVCE.js';
|
|
32
|
-
import './chunk-2O5KA67H.js';
|
|
33
|
-
import './chunk-BKO6JEZI.js';
|
|
34
|
-
import './chunk-HCVLP7XA.js';
|
|
35
|
-
import './chunk-BJ3KTMDH.js';
|
|
36
|
-
import './chunk-ZGB4ZJ5E.js';
|
|
37
|
-
import './chunk-5BHIBJDA.js';
|
|
38
|
-
import './chunk-CDDED64E.js';
|
|
39
|
-
import './chunk-RA3O54FC.js';
|
|
40
|
-
import './chunk-T2Q7ISPE.js';
|
|
41
|
-
import './chunk-WZBHBH3J.js';
|
|
42
|
-
import './chunk-CRB3POP4.js';
|
|
43
|
-
import './chunk-URIZRCRY.js';
|
|
44
|
-
import './chunk-TUSYOL2O.js';
|
|
45
|
-
import { parseProofRecipe, parseProofSetup } from './chunk-CQRPR4WZ.js';
|
|
46
|
-
import { ARTIFACT_MAX_CAPTION_LENGTH } from './chunk-EOTMDIYC.js';
|
|
47
|
-
import './chunk-VNVZDWO3.js';
|
|
48
|
-
import './chunk-HACGUL52.js';
|
|
49
|
-
import './chunk-MESOOLF4.js';
|
|
50
|
-
import './chunk-Z5GEFMRW.js';
|
|
51
|
-
import { spawnSync, spawn } from 'child_process';
|
|
52
|
-
import { mkdtempSync, rmSync, statSync, openSync, closeSync, readdirSync, readFileSync } from 'fs';
|
|
53
|
-
import { tmpdir } from 'os';
|
|
54
|
-
import { join } from 'path';
|
|
55
|
-
|
|
56
|
-
createRequire(import.meta.url);
|
|
57
|
-
var runShell = (command, params) => {
|
|
58
|
-
const logPath = join(params.logDir, `${params.groups.length}-${Date.now()}.log`);
|
|
59
|
-
const fd = openSync(logPath, "w");
|
|
60
|
-
return new Promise((resolve) => {
|
|
61
|
-
const child = spawn("sh", ["-c", command], { cwd: params.cwd, env: params.env, detached: true, stdio: ["ignore", fd, fd] });
|
|
62
|
-
closeSync(fd);
|
|
63
|
-
if (child.pid !== void 0) params.groups.push(child.pid);
|
|
64
|
-
let timedOut = false;
|
|
65
|
-
const timer = setTimeout(() => {
|
|
66
|
-
timedOut = true;
|
|
67
|
-
try {
|
|
68
|
-
process.kill(-child.pid, "SIGKILL");
|
|
69
|
-
} catch {
|
|
70
|
-
}
|
|
71
|
-
}, params.timeoutMs);
|
|
72
|
-
const finish = (status) => {
|
|
73
|
-
clearTimeout(timer);
|
|
74
|
-
let text = "";
|
|
75
|
-
try {
|
|
76
|
-
text = readFileSync(logPath, "utf8");
|
|
77
|
-
} catch {
|
|
78
|
-
}
|
|
79
|
-
resolve({ status, timedOut, tail: text.trimEnd().split("\n").slice(-8).join("\n") });
|
|
80
|
-
};
|
|
81
|
-
child.on("error", () => finish(null));
|
|
82
|
-
child.on("exit", (status) => finish(status));
|
|
83
|
-
});
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// src/work-proofs-record.ts
|
|
87
|
-
var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
88
|
-
var MTIME_SLOP_MS = 2e3;
|
|
89
|
-
function showAtHead(root, path) {
|
|
90
|
-
const shown = spawnSync("git", ["show", `HEAD:${path}`], { cwd: root, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
|
|
91
|
-
return shown.status === 0 ? shown.stdout : void 0;
|
|
92
|
-
}
|
|
93
|
-
function findManifests(folder) {
|
|
94
|
-
const found = [];
|
|
95
|
-
for (const entry of readdirSync(folder, { withFileTypes: true })) {
|
|
96
|
-
const path = join(folder, entry.name);
|
|
97
|
-
if (entry.isDirectory()) found.push(...findManifests(path));
|
|
98
|
-
else if (entry.name === "manifest.json" && folder.split("/").pop() === "demo") found.push(path);
|
|
99
|
-
}
|
|
100
|
-
return found;
|
|
101
|
-
}
|
|
102
|
-
function unstoredUploads(result, spooled) {
|
|
103
|
-
if (typeof result !== "object" || result === null) return [];
|
|
104
|
-
const { artifactsDropped, artifactsHeld, rejected, uploadedArtifacts } = result;
|
|
105
|
-
if (Array.isArray(uploadedArtifacts)) {
|
|
106
|
-
const stored = new Set(uploadedArtifacts.map((a) => a?.artifactId));
|
|
107
|
-
return spooled.filter((id) => !stored.has(id));
|
|
108
|
-
}
|
|
109
|
-
const lost = typeof artifactsDropped === "number" && artifactsDropped > 0 || typeof artifactsHeld === "number" && artifactsHeld > 0 || Array.isArray(rejected) && rejected.length > 0;
|
|
110
|
-
return lost ? "all" : [];
|
|
111
|
-
}
|
|
112
|
-
function shortSha(sha) {
|
|
113
|
-
return sha.slice(0, 7);
|
|
114
|
-
}
|
|
115
|
-
function oneLine(text) {
|
|
116
|
-
return text.replace(/\s+/g, " ").trim();
|
|
117
|
-
}
|
|
118
|
-
async function runWorkProofsRecord(opts) {
|
|
119
|
-
const env = opts.env ?? process.env;
|
|
120
|
-
const item = resolveProofsItem({ item: opts.item, env });
|
|
121
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
122
|
-
const identity = resolveRepoIdentity(cwd);
|
|
123
|
-
if (identity.repoRoot === void 0 || identity.commitSha === void 0) {
|
|
124
|
-
throw new WorkCommandError("this is not a git checkout with a commit. Run this inside the repository, after the recipe is committed.");
|
|
125
|
-
}
|
|
126
|
-
const { repoRoot: root, commitSha: head } = identity;
|
|
127
|
-
const fetchImpl = opts.fetchImpl ?? defaultFetch;
|
|
128
|
-
const creds = resolveCredentials({ ...opts, env });
|
|
129
|
-
const home = opts.home;
|
|
130
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
131
|
-
const lines = [];
|
|
132
|
-
const say = (line) => void lines.push(line);
|
|
133
|
-
const { data } = await getJson(creds.url, `/v1/work-items/${item}`, creds.apiKey, creds.appId, fetchImpl);
|
|
134
|
-
const body = data;
|
|
135
|
-
const key = recipeKeyFor(body.trackerKey ?? "");
|
|
136
|
-
if (key === void 0) {
|
|
137
|
-
throw new WorkCommandError(`the item's tracker key "${body.trackerKey ?? ""}" cannot name a recipe file.`);
|
|
138
|
-
}
|
|
139
|
-
const own = ownPullRequest(body.pullRequests ?? [], opts.pr, identity.gitRepo);
|
|
140
|
-
if ("ambiguous" in own) {
|
|
141
|
-
throw new WorkCommandError(`the item links several open pull requests (${own.ambiguous.map((n) => `#${n}`).join(", ")}). Pass --pr <number> to name this checkout's.`);
|
|
142
|
-
}
|
|
143
|
-
const ownPr = own.pr;
|
|
144
|
-
const heads = /* @__PURE__ */ new Set([head.toLowerCase(), ...otherPullRequestHeads(body.pullRequests ?? [], ownPr, identity.gitRepo)]);
|
|
145
|
-
const states = classifyProofsAt(body, heads);
|
|
146
|
-
const postGeneral = async (text, clientCommentId) => {
|
|
147
|
-
try {
|
|
148
|
-
await runWorkComment({ ...creds, item, body: text, clientCommentId, cwd, home, env, fetchImpl, quiet: true });
|
|
149
|
-
} catch (err) {
|
|
150
|
-
if (!(err instanceof WorkCommandError && err.code === "comment_conflict")) {
|
|
151
|
-
say(`The comment on the item's thread was not posted: ${oneLine(String(err instanceof Error ? err.message : err))}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
const recipePath = `${PROOFS_DIR}/${key}.yaml`;
|
|
156
|
-
const setupPath = `${PROOFS_DIR}/${SETUP_FILE}`;
|
|
157
|
-
const recipeText = showAtHead(root, recipePath);
|
|
158
|
-
const setupText = showAtHead(root, setupPath);
|
|
159
|
-
const recipeParsed = recipeText === void 0 ? { ok: true, value: { run: [], manual: [] } } : parseProofRecipe(recipeText);
|
|
160
|
-
const setupParsed = setupText === void 0 ? { ok: true, value: { setup: [] } } : parseProofSetup(setupText);
|
|
161
|
-
for (const [file, parsed] of [
|
|
162
|
-
[recipePath, recipeParsed],
|
|
163
|
-
[setupPath, setupParsed]
|
|
164
|
-
]) {
|
|
165
|
-
if (parsed.ok) continue;
|
|
166
|
-
const problem = `${file} cannot be read: ${parsed.problems.join("; ")}`;
|
|
167
|
-
if (opts.comment) {
|
|
168
|
-
const text = `The proof recipe cannot be read at ${shortSha(head)}, so no proof was recorded. ${problem}`.slice(0, 2e3);
|
|
169
|
-
await postGeneral(text, proofsCommentId(item, head, [{ id: file, proof: "recipe", reason: problem }]));
|
|
170
|
-
}
|
|
171
|
-
throw new WorkCommandError(problem);
|
|
172
|
-
}
|
|
173
|
-
const recipe = recipeParsed.value;
|
|
174
|
-
const setup = setupParsed.value;
|
|
175
|
-
if (recipeText === void 0) say(`No recipe at ${recipePath} in the HEAD commit, so no entry runs.`);
|
|
176
|
-
const dirty = spawnSync("git", ["diff", "--quiet", "HEAD", "--", PROOFS_DIR], { cwd: root });
|
|
177
|
-
if (dirty.status === 1) say(`The working copy of ${PROOFS_DIR} differs from HEAD. Only the committed files run. Commit the change, then record again.`);
|
|
178
|
-
const declared = new Map((body.criteria?.items ?? []).map((criterion) => [criterion.id, criterion]));
|
|
179
|
-
const reasons = /* @__PURE__ */ new Map();
|
|
180
|
-
const emitted = /* @__PURE__ */ new Map();
|
|
181
|
-
const manual = new Map(recipe.manual.map((entry) => [entry.criterion, entry]));
|
|
182
|
-
const plan = planEntries(recipe, states);
|
|
183
|
-
const toRun = [];
|
|
184
|
-
for (const planned of plan) {
|
|
185
|
-
if (planned.skip !== null) continue;
|
|
186
|
-
const label = planned.entry.emit === "demo" ? "the demo entry" : `the entry for ${planned.entry.for}`;
|
|
187
|
-
const refuse = (reason) => {
|
|
188
|
-
say(`Skipped ${label}: ${reason}`);
|
|
189
|
-
for (const id of planned.criteria) reasons.set(id, reason);
|
|
190
|
-
};
|
|
191
|
-
if (planned.entry.command.trim() === "") {
|
|
192
|
-
refuse("it has no command. Fill in the recipe.");
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
if (planned.entry.emit !== "demo") {
|
|
196
|
-
const checked = checkEmitPath(root, planned.entry.emit);
|
|
197
|
-
if (!checked.ok) {
|
|
198
|
-
refuse(checked.reason);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
toRun.push(planned);
|
|
203
|
-
}
|
|
204
|
-
const logDir = mkdtempSync(join(tmpdir(), "outerlayer-proofs-"));
|
|
205
|
-
const groups = [];
|
|
206
|
-
const runCommand = opts.runCommand ?? runShell;
|
|
207
|
-
const baseEnv = { ...process.env, ...env };
|
|
208
|
-
let spooledCount = 0;
|
|
209
|
-
const spooledCriteria = /* @__PURE__ */ new Set();
|
|
210
|
-
const emitArtifact = async (emitOpts) => {
|
|
211
|
-
const result = await (opts.emitArtifact ?? runEmitArtifact)(emitOpts);
|
|
212
|
-
if (result.spooled) spooledCount += 1;
|
|
213
|
-
return result;
|
|
214
|
-
};
|
|
215
|
-
const common = { url: creds.url, apiKey: creds.apiKey, appId: creds.appId, cwd: root, home, env, fetchImpl, quiet: true };
|
|
216
|
-
let setupStarted = false;
|
|
217
|
-
let setupFailed;
|
|
218
|
-
const stopGroups = () => {
|
|
219
|
-
for (const pid of groups) {
|
|
220
|
-
try {
|
|
221
|
-
process.kill(-pid, "SIGTERM");
|
|
222
|
-
} catch {
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
const onSignal = (signal) => {
|
|
227
|
-
stopGroups();
|
|
228
|
-
rmSync(logDir, { recursive: true, force: true });
|
|
229
|
-
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
230
|
-
};
|
|
231
|
-
const onInterrupt = () => onSignal("SIGINT");
|
|
232
|
-
const onTerminate = () => onSignal("SIGTERM");
|
|
233
|
-
process.once("SIGINT", onInterrupt);
|
|
234
|
-
process.once("SIGTERM", onTerminate);
|
|
235
|
-
try {
|
|
236
|
-
if (toRun.length > 0) {
|
|
237
|
-
setupStarted = true;
|
|
238
|
-
for (const command of setup.setup) {
|
|
239
|
-
const result = await runCommand(command, { cwd: root, env: baseEnv, timeoutMs, logDir, groups });
|
|
240
|
-
if (result.status !== 0) {
|
|
241
|
-
setupFailed = `setup failed: "${oneLine(command)}" ${result.timedOut ? `timed out after ${Math.round(timeoutMs / 1e3)} s` : `exited with status ${result.status}`}`;
|
|
242
|
-
say(setupFailed);
|
|
243
|
-
if (result.tail !== "") say(result.tail);
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
for (const planned of toRun) {
|
|
249
|
-
if (setupFailed !== void 0) {
|
|
250
|
-
for (const id of planned.criteria) reasons.set(id, setupFailed);
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
const { entry } = planned;
|
|
254
|
-
const proofOut = mkdtempSync(join(tmpdir(), "outerlayer-proof-out-"));
|
|
255
|
-
const startedAt = Date.now();
|
|
256
|
-
try {
|
|
257
|
-
const result = await runCommand(entry.command, { cwd: root, env: { ...baseEnv, OUTERLAYER_PROOF_OUT: proofOut }, timeoutMs, logDir, groups });
|
|
258
|
-
const failure = result.timedOut ? `the command timed out after ${Math.round(timeoutMs / 1e3)} s` : result.status === 0 ? void 0 : `the command exited ${result.status}`;
|
|
259
|
-
if (entry.emit === "demo") {
|
|
260
|
-
await recordDemo({ planned, proofOut, failure, tail: result.tail });
|
|
261
|
-
} else {
|
|
262
|
-
await recordFile({ planned, failure, tail: result.tail, startedAt });
|
|
263
|
-
}
|
|
264
|
-
} finally {
|
|
265
|
-
rmSync(proofOut, { recursive: true, force: true });
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
} finally {
|
|
269
|
-
if (setupStarted) {
|
|
270
|
-
for (const command of setup.teardown ?? []) {
|
|
271
|
-
const result = await runCommand(command, { cwd: root, env: baseEnv, timeoutMs, logDir, groups });
|
|
272
|
-
if (result.status !== 0) say(`teardown failed: "${oneLine(command)}" ${result.timedOut ? "timed out" : `exited with status ${result.status}`}`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
process.off("SIGINT", onInterrupt);
|
|
276
|
-
process.off("SIGTERM", onTerminate);
|
|
277
|
-
stopGroups();
|
|
278
|
-
rmSync(logDir, { recursive: true, force: true });
|
|
279
|
-
}
|
|
280
|
-
async function recordFile(params) {
|
|
281
|
-
const { entry } = params.planned;
|
|
282
|
-
const id = entry.for;
|
|
283
|
-
const fail = (reason) => {
|
|
284
|
-
reasons.set(id, reason);
|
|
285
|
-
say(`Not recorded, ${id}: ${reason}`);
|
|
286
|
-
if (params.tail !== "" && params.failure !== void 0) say(params.tail);
|
|
287
|
-
};
|
|
288
|
-
if (params.failure !== void 0) return fail(params.failure);
|
|
289
|
-
const checked = checkEmitPath(root, entry.emit);
|
|
290
|
-
if (!checked.ok) return fail(checked.reason);
|
|
291
|
-
let modified;
|
|
292
|
-
try {
|
|
293
|
-
const stat = statSync(checked.path);
|
|
294
|
-
if (!stat.isFile()) return fail(`${entry.emit} is not a file`);
|
|
295
|
-
modified = stat.mtimeMs;
|
|
296
|
-
} catch {
|
|
297
|
-
return fail(`the command did not write ${entry.emit}`);
|
|
298
|
-
}
|
|
299
|
-
if (!insideRoot(root, checked.path)) return fail(`${entry.emit} is outside the repository: it links out of it`);
|
|
300
|
-
if (modified < params.startedAt - MTIME_SLOP_MS) return fail(`the command did not write ${entry.emit}: the file is older than this run`);
|
|
301
|
-
const problem = kindProblem(declared.get(id)?.proof ?? null, checked.path);
|
|
302
|
-
if (problem !== void 0) return fail(problem);
|
|
303
|
-
try {
|
|
304
|
-
const caption = (declared.get(id)?.text ?? `Proof for ${id}`).slice(0, ARTIFACT_MAX_CAPTION_LENGTH);
|
|
305
|
-
const before = spooledCount;
|
|
306
|
-
const result = await emitArtifact({ ...common, file: checked.path, caption, criterionId: id, ...ownPr === void 0 ? {} : { pr: ownPr } });
|
|
307
|
-
const artifactId = result.record?.artifactId ?? result.data?.clientArtifactId;
|
|
308
|
-
if (artifactId === void 0) return fail("the upload returned no artifact id");
|
|
309
|
-
emitted.set(id, artifactId);
|
|
310
|
-
if (spooledCount > before) spooledCriteria.add(id);
|
|
311
|
-
say(`Recorded ${id} from ${entry.emit}.`);
|
|
312
|
-
} catch (err) {
|
|
313
|
-
fail(`the upload failed: ${oneLine(String(err instanceof Error ? err.message : err))}`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
async function recordDemo(params) {
|
|
317
|
-
const fail = (reason) => {
|
|
318
|
-
for (const id of params.planned.criteria) reasons.set(id, reason);
|
|
319
|
-
say(`Not recorded, the demo: ${reason}`);
|
|
320
|
-
if (params.tail !== "" && params.failure !== void 0) say(params.tail);
|
|
321
|
-
};
|
|
322
|
-
const manifests = findManifests(params.proofOut);
|
|
323
|
-
if (manifests.length !== 1) {
|
|
324
|
-
const status = params.failure === void 0 ? "" : ` (${params.failure})`;
|
|
325
|
-
return fail(manifests.length === 0 ? `the command left no demo manifest${status}` : `the command left ${manifests.length} demo manifests, so it is unclear which to store`);
|
|
326
|
-
}
|
|
327
|
-
let replaces;
|
|
328
|
-
try {
|
|
329
|
-
replaces = (await readNewestDemo({ url: creds.url, apiKey: creds.apiKey, appId: creds.appId, item, fetchImpl }))?.artifactId;
|
|
330
|
-
} catch (err) {
|
|
331
|
-
replaces = void 0;
|
|
332
|
-
say(`The previous demo could not be read, so the new demo does not replace it: ${oneLine(String(err instanceof Error ? err.message : err))}`);
|
|
333
|
-
}
|
|
334
|
-
const before = spooledCount;
|
|
335
|
-
let result;
|
|
336
|
-
try {
|
|
337
|
-
result = await (opts.emitDemo ?? runEmitDemo)({
|
|
338
|
-
...common,
|
|
339
|
-
manifest: manifests[0],
|
|
340
|
-
item,
|
|
341
|
-
...ownPr === void 0 ? {} : { pr: ownPr },
|
|
342
|
-
...replaces === void 0 ? {} : { replaces },
|
|
343
|
-
emitArtifact
|
|
344
|
-
});
|
|
345
|
-
} catch (err) {
|
|
346
|
-
return fail(`the demo was not stored: ${oneLine(String(err instanceof Error ? err.message : err))}`);
|
|
347
|
-
}
|
|
348
|
-
for (const id of params.planned.criteria) {
|
|
349
|
-
const proof = declared.get(id)?.proof;
|
|
350
|
-
const artifactId = proof === "screenshot" ? result.screenshots.find((shot) => shot.criterionId === id && shot.artifactId !== void 0)?.artifactId : result.clips.find((clip) => clip.criterionId === id && clip.bound)?.clipArtifactId;
|
|
351
|
-
if (artifactId !== void 0) {
|
|
352
|
-
emitted.set(id, artifactId);
|
|
353
|
-
if (spooledCount > before) spooledCriteria.add(id);
|
|
354
|
-
} else reasons.set(id, result.failures.length > 0 ? `the demo failed a check: ${oneLine(result.failures[0])}` : "the demo has no passing chapter for it");
|
|
355
|
-
}
|
|
356
|
-
say(`Recorded the demo: ${params.planned.criteria.filter((id) => emitted.has(id)).length} of ${params.planned.criteria.length} proofs.`);
|
|
357
|
-
for (const failure of result.failures) say(`Demo check failed: ${oneLine(failure)}`);
|
|
358
|
-
}
|
|
359
|
-
if (spooledCount > 0) {
|
|
360
|
-
try {
|
|
361
|
-
const synced = await (opts.sync ?? (() => runSync({ ...common })))();
|
|
362
|
-
const unstored = unstoredUploads(
|
|
363
|
-
synced,
|
|
364
|
-
[...spooledCriteria].map((id) => emitted.get(id))
|
|
365
|
-
);
|
|
366
|
-
const lostIds = unstored === "all" ? [...spooledCriteria] : [...spooledCriteria].filter((id) => unstored.includes(emitted.get(id)));
|
|
367
|
-
for (const id of lostIds) {
|
|
368
|
-
emitted.delete(id);
|
|
369
|
-
reasons.set(id, "the sync did not store the upload, so the proof is stored only in the session record");
|
|
370
|
-
}
|
|
371
|
-
if (lostIds.length > 0) say("The sync did not store an upload. The proofs that went through the session record are not stored.");
|
|
372
|
-
} catch (err) {
|
|
373
|
-
for (const id of spooledCriteria) {
|
|
374
|
-
emitted.delete(id);
|
|
375
|
-
reasons.set(id, "the sync failed, so the proof is stored only in the session record");
|
|
376
|
-
}
|
|
377
|
-
say(`The sync failed, so the new proofs are stored only in the session record: ${oneLine(String(err instanceof Error ? err.message : err))}`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
let finalStates = states;
|
|
381
|
-
try {
|
|
382
|
-
const { data: fresh } = await getJson(creds.url, `/v1/work-items/${item}`, creds.apiKey, creds.appId, fetchImpl);
|
|
383
|
-
finalStates = classifyProofsAt(fresh, heads);
|
|
384
|
-
} catch {
|
|
385
|
-
}
|
|
386
|
-
const notCurrent = finalStates.filter((state) => state.state !== "current" && !emitted.has(state.id)).map((state) => ({ id: state.id, proof: state.proof, reason: reasons.get(state.id) ?? manual.get(state.id)?.reason ?? "no entry in the recipe" }));
|
|
387
|
-
if (opts.comment && notCurrent.length > 0) {
|
|
388
|
-
await postGeneral(proofsCommentBody(head, notCurrent), proofsCommentId(item, head, notCurrent));
|
|
389
|
-
}
|
|
390
|
-
for (const proof of notCurrent) say(`Not current, ${proof.id} (${proof.proof}): ${proof.reason}`);
|
|
391
|
-
const closing = await runWorkProofs({ ...opts, item, env, quiet: true, checkoutHead: head, fetchImpl });
|
|
392
|
-
const output = [...lines, closing.output].join("\n");
|
|
393
|
-
if (!opts.quiet) process.stdout.write(output + "\n");
|
|
394
|
-
return {
|
|
395
|
-
exitCode: closing.exitCode,
|
|
396
|
-
output,
|
|
397
|
-
emitted: [...emitted].map(([criterion, artifactId]) => ({ criterion, artifactId })),
|
|
398
|
-
notCurrent
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export { runWorkProofsRecord };
|