reeval 0.0.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/LICENSE +202 -0
- package/THIRD_PARTY_NOTICES.md +2160 -0
- package/assets/package.json +9 -0
- package/assets/pnpm-lock.yaml +1336 -0
- package/dist/agent-DVHkx8aT.js +532 -0
- package/dist/agent-DVHkx8aT.js.map +1 -0
- package/dist/analysis-CxSCt1YY.js +16 -0
- package/dist/analysis-CxSCt1YY.js.map +1 -0
- package/dist/assemble-B-nmz8oH.js +695 -0
- package/dist/assemble-B-nmz8oH.js.map +1 -0
- package/dist/assemble-CmRVDZ2Q.js +2 -0
- package/dist/attempt-BdRQ9jKX.js +176 -0
- package/dist/attempt-BdRQ9jKX.js.map +1 -0
- package/dist/bridge/index.mjs +1911 -0
- package/dist/bridge/package.json +13 -0
- package/dist/bridge/pnpm-lock.yaml +1041 -0
- package/dist/bridge/pnpm-workspace.yaml +2 -0
- package/dist/budget-DBUrH4jX.js +8 -0
- package/dist/budget-DBUrH4jX.js.map +1 -0
- package/dist/cli--CQ3Iy9q.js +1865 -0
- package/dist/cli--CQ3Iy9q.js.map +1 -0
- package/dist/cli-spec-DKAu09Gi.js +478 -0
- package/dist/cli-spec-DKAu09Gi.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors-QV9dFVhN.js +34 -0
- package/dist/colors-QV9dFVhN.js.map +1 -0
- package/dist/command-BSJzHMtP.js +280 -0
- package/dist/command-BSJzHMtP.js.map +1 -0
- package/dist/command-CEm8PZ1N.js +1043 -0
- package/dist/command-CEm8PZ1N.js.map +1 -0
- package/dist/command-D-jnQWVG.js +673 -0
- package/dist/command-D-jnQWVG.js.map +1 -0
- package/dist/command-DQAvbG7d.js +432 -0
- package/dist/command-DQAvbG7d.js.map +1 -0
- package/dist/commands-A4udfyKZ.js +2 -0
- package/dist/commands-CF8mOfiu.js +207 -0
- package/dist/commands-CF8mOfiu.js.map +1 -0
- package/dist/config-Cuj0kVYj.js +415 -0
- package/dist/config-Cuj0kVYj.js.map +1 -0
- package/dist/context-loi9OCZv.js +106 -0
- package/dist/context-loi9OCZv.js.map +1 -0
- package/dist/core-tLN-z1Be.js +96 -0
- package/dist/core-tLN-z1Be.js.map +1 -0
- package/dist/db-Cjj2ZsfQ.js +2 -0
- package/dist/db-DxDzMhtB.js +827 -0
- package/dist/db-DxDzMhtB.js.map +1 -0
- package/dist/debug-D24NOQH1.js +89 -0
- package/dist/debug-D24NOQH1.js.map +1 -0
- package/dist/debug-SEmW4w8f.js +2 -0
- package/dist/dist-BZXYHN2X.js +1673 -0
- package/dist/dist-BZXYHN2X.js.map +1 -0
- package/dist/docker-DQDZWXF9.js +20 -0
- package/dist/docker-DQDZWXF9.js.map +1 -0
- package/dist/docker-DnWAVdEm.js +2 -0
- package/dist/docker-oSbCExHL.js +153 -0
- package/dist/docker-oSbCExHL.js.map +1 -0
- package/dist/doctor-CxlH1pDM.js +525 -0
- package/dist/doctor-CxlH1pDM.js.map +1 -0
- package/dist/egress-73SQfKdx.js +619 -0
- package/dist/egress-73SQfKdx.js.map +1 -0
- package/dist/env-BQqj93gu.js +21 -0
- package/dist/env-BQqj93gu.js.map +1 -0
- package/dist/environment-Mz7wvrtD.js +552 -0
- package/dist/environment-Mz7wvrtD.js.map +1 -0
- package/dist/errors-C9w_ud7g.js +160 -0
- package/dist/errors-C9w_ud7g.js.map +1 -0
- package/dist/errors-CSxfJ4qu.js +11 -0
- package/dist/errors-CSxfJ4qu.js.map +1 -0
- package/dist/files--6CEUXm7.js +2 -0
- package/dist/files-BLlrhU4-.js +62 -0
- package/dist/files-BLlrhU4-.js.map +1 -0
- package/dist/files-Bd25OAQH.js +2 -0
- package/dist/files-BvHCFWgD.js +2 -0
- package/dist/files-D2-qn_Dl.js +275 -0
- package/dist/files-D2-qn_Dl.js.map +1 -0
- package/dist/files-bF2fPuT9.js +99 -0
- package/dist/files-bF2fPuT9.js.map +1 -0
- package/dist/git-D6hMeieS.js +44 -0
- package/dist/git-D6hMeieS.js.map +1 -0
- package/dist/harness-ls5aX1UO.js +131 -0
- package/dist/harness-ls5aX1UO.js.map +1 -0
- package/dist/harness.d.ts +74 -0
- package/dist/harness.js +2 -0
- package/dist/header-DzFDoMCI.js +78 -0
- package/dist/header-DzFDoMCI.js.map +1 -0
- package/dist/ids-Bp6ut7ZD.js +49 -0
- package/dist/ids-Bp6ut7ZD.js.map +1 -0
- package/dist/images-Cc1-coFc.js +286 -0
- package/dist/images-Cc1-coFc.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +1959 -0
- package/dist/index.js.map +1 -0
- package/dist/init-CFljS8BJ.js +344 -0
- package/dist/init-CFljS8BJ.js.map +1 -0
- package/dist/init-z2ZPn9Tj.js +2 -0
- package/dist/invocations-rXktgSAn.js +157 -0
- package/dist/invocations-rXktgSAn.js.map +1 -0
- package/dist/jiti-BOLXpKyG.js +46 -0
- package/dist/jiti-BOLXpKyG.js.map +1 -0
- package/dist/judge-D8Uf4dA9.js +146 -0
- package/dist/judge-D8Uf4dA9.js.map +1 -0
- package/dist/judge.d.ts +21 -0
- package/dist/judge.js +19 -0
- package/dist/judge.js.map +1 -0
- package/dist/live-Bs3LReMV.js +98 -0
- package/dist/live-Bs3LReMV.js.map +1 -0
- package/dist/matchers-DyLvm7EG.js +374 -0
- package/dist/matchers-DyLvm7EG.js.map +1 -0
- package/dist/model-BdO79gWz.js +44 -0
- package/dist/model-BdO79gWz.js.map +1 -0
- package/dist/model-CDL98xue.js +725 -0
- package/dist/model-CDL98xue.js.map +1 -0
- package/dist/model-v-0_Ik7S.js +2 -0
- package/dist/node-kG64XxdE.js +506 -0
- package/dist/node-kG64XxdE.js.map +1 -0
- package/dist/opencode-providers-DZuD_oYt.js +3149 -0
- package/dist/opencode-providers-DZuD_oYt.js.map +1 -0
- package/dist/options-CAQhu--C.js +452 -0
- package/dist/options-CAQhu--C.js.map +1 -0
- package/dist/patch-BkKje4cp.js +69 -0
- package/dist/patch-BkKje4cp.js.map +1 -0
- package/dist/paths-CBvm6pap.js +72 -0
- package/dist/paths-CBvm6pap.js.map +1 -0
- package/dist/plan-gwk6dJ8c.js +237 -0
- package/dist/plan-gwk6dJ8c.js.map +1 -0
- package/dist/preflight-BtubXKQ8.js +2 -0
- package/dist/preflight-BypZMNBG.js +185 -0
- package/dist/preflight-BypZMNBG.js.map +1 -0
- package/dist/prepare-Cwv0bHs_.js +3 -0
- package/dist/prepare-D6Uzcubb.js +213 -0
- package/dist/prepare-D6Uzcubb.js.map +1 -0
- package/dist/provenance-Dfb8YY5Y.js +129 -0
- package/dist/provenance-Dfb8YY5Y.js.map +1 -0
- package/dist/provider-BDbNOQhQ.js +2 -0
- package/dist/provider-CmAYTQQs.js +14 -0
- package/dist/provider-CmAYTQQs.js.map +1 -0
- package/dist/providers-2p3ylMHI.js +107 -0
- package/dist/providers-2p3ylMHI.js.map +1 -0
- package/dist/providers-BUOxitcT.js +2 -0
- package/dist/query-CFATHZmV.js +91 -0
- package/dist/query-CFATHZmV.js.map +1 -0
- package/dist/query-DR4m6CXf.js +2 -0
- package/dist/record-DQ8wzyco.js +99 -0
- package/dist/record-DQ8wzyco.js.map +1 -0
- package/dist/redact-C4fL_bZP.js +167 -0
- package/dist/redact-C4fL_bZP.js.map +1 -0
- package/dist/results-CeXb8Wde.js +37 -0
- package/dist/results-CeXb8Wde.js.map +1 -0
- package/dist/root-CFDjpWuc.js +110 -0
- package/dist/root-CFDjpWuc.js.map +1 -0
- package/dist/sandbox-By2gkdkC.js +133 -0
- package/dist/sandbox-By2gkdkC.js.map +1 -0
- package/dist/sandbox.d.ts +41 -0
- package/dist/sandbox.js +2 -0
- package/dist/server-Bi61uiY0.js +350 -0
- package/dist/server-Bi61uiY0.js.map +1 -0
- package/dist/server-CsSBL8NC.js +2 -0
- package/dist/source-BXzrmof5.js +2 -0
- package/dist/source-Bsfa3LZH.js +40 -0
- package/dist/source-Bsfa3LZH.js.map +1 -0
- package/dist/src-55j1zNHH.js +3 -0
- package/dist/src-AEwxd1UP.js +602 -0
- package/dist/src-AEwxd1UP.js.map +1 -0
- package/dist/src-BDwSuyIo.js +3 -0
- package/dist/src-BQyMiWri.js +490 -0
- package/dist/src-BQyMiWri.js.map +1 -0
- package/dist/src-C-LHyTSV.js +3 -0
- package/dist/src-Cmp-7YrD.js +1136 -0
- package/dist/src-Cmp-7YrD.js.map +1 -0
- package/dist/src-D0RdqpNb.js +252 -0
- package/dist/src-D0RdqpNb.js.map +1 -0
- package/dist/src-D8KYzxBX.js +793 -0
- package/dist/src-D8KYzxBX.js.map +1 -0
- package/dist/src-DEgff9Z7.js +4 -0
- package/dist/src-DXx_29pq.js +567 -0
- package/dist/src-DXx_29pq.js.map +1 -0
- package/dist/src-DwwiVLqv.js +2937 -0
- package/dist/src-DwwiVLqv.js.map +1 -0
- package/dist/src-N7E_mfcZ.js +2 -0
- package/dist/src-W9g-y5X5.js +3 -0
- package/dist/src-daS0wKH2.js +3 -0
- package/dist/state-Cyw3QTsN.js +2 -0
- package/dist/state-DZ7eqb1F.js +167 -0
- package/dist/state-DZ7eqb1F.js.map +1 -0
- package/dist/stats-BNAHlNMD.js +114 -0
- package/dist/stats-BNAHlNMD.js.map +1 -0
- package/dist/studio-app/assets/index-D6XyfFSH.js +58 -0
- package/dist/studio-app/assets/index-xwzWdcE_.css +2 -0
- package/dist/studio-app/favicon.svg +1 -0
- package/dist/studio-app/index.html +15 -0
- package/dist/summary-D7Ttky0z.js +166 -0
- package/dist/summary-D7Ttky0z.js.map +1 -0
- package/dist/text-gEcKjAyp.js +29 -0
- package/dist/text-gEcKjAyp.js.map +1 -0
- package/dist/types-C5vI3g1C.js +9 -0
- package/dist/types-C5vI3g1C.js.map +1 -0
- package/dist/types-DLec-6Cx.d.ts +1535 -0
- package/dist/usage-DsYUqIUM.js +24 -0
- package/dist/usage-DsYUqIUM.js.map +1 -0
- package/dist/view/index.d.ts +645 -0
- package/dist/view/index.js +8 -0
- package/dist/views-BrHOv6wg.js +220 -0
- package/dist/views-BrHOv6wg.js.map +1 -0
- package/dist/views-CwCWLWqo.js +3 -0
- package/dist/worker-entry-DnYkANPG.js +506 -0
- package/dist/worker-entry-DnYkANPG.js.map +1 -0
- package/dist/workspace-C2S0Z6gl.js +327 -0
- package/dist/workspace-C2S0Z6gl.js.map +1 -0
- package/dist/workspace-CyoXG6CY.js +2 -0
- package/docker/Dockerfile +19 -0
- package/package.json +79 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
import { t as UsageError } from "./errors-CSxfJ4qu.js";
|
|
2
|
+
import { n as loadEnvFiles, t as inCI } from "./env-BQqj93gu.js";
|
|
3
|
+
import { n as SECRET_NAME } from "./redact-C4fL_bZP.js";
|
|
4
|
+
import { i as PACKAGE_DIR, o as PACKAGE_VERSION } from "./root-CFDjpWuc.js";
|
|
5
|
+
import { a as fromCwd } from "./ids-Bp6ut7ZD.js";
|
|
6
|
+
import { et as run } from "./src-DwwiVLqv.js";
|
|
7
|
+
import { i as configPathFor, l as loadConfig } from "./config-Cuj0kVYj.js";
|
|
8
|
+
import { i as parseAttemptKey, t as attemptDebugDir } from "./files-BLlrhU4-.js";
|
|
9
|
+
import { i as masks, n as readInvocations, o as registerEnvFileValues, s as scrubber } from "./invocations-rXktgSAn.js";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir, release } from "node:os";
|
|
12
|
+
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
13
|
+
import { execFile } from "node:child_process";
|
|
14
|
+
import { detectAgent } from "std-env";
|
|
15
|
+
import { gzipSync } from "node:zlib";
|
|
16
|
+
const LOG_LINES = 60;
|
|
17
|
+
const TRACE_LINES = 20;
|
|
18
|
+
const TRACE_LINE_CHARS = 500;
|
|
19
|
+
/** `npm_config_user_agent` names the manager that ran `reeval`; else the project's lockfile. */
|
|
20
|
+
function packageManager(env, root) {
|
|
21
|
+
const agent = /^(npm|pnpm|yarn|bun)\/(\S+)/.exec(env.npm_config_user_agent ?? "");
|
|
22
|
+
if (agent) return {
|
|
23
|
+
name: agent[1],
|
|
24
|
+
version: agent[2],
|
|
25
|
+
from: "user agent"
|
|
26
|
+
};
|
|
27
|
+
const locks = [
|
|
28
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
29
|
+
["yarn.lock", "yarn"],
|
|
30
|
+
["bun.lock", "bun"],
|
|
31
|
+
["bun.lockb", "bun"],
|
|
32
|
+
["package-lock.json", "npm"]
|
|
33
|
+
];
|
|
34
|
+
for (let dir = root;; dir = dirname(dir)) {
|
|
35
|
+
const hit = locks.find(([file]) => existsSync(join(dir, file)));
|
|
36
|
+
if (hit) return {
|
|
37
|
+
name: hit[1],
|
|
38
|
+
version: null,
|
|
39
|
+
from: hit[0]
|
|
40
|
+
};
|
|
41
|
+
if (dirname(dir) === dir) return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** The config's keys and the kinds it uses; no value, model or path from it. */
|
|
45
|
+
function configShape(config) {
|
|
46
|
+
const harnesses = new Set(config.harness.map((h) => h.kind));
|
|
47
|
+
const sandboxes = /* @__PURE__ */ new Set([config.sandbox.kind]);
|
|
48
|
+
for (const profile of Object.values(config.profiles)) {
|
|
49
|
+
for (const h of [profile.defaults?.harness ?? []].flat()) harnesses.add(h.kind);
|
|
50
|
+
if (profile.defaults?.sandbox) sandboxes.add(profile.defaults.sandbox.kind);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
file: config.file,
|
|
54
|
+
optionKeys: [...config.optionKeys ?? []].sort(),
|
|
55
|
+
harnesses: [...harnesses],
|
|
56
|
+
sandboxes: [...sandboxes],
|
|
57
|
+
profiles: Object.entries(config.profiles).map(([name, p]) => ({
|
|
58
|
+
name,
|
|
59
|
+
keys: Object.keys(p).sort(),
|
|
60
|
+
defaults: Object.keys(p.defaults ?? {}).sort()
|
|
61
|
+
}))
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const tail = (text, n) => text.trimEnd().split("\n").slice(-n);
|
|
65
|
+
const OMITTED_CONFIG = JSON.stringify({
|
|
66
|
+
kind: "config",
|
|
67
|
+
omitted: "reeval report leaves the config's values out"
|
|
68
|
+
});
|
|
69
|
+
/** A trace without its `config` entries, which hold the config's values. */
|
|
70
|
+
const withoutConfig = (trace) => trace.split("\n").map((line) => {
|
|
71
|
+
if (!line.includes("\"kind\":\"config\"")) return line;
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(line).kind === "config" ? OMITTED_CONFIG : line;
|
|
74
|
+
} catch {
|
|
75
|
+
return line;
|
|
76
|
+
}
|
|
77
|
+
}).join("\n");
|
|
78
|
+
const read = (path) => existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
79
|
+
function filesIn(dir) {
|
|
80
|
+
if (!existsSync(dir)) return [];
|
|
81
|
+
return readdirSync(dir, {
|
|
82
|
+
recursive: true,
|
|
83
|
+
withFileTypes: true
|
|
84
|
+
}).filter((e) => e.isFile()).map((e) => relative(dir, join(e.parentPath, e.name)).replaceAll("\\", "/")).sort();
|
|
85
|
+
}
|
|
86
|
+
function failedAttempts(record) {
|
|
87
|
+
return record.evals.flatMap((e) => e.attempts.filter((a) => a.outcome === "fail" || a.outcome === "infra_error"));
|
|
88
|
+
}
|
|
89
|
+
function attemptSlice(root, runId, attempt) {
|
|
90
|
+
const key = parseAttemptKey(attempt.key);
|
|
91
|
+
const dir = key ? attemptDebugDir(root, runId, key.evalId, key.cell, key.n) : "";
|
|
92
|
+
const requests = tail(read(join(dir, "requests.ndjson")), 500).filter((line) => {
|
|
93
|
+
try {
|
|
94
|
+
const r = JSON.parse(line);
|
|
95
|
+
return r.allowed === false || (r.status ?? 0) >= 400;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
key: attempt.key,
|
|
102
|
+
outcome: attempt.outcome,
|
|
103
|
+
error: attempt.error,
|
|
104
|
+
turnErrors: attempt.turns.flatMap((t) => t.error ? [t.error] : []),
|
|
105
|
+
debugFiles: dir ? filesIn(dir) : [],
|
|
106
|
+
debugLog: tail(read(join(dir, "debug.log")), LOG_LINES).filter(Boolean),
|
|
107
|
+
trace: tail(withoutConfig(read(join(dir, "trace.ndjson"))), TRACE_LINES).filter(Boolean).map((l) => l.length > TRACE_LINE_CHARS ? `${l.slice(0, TRACE_LINE_CHARS)}…` : l),
|
|
108
|
+
requests: requests.slice(-20)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function findRun(config, ref) {
|
|
112
|
+
const { allRuns } = await import("./commands-A4udfyKZ.js");
|
|
113
|
+
const { resolveRun } = await import("./views-CwCWLWqo.js");
|
|
114
|
+
const { readRecord, scratchDir } = await import("./files--6CEUXm7.js");
|
|
115
|
+
try {
|
|
116
|
+
const entry = resolveRun(await allRuns(config), ref);
|
|
117
|
+
const dir = entry.recorded ? config.history.dir : scratchDir(config.root);
|
|
118
|
+
return { record: await readRecord(join(dir, `${entry.runId}.json`)) };
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return { error: error.message };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Which command the report is about: the newest one, or with `--last` the newest that failed. */
|
|
124
|
+
function pickInvocation(root, last) {
|
|
125
|
+
const logged = readInvocations(root).filter((i) => i.command !== "report");
|
|
126
|
+
return (last ? logged.filter((i) => i.exitCode !== 0) : logged).at(-1) ?? null;
|
|
127
|
+
}
|
|
128
|
+
/** Collects the bundle for `reeval report [run]`, scrubbed; see `ReportDeps` for what it reads. */
|
|
129
|
+
async function collectReport(flags, refs, deps) {
|
|
130
|
+
const configPath = configPathFor(flags.config);
|
|
131
|
+
const root = configPath ? dirname(configPath) : deps.cwd;
|
|
132
|
+
loadEnvFiles(configPath);
|
|
133
|
+
for (const name of [".env.local", ".env"]) {
|
|
134
|
+
const contents = read(join(root, name));
|
|
135
|
+
if (contents) registerEnvFileValues(contents);
|
|
136
|
+
}
|
|
137
|
+
const scrub = scrubber({
|
|
138
|
+
env: deps.env,
|
|
139
|
+
home: deps.home,
|
|
140
|
+
project: root
|
|
141
|
+
});
|
|
142
|
+
let config;
|
|
143
|
+
let configError = null;
|
|
144
|
+
if (configPath) try {
|
|
145
|
+
config = await loadConfig(configPath, "report");
|
|
146
|
+
} catch (error) {
|
|
147
|
+
configError = error.message;
|
|
148
|
+
}
|
|
149
|
+
else configError = "no reeval.config.* here or above";
|
|
150
|
+
const doctor = await deps.doctor({ config: flags.config }).catch((error) => ({ error: error.message }));
|
|
151
|
+
const invocation = pickInvocation(root, !!flags.last);
|
|
152
|
+
let run = null;
|
|
153
|
+
let runError = null;
|
|
154
|
+
const ref = refs[0] ?? (flags.last && invocation?.runId ? invocation.runId : "latest");
|
|
155
|
+
const attachments = [];
|
|
156
|
+
const attachedMasks = {
|
|
157
|
+
before: 0,
|
|
158
|
+
after: 0
|
|
159
|
+
};
|
|
160
|
+
if (config) {
|
|
161
|
+
const found = await findRun(config, ref);
|
|
162
|
+
if ("error" in found) runError = found.error;
|
|
163
|
+
else {
|
|
164
|
+
const { record } = found;
|
|
165
|
+
const failed = failedAttempts(record);
|
|
166
|
+
const { runOutcome } = await import("./model-v-0_Ik7S.js");
|
|
167
|
+
run = {
|
|
168
|
+
runId: record.runId,
|
|
169
|
+
status: record.status,
|
|
170
|
+
outcome: runOutcome(record),
|
|
171
|
+
summary: { ...record.summary },
|
|
172
|
+
stack: record.stack,
|
|
173
|
+
failures: failed.slice(0, 5).map((a) => attemptSlice(root, record.runId, a)),
|
|
174
|
+
moreFailures: Math.max(0, failed.length - 5)
|
|
175
|
+
};
|
|
176
|
+
let bytes = 0;
|
|
177
|
+
for (const attempt of failed) {
|
|
178
|
+
const key = parseAttemptKey(attempt.key);
|
|
179
|
+
if (!key) continue;
|
|
180
|
+
const dir = attemptDebugDir(root, record.runId, key.evalId, key.cell, key.n);
|
|
181
|
+
for (const file of filesIn(dir)) {
|
|
182
|
+
const path = join(dir, file);
|
|
183
|
+
const size = statSync(path).size;
|
|
184
|
+
if (bytes + size > 20971520) continue;
|
|
185
|
+
bytes += size;
|
|
186
|
+
const text = readFileSync(path, "utf8");
|
|
187
|
+
const kept = file.endsWith("trace.ndjson") ? withoutConfig(text) : text;
|
|
188
|
+
const scrubbed = scrub.text(kept);
|
|
189
|
+
attachedMasks.before += masks(kept);
|
|
190
|
+
attachedMasks.after += masks(scrubbed);
|
|
191
|
+
attachments.push({
|
|
192
|
+
path: relative(join(root, ".reeval", "debug"), path).replaceAll("\\", "/"),
|
|
193
|
+
data: Buffer.from(scrubbed)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const raw = {
|
|
200
|
+
kind: "reeval-report",
|
|
201
|
+
version: 1,
|
|
202
|
+
createdAt: deps.now().toISOString(),
|
|
203
|
+
es: PACKAGE_VERSION,
|
|
204
|
+
node: deps.nodeVersion,
|
|
205
|
+
npm: await deps.npmVersion(),
|
|
206
|
+
os: {
|
|
207
|
+
platform: deps.platform,
|
|
208
|
+
release: deps.release,
|
|
209
|
+
arch: deps.arch
|
|
210
|
+
},
|
|
211
|
+
packageManager: packageManager(deps.env, root),
|
|
212
|
+
ci: inCI(deps.env),
|
|
213
|
+
agent: deps.agent,
|
|
214
|
+
env: Object.keys(deps.env).filter((name) => name.startsWith("REEVAL_") || SECRET_NAME.test(name)).filter((name) => deps.env[name]).sort(),
|
|
215
|
+
invocation,
|
|
216
|
+
doctor,
|
|
217
|
+
config: config ? configShape(config) : null,
|
|
218
|
+
configError,
|
|
219
|
+
run,
|
|
220
|
+
runError,
|
|
221
|
+
redaction: {
|
|
222
|
+
masked: 0,
|
|
223
|
+
upstream: 0,
|
|
224
|
+
paths: scrub.paths
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const bundle = scrub.value(raw);
|
|
228
|
+
const upstream = masks(JSON.stringify(raw)) + attachedMasks.before;
|
|
229
|
+
bundle.redaction = {
|
|
230
|
+
masked: masks(JSON.stringify(bundle)) + attachedMasks.after,
|
|
231
|
+
upstream,
|
|
232
|
+
paths: scrub.paths
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
bundle,
|
|
236
|
+
attachments,
|
|
237
|
+
root,
|
|
238
|
+
scrub
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/report/issue.ts
|
|
243
|
+
/** GitHub refuses longer new-issue URLs; this leaves room under its ~8 KB limit. */
|
|
244
|
+
const MAX_URL = 8e3;
|
|
245
|
+
/** GitHub's limit on an issue body is 65536 characters. */
|
|
246
|
+
const MAX_BODY = 6e4;
|
|
247
|
+
/** The issue form in `.github/ISSUE_TEMPLATE/`, and the ids of the fields reeval fills. */
|
|
248
|
+
const ISSUE_TEMPLATE = "bug_report.yml";
|
|
249
|
+
const FIELDS = {
|
|
250
|
+
report: "report",
|
|
251
|
+
whatHappened: "what-happened"
|
|
252
|
+
};
|
|
253
|
+
const TRUNCATED = "\n\n…cut to fit the URL: attach report.md for the rest.";
|
|
254
|
+
/** The issues page from `bugs` in reeval's package.json, so a rename moves it too. */
|
|
255
|
+
function issueRepo(pkg = JSON.parse(readFileSync(join(PACKAGE_DIR, "package.json"), "utf8"))) {
|
|
256
|
+
const url = (typeof pkg.bugs === "string" ? pkg.bugs : pkg.bugs?.url)?.replace(/\/+$/, "");
|
|
257
|
+
if (!url) throw new Error("reeval's package.json has no bugs URL");
|
|
258
|
+
return {
|
|
259
|
+
issues: url,
|
|
260
|
+
slug: /^https:\/\/github\.com\/([^/]+\/[^/]+)\/issues$/.exec(url)?.[1] ?? null
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function build(repo, title, whatHappened, report) {
|
|
264
|
+
const params = new URLSearchParams({
|
|
265
|
+
template: ISSUE_TEMPLATE,
|
|
266
|
+
title,
|
|
267
|
+
[FIELDS.whatHappened]: whatHappened,
|
|
268
|
+
[FIELDS.report]: report
|
|
269
|
+
});
|
|
270
|
+
return `${repo.issues}/new?${params.toString().replaceAll("+", "%20")}`;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* The new-issue URL with the form's fields filled, at most `max` characters: the report field is
|
|
274
|
+
* cut at a line (and says so) until it fits.
|
|
275
|
+
*/
|
|
276
|
+
function issueUrl(repo, title, report, whatHappened = "", max = MAX_URL) {
|
|
277
|
+
const full = build(repo, title, whatHappened, report);
|
|
278
|
+
if (full.length <= max) return full;
|
|
279
|
+
let lo = 0;
|
|
280
|
+
let hi = report.length;
|
|
281
|
+
while (lo < hi) {
|
|
282
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
283
|
+
if (build(repo, title, whatHappened, report.slice(0, mid) + TRUNCATED).length <= max) lo = mid;
|
|
284
|
+
else hi = mid - 1;
|
|
285
|
+
}
|
|
286
|
+
const cut = report.slice(0, lo);
|
|
287
|
+
const atLine = cut.lastIndexOf("\n");
|
|
288
|
+
const url = build(repo, title, whatHappened, (atLine > cut.length / 2 ? cut.slice(0, atLine) : cut) + TRUNCATED);
|
|
289
|
+
if (url.length > max) throw new Error("The issue title alone is too long for a URL");
|
|
290
|
+
return url;
|
|
291
|
+
}
|
|
292
|
+
const exec = (command, args) => new Promise((resolve) => {
|
|
293
|
+
execFile(command, args, { timeout: 6e4 }, (error, stdout, stderr) => resolve({
|
|
294
|
+
code: error ? typeof error.code === "number" ? error.code : 1 : 0,
|
|
295
|
+
stdout: String(stdout),
|
|
296
|
+
stderr: String(stderr)
|
|
297
|
+
}));
|
|
298
|
+
});
|
|
299
|
+
/** gh through reeval's own `run`, which finds it however it's installed on Windows (.exe or .cmd). */
|
|
300
|
+
const gh = (args) => run("gh", args, {
|
|
301
|
+
signal: AbortSignal.timeout(6e4),
|
|
302
|
+
detached: false
|
|
303
|
+
}).then((r) => ({
|
|
304
|
+
code: r.exitCode,
|
|
305
|
+
stdout: r.stdout,
|
|
306
|
+
stderr: r.stderr
|
|
307
|
+
}), (error) => ({
|
|
308
|
+
code: 1,
|
|
309
|
+
stdout: "",
|
|
310
|
+
stderr: error.message
|
|
311
|
+
}));
|
|
312
|
+
function defaultIssueDeps() {
|
|
313
|
+
return {
|
|
314
|
+
hasGh: async () => (await gh(["--version"])).code === 0,
|
|
315
|
+
gh,
|
|
316
|
+
open: async (url) => {
|
|
317
|
+
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", [
|
|
318
|
+
"/c",
|
|
319
|
+
"start",
|
|
320
|
+
"\"\"",
|
|
321
|
+
url.replaceAll("&", "^&")
|
|
322
|
+
]] : ["xdg-open", [url]];
|
|
323
|
+
return (await exec(command, args)).code === 0;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/** Files it with gh when gh is there and the user says yes (or `-y`); else the URL. */
|
|
328
|
+
async function fileIssue(o, deps) {
|
|
329
|
+
if (o.repo.slug && await deps.hasGh()) {
|
|
330
|
+
if (o.yes || !!o.confirm && await o.confirm(`File this as an issue on ${o.repo.slug} with gh issue create? [y/N] `)) {
|
|
331
|
+
const r = await deps.gh([
|
|
332
|
+
"issue",
|
|
333
|
+
"create",
|
|
334
|
+
"--repo",
|
|
335
|
+
o.repo.slug,
|
|
336
|
+
"--title",
|
|
337
|
+
o.title,
|
|
338
|
+
"--body-file",
|
|
339
|
+
o.bodyFile
|
|
340
|
+
]);
|
|
341
|
+
if (r.code === 0) {
|
|
342
|
+
o.out(`Filed ${r.stdout.trim()}`);
|
|
343
|
+
return "gh";
|
|
344
|
+
}
|
|
345
|
+
o.out(`gh issue create failed (${r.stderr.trim() || `exit ${r.code}`}); use the URL instead.`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const url = issueUrl(o.repo, o.title, o.body, o.whatHappened);
|
|
349
|
+
o.out("Open this to file the issue (pre-filled; nothing is sent until you submit it):");
|
|
350
|
+
o.out(url);
|
|
351
|
+
if (o.interactive) await deps.open(url);
|
|
352
|
+
return "url";
|
|
353
|
+
}
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/report/render.ts
|
|
356
|
+
const isDoctor = (value) => !!value && typeof value === "object" && Array.isArray(value.checks);
|
|
357
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
358
|
+
function versionsLine(b) {
|
|
359
|
+
const pm = b.packageManager;
|
|
360
|
+
return [
|
|
361
|
+
`reeval ${b.es}`,
|
|
362
|
+
`Node ${b.node}`,
|
|
363
|
+
b.npm ? `npm ${b.npm}` : "no npm",
|
|
364
|
+
...pm ? [`${pm.name}${pm.version ? ` ${pm.version}` : ""}`] : [],
|
|
365
|
+
`${b.os.platform} ${b.os.release} ${b.os.arch}`,
|
|
366
|
+
...b.ci ? ["CI"] : [],
|
|
367
|
+
...b.agent ? [`agent ${b.agent}`] : []
|
|
368
|
+
].join(" · ");
|
|
369
|
+
}
|
|
370
|
+
function commandLine(b) {
|
|
371
|
+
const i = b.invocation;
|
|
372
|
+
if (!i) return null;
|
|
373
|
+
return `reeval ${i.argv.join(" ")} → exit ${i.exitCode}${i.runId ? ` (run ${i.runId})` : ""}, ${i.at}`;
|
|
374
|
+
}
|
|
375
|
+
/** The error the report is about: the last command's, else the run's first failure. */
|
|
376
|
+
function headline(b) {
|
|
377
|
+
const error = b.invocation?.error;
|
|
378
|
+
if (error) return `${error.name}: ${error.message}`.split("\n")[0];
|
|
379
|
+
const unhandled = b.invocation?.unhandledErrors[0];
|
|
380
|
+
if (unhandled) return unhandled.split("\n")[0];
|
|
381
|
+
const failure = b.run?.failures.find((f) => f.outcome === "infra_error") ?? b.run?.failures[0];
|
|
382
|
+
if (failure?.error) return `${failure.error.kind}: ${failure.error.message}`.split("\n")[0];
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
function runLine(b) {
|
|
386
|
+
if (!b.run) return b.runError ? `none (${b.runError})` : null;
|
|
387
|
+
const s = b.run.summary;
|
|
388
|
+
const parts = [
|
|
389
|
+
plural(s.attempts ?? 0, "attempt"),
|
|
390
|
+
`${s.pass ?? 0} pass`,
|
|
391
|
+
...s.fail ? [`${s.fail} fail`] : [],
|
|
392
|
+
...s.infraError ? [plural(s.infraError, "infra error")] : [],
|
|
393
|
+
...s.skipped ? [`${s.skipped} skipped`] : []
|
|
394
|
+
];
|
|
395
|
+
return `${b.run.runId}: ${b.run.outcome}, ${parts.join(", ")}`;
|
|
396
|
+
}
|
|
397
|
+
function doctorLine(b) {
|
|
398
|
+
if (!isDoctor(b.doctor)) return `didn't run (${b.doctor?.error ?? "?"})`;
|
|
399
|
+
const d = b.doctor;
|
|
400
|
+
const failing = [...new Set(d.checks.filter((c) => c.status === "fail").map((c) => c.id))];
|
|
401
|
+
return `${plural(d.problems, "problem")}${failing.length ? ` (${failing.join(", ")})` : ""}, ${plural(d.warnings, "warning")}`;
|
|
402
|
+
}
|
|
403
|
+
function configLine(b) {
|
|
404
|
+
const c = b.config;
|
|
405
|
+
if (!c) return `not loaded (${b.configError ?? "?"})`;
|
|
406
|
+
return [
|
|
407
|
+
`${c.file}: keys ${c.optionKeys.join(", ") || "none"}`,
|
|
408
|
+
`harness ${c.harnesses.join(", ")}`,
|
|
409
|
+
`sandbox ${c.sandboxes.join(", ")}`,
|
|
410
|
+
c.profiles.length ? `profiles ${c.profiles.map((p) => p.name).join(", ")}` : "no profiles"
|
|
411
|
+
].join("; ");
|
|
412
|
+
}
|
|
413
|
+
function redactionLine(b) {
|
|
414
|
+
const paths = ["home → ~", ...b.redaction.paths.includes("<project>") ? ["project → <project>"] : []];
|
|
415
|
+
const { masked, upstream } = b.redaction;
|
|
416
|
+
const already = upstream && upstream < masked ? `, ${upstream} of them already by reeval's records and logs` : upstream ? ", all already by reeval's records and logs" : "";
|
|
417
|
+
return `${plural(masked, "secret value")} masked${already}; ${paths.join(", ")}`;
|
|
418
|
+
}
|
|
419
|
+
/** The terminal summary, one labelled line each. */
|
|
420
|
+
function summaryLines(b, attached) {
|
|
421
|
+
const rows = [
|
|
422
|
+
["versions", versionsLine(b)],
|
|
423
|
+
["command", commandLine(b) ?? "none logged in this project"],
|
|
424
|
+
["error", headline(b)],
|
|
425
|
+
["run", runLine(b)],
|
|
426
|
+
["failing", b.run?.failures.length ? b.run.failures.map((f) => `${f.key}${f.error ? ` (${f.error.kind})` : ""}`).join("; ") + (b.run.moreFailures ? ` and ${b.run.moreFailures} more` : "") : null],
|
|
427
|
+
["doctor", doctorLine(b)],
|
|
428
|
+
["config", configLine(b)],
|
|
429
|
+
["logs", attached ? `${plural(attached, "debug file")} attached` : null],
|
|
430
|
+
["redacted", redactionLine(b)]
|
|
431
|
+
];
|
|
432
|
+
const width = Math.max(...rows.map(([k]) => k.length));
|
|
433
|
+
return rows.flatMap(([k, v]) => v ? [`${k.padEnd(width)} ${v}`] : []);
|
|
434
|
+
}
|
|
435
|
+
const fence = (lang, lines) => [
|
|
436
|
+
"",
|
|
437
|
+
`\`\`\`${lang}`,
|
|
438
|
+
...lines,
|
|
439
|
+
"```"
|
|
440
|
+
];
|
|
441
|
+
function stackLines(b, max = Number.POSITIVE_INFINITY) {
|
|
442
|
+
const error = b.invocation?.error;
|
|
443
|
+
if (!error) return [];
|
|
444
|
+
const lines = (error.stack ?? `${error.name}: ${error.message}`).split("\n");
|
|
445
|
+
return lines.length > max ? [...lines.slice(0, max), "…"] : lines;
|
|
446
|
+
}
|
|
447
|
+
/** report.md: everything in the bundle, for a person to read and attach. */
|
|
448
|
+
function renderMarkdown(b, attachedFiles) {
|
|
449
|
+
const out = [
|
|
450
|
+
"# reeval report",
|
|
451
|
+
"",
|
|
452
|
+
`Collected by \`reeval report\` at ${b.createdAt}. ${redactionLine(b)}. Review it before you share it.`,
|
|
453
|
+
"",
|
|
454
|
+
"## Environment",
|
|
455
|
+
"",
|
|
456
|
+
"| | |",
|
|
457
|
+
"|---|---|",
|
|
458
|
+
`| reeval | ${b.es} |`,
|
|
459
|
+
`| Node | ${b.node} |`,
|
|
460
|
+
`| npm | ${b.npm ?? "not found"} |`,
|
|
461
|
+
`| Package manager | ${b.packageManager ? `${b.packageManager.name} ${b.packageManager.version ?? ""} (from ${b.packageManager.from})` : "unknown"} |`,
|
|
462
|
+
`| OS | ${b.os.platform} ${b.os.release} ${b.os.arch} |`,
|
|
463
|
+
`| CI | ${b.ci ? "yes" : "no"} |`,
|
|
464
|
+
`| Coding agent | ${b.agent ?? "none"} |`,
|
|
465
|
+
`| Variables set | ${b.env.join(", ") || "none"} |`,
|
|
466
|
+
"",
|
|
467
|
+
"## Last command",
|
|
468
|
+
"",
|
|
469
|
+
b.invocation ? `\`reeval ${b.invocation.argv.join(" ")}\` exited ${b.invocation.exitCode} after ${b.invocation.durationMs} ms, at ${b.invocation.at}${b.invocation.runId ? `, run \`${b.invocation.runId}\`` : ""}.` : "No `reeval` command is logged in this project (`.reeval/cli.jsonl`)."
|
|
470
|
+
];
|
|
471
|
+
if (b.invocation?.error) out.push(...fence("text", stackLines(b)));
|
|
472
|
+
for (const e of b.invocation?.unhandledErrors ?? []) out.push(...fence("text", e.split("\n")));
|
|
473
|
+
out.push("", "## Run", "");
|
|
474
|
+
if (!b.run) out.push(b.runError ? `No run: ${b.runError}` : "No run.");
|
|
475
|
+
else {
|
|
476
|
+
out.push(`${runLine(b)}, status ${b.run.status}.`);
|
|
477
|
+
if (b.run.moreFailures) out.push("", `${b.run.moreFailures} more failing attempts aren't shown.`);
|
|
478
|
+
for (const f of b.run.failures) {
|
|
479
|
+
out.push("", `### ${f.key}`, "", `Outcome ${f.outcome}.`);
|
|
480
|
+
if (f.error) out.push(...fence("text", [`${f.error.kind}: ${f.error.message}`]));
|
|
481
|
+
for (const t of f.turnErrors) out.push(...fence("text", [`turn: ${t}`]));
|
|
482
|
+
if (f.debugLog.length) out.push("", "debug.log, last lines:", ...fence("text", f.debugLog));
|
|
483
|
+
if (f.requests.length) out.push("", "Refused or failed gateway requests:", ...fence("json", f.requests));
|
|
484
|
+
if (f.trace.length) out.push("", "trace.ndjson, last lines:", ...fence("json", f.trace));
|
|
485
|
+
}
|
|
486
|
+
out.push("", "### Sandbox and images", ...fence("json", [JSON.stringify(b.run.stack, null, 2)]));
|
|
487
|
+
}
|
|
488
|
+
out.push("", "## reeval doctor");
|
|
489
|
+
if (isDoctor(b.doctor)) out.push(...fence("text", b.doctor.checks.map((c) => `${c.status.padEnd(4)} ${c.id.padEnd(9)} ${c.summary}${c.fix ? ` (fix: ${c.fix})` : ""}`)));
|
|
490
|
+
else out.push("", doctorLine(b));
|
|
491
|
+
out.push("", "## Config", "", configLine(b));
|
|
492
|
+
if (b.config?.profiles.length) {
|
|
493
|
+
out.push("", "| Profile | Keys | Defaults |", "|---|---|---|");
|
|
494
|
+
for (const p of b.config.profiles) out.push(`| ${p.name} | ${p.keys.join(", ")} | ${p.defaults.join(", ") || "-"} |`);
|
|
495
|
+
}
|
|
496
|
+
if (attachedFiles.length) {
|
|
497
|
+
out.push("", "## Attached logs", "", "In `report-logs.tar.gz`, by path under `.reeval/debug`:", "");
|
|
498
|
+
out.push(...attachedFiles.map((f) => `- \`${f}\``));
|
|
499
|
+
}
|
|
500
|
+
return `${out.join("\n")}\n`;
|
|
501
|
+
}
|
|
502
|
+
/** The issue's body: what a maintainer reads first; report.md has the rest. */
|
|
503
|
+
function renderIssueBody(b, reportDir) {
|
|
504
|
+
const out = [
|
|
505
|
+
`**Environment:** ${versionsLine(b)}`,
|
|
506
|
+
"",
|
|
507
|
+
`**Command:** ${b.invocation ? `\`reeval ${b.invocation.argv.join(" ")}\` → exit ${b.invocation.exitCode}` : "none logged"}`
|
|
508
|
+
];
|
|
509
|
+
const stack = stackLines(b, 25);
|
|
510
|
+
if (stack.length) out.push(...fence("text", stack));
|
|
511
|
+
const run = runLine(b);
|
|
512
|
+
if (run) out.push("", `**Run:** ${run}`);
|
|
513
|
+
for (const f of b.run?.failures ?? []) out.push(`- \`${f.key}\`: ${f.error ? `${f.error.kind}: ${f.error.message.split("\n")[0]}` : f.outcome}`);
|
|
514
|
+
out.push("", `**reeval doctor:** ${doctorLine(b)}`);
|
|
515
|
+
if (isDoctor(b.doctor)) for (const c of b.doctor.checks.filter((c) => c.status === "fail" || c.status === "warn")) out.push(`- ${c.status} ${c.id}: ${c.summary}`);
|
|
516
|
+
out.push("", `**Config:** ${configLine(b)}`, "", `_${redactionLine(b)}._`);
|
|
517
|
+
out.push("", reportDir ? `The full report is in \`${reportDir}\`: attach \`report.md\` (and \`report-logs.tar.gz\`, if it's there) to this issue.` : "Run `reeval report --out <dir>` and attach `report.md` to this issue.");
|
|
518
|
+
return out.join("\n");
|
|
519
|
+
}
|
|
520
|
+
/** The issue's title, from the error. */
|
|
521
|
+
function issueTitle(b) {
|
|
522
|
+
const title = `${b.invocation?.command ? `reeval ${b.invocation.command}` : "es"}: ${headline(b) ?? (b.run ? `run ended ${b.run.outcome}` : "bug report")}`;
|
|
523
|
+
return title.length > 100 ? `${title.slice(0, 99)}…` : title;
|
|
524
|
+
}
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region src/report/tar.ts
|
|
527
|
+
const BLOCK = 512;
|
|
528
|
+
function octal(value, width) {
|
|
529
|
+
return `${value.toString(8).padStart(width - 1, "0")}\0`;
|
|
530
|
+
}
|
|
531
|
+
/** `path` as ustar's 155-byte prefix and 100-byte name, split on a `/`. */
|
|
532
|
+
function splitPath(path) {
|
|
533
|
+
if (Buffer.byteLength(path) <= 100) return {
|
|
534
|
+
prefix: "",
|
|
535
|
+
name: path
|
|
536
|
+
};
|
|
537
|
+
for (let i = path.indexOf("/"); i !== -1; i = path.indexOf("/", i + 1)) {
|
|
538
|
+
const prefix = path.slice(0, i);
|
|
539
|
+
const name = path.slice(i + 1);
|
|
540
|
+
if (Buffer.byteLength(prefix) <= 155 && Buffer.byteLength(name) <= 100) return {
|
|
541
|
+
prefix,
|
|
542
|
+
name
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
throw new Error(`${path} is too long for a tar entry`);
|
|
546
|
+
}
|
|
547
|
+
function header(path, size, mtime) {
|
|
548
|
+
const block = Buffer.alloc(BLOCK);
|
|
549
|
+
const { prefix, name } = splitPath(path);
|
|
550
|
+
block.write(name, 0);
|
|
551
|
+
block.write(octal(420, 8), 100);
|
|
552
|
+
block.write(octal(0, 8), 108);
|
|
553
|
+
block.write(octal(0, 8), 116);
|
|
554
|
+
block.write(octal(size, 12), 124);
|
|
555
|
+
block.write(octal(mtime, 12), 136);
|
|
556
|
+
block.write(" ".repeat(8), 148);
|
|
557
|
+
block.write("0", 156);
|
|
558
|
+
block.write("ustar\0", 257);
|
|
559
|
+
block.write("00", 263);
|
|
560
|
+
block.write(prefix, 345);
|
|
561
|
+
let sum = 0;
|
|
562
|
+
for (const byte of block) sum += byte;
|
|
563
|
+
block.write(`${sum.toString(8).padStart(6, "0")}\0 `, 148);
|
|
564
|
+
return block;
|
|
565
|
+
}
|
|
566
|
+
/** A gzipped ustar archive of regular files, readable by `tar -xzf`. */
|
|
567
|
+
function tarGz(entries, mtime = Math.floor(Date.now() / 1e3)) {
|
|
568
|
+
const parts = [];
|
|
569
|
+
for (const { path, data } of entries) {
|
|
570
|
+
parts.push(header(path, data.length, mtime), data);
|
|
571
|
+
const pad = (BLOCK - data.length % BLOCK) % BLOCK;
|
|
572
|
+
if (pad) parts.push(Buffer.alloc(pad));
|
|
573
|
+
}
|
|
574
|
+
parts.push(Buffer.alloc(BLOCK * 2));
|
|
575
|
+
return gzipSync(Buffer.concat(parts));
|
|
576
|
+
}
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/report/command.ts
|
|
579
|
+
async function defaultReportDeps() {
|
|
580
|
+
const { defaultDeps, doctor } = await import("./doctor-CxlH1pDM.js");
|
|
581
|
+
const doctorDeps = defaultDeps();
|
|
582
|
+
return {
|
|
583
|
+
env: process.env,
|
|
584
|
+
cwd: process.cwd(),
|
|
585
|
+
home: homedir(),
|
|
586
|
+
platform: process.platform,
|
|
587
|
+
release: release(),
|
|
588
|
+
arch: process.arch,
|
|
589
|
+
nodeVersion: process.versions.node,
|
|
590
|
+
npmVersion: () => new Promise((done) => execFile("npm", ["--version"], {
|
|
591
|
+
shell: process.platform === "win32",
|
|
592
|
+
timeout: 2e4
|
|
593
|
+
}, (error, stdout) => done(error ? null : String(stdout).trim()))),
|
|
594
|
+
doctor: async (flags) => (await doctor(flags, doctorDeps)).report,
|
|
595
|
+
agent: detectAgent().name ?? null,
|
|
596
|
+
now: () => /* @__PURE__ */ new Date()
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
const stamp = (date) => date.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
600
|
+
/**
|
|
601
|
+
* Writes report.md, report.json and (with logs) report-logs.tar.gz. `out` is a directory, or a
|
|
602
|
+
* `.md` or `.json` file that the others are written beside.
|
|
603
|
+
*/
|
|
604
|
+
function writeReport(collected, out, attached) {
|
|
605
|
+
const ext = extname(out).toLowerCase();
|
|
606
|
+
const asFile = ext === ".md" || ext === ".json";
|
|
607
|
+
const dir = asFile ? dirname(out) : out;
|
|
608
|
+
const base = asFile ? basename(out, extname(out)) : "report";
|
|
609
|
+
mkdirSync(dir, { recursive: true });
|
|
610
|
+
const markdown = join(dir, `${base}.md`);
|
|
611
|
+
const json = join(dir, `${base}.json`);
|
|
612
|
+
writeFileSync(markdown, renderMarkdown(collected.bundle, attached));
|
|
613
|
+
writeFileSync(json, `${JSON.stringify(collected.bundle, null, 2)}\n`);
|
|
614
|
+
let logs = null;
|
|
615
|
+
if (collected.attachments.length) {
|
|
616
|
+
logs = join(dir, `${base}-logs.tar.gz`);
|
|
617
|
+
writeFileSync(logs, tarGz(collected.attachments));
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
dir,
|
|
621
|
+
markdown,
|
|
622
|
+
json,
|
|
623
|
+
logs
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
async function reportCommand(flags, refs, io, deps, issueDeps = defaultIssueDeps()) {
|
|
627
|
+
if (flags.json && flags.issue) throw new UsageError("reeval report --json prints the bundle; --issue files it. Pick one.");
|
|
628
|
+
const collected = await collectReport(flags, refs, deps ?? await defaultReportDeps());
|
|
629
|
+
const { bundle, attachments, root } = collected;
|
|
630
|
+
const attached = attachments.map((a) => a.path);
|
|
631
|
+
if (flags.json) {
|
|
632
|
+
io.out(JSON.stringify(bundle, null, 2));
|
|
633
|
+
if (flags.out) {
|
|
634
|
+
const w = writeReport(collected, resolve(flags.out), attached);
|
|
635
|
+
io.err(`Wrote ${files(w)}`);
|
|
636
|
+
}
|
|
637
|
+
return 0;
|
|
638
|
+
}
|
|
639
|
+
const written = writeReport(collected, resolve(flags.out ?? join(root, ".reeval", "reports", stamp(new Date(bundle.createdAt)))), attached);
|
|
640
|
+
io.out("reeval report");
|
|
641
|
+
for (const line of summaryLines(bundle, attached.length)) io.out(line);
|
|
642
|
+
io.out("");
|
|
643
|
+
io.out(`Wrote ${files(written)}`);
|
|
644
|
+
if (!flags.issue) {
|
|
645
|
+
io.out("Nothing was uploaded. Review the files, then file it with reeval report --issue.");
|
|
646
|
+
return 0;
|
|
647
|
+
}
|
|
648
|
+
const body = renderIssueBody(bundle, collected.scrub.text(written.dir));
|
|
649
|
+
const bodyFile = join(written.dir, "issue.md");
|
|
650
|
+
writeFileSync(bodyFile, body.length > 6e4 ? `${body.slice(0, MAX_BODY)}\n…` : body);
|
|
651
|
+
await fileIssue({
|
|
652
|
+
repo: issueRepo(),
|
|
653
|
+
title: issueTitle(bundle),
|
|
654
|
+
body,
|
|
655
|
+
bodyFile,
|
|
656
|
+
whatHappened: "",
|
|
657
|
+
confirm: io.confirm,
|
|
658
|
+
yes: !!flags.yes,
|
|
659
|
+
interactive: io.interactive,
|
|
660
|
+
out: io.out
|
|
661
|
+
}, issueDeps);
|
|
662
|
+
return 0;
|
|
663
|
+
}
|
|
664
|
+
const rel = (path) => fromCwd(path) || path;
|
|
665
|
+
const files = (w) => [
|
|
666
|
+
w.markdown,
|
|
667
|
+
w.json,
|
|
668
|
+
...w.logs ? [w.logs] : []
|
|
669
|
+
].map(rel).join(", ");
|
|
670
|
+
//#endregion
|
|
671
|
+
export { reportCommand };
|
|
672
|
+
|
|
673
|
+
//# sourceMappingURL=command-D-jnQWVG.js.map
|