@alint-js/cli 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -24
- package/dist/bin/index.mjs +1 -1
- package/dist/{cli-DFWnPI6m.mjs → cli-cQihuHqx.mjs} +1967 -1269
- package/dist/index.d.mts +11 -3
- package/dist/index.mjs +1 -1
- package/dist/server-xx0w7UQL.mjs +368 -0
- package/package.json +24 -6
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { inspect } from "node:util";
|
|
3
3
|
import { cac } from "cac";
|
|
4
|
-
import { appendFile, mkdir, opendir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
5
|
-
import { addProviderModels, getGlobalSetupConfigPath, getProjectSetupConfigPath, getStatsDir, installStaticPlugins, isAcpModel, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, normalizeSetupConfig, pruneProviderModels, removeProviderModels, replaceSetupProvider, setProviderEndpoint, setProviderHeader, unsetProviderHeader, writeSetupConfig } from "@alint-js/config";
|
|
6
|
-
import { AlintRunCancelledError, AlintRunError, benchmarkModels, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, runAlint } from "@alint-js/core";
|
|
4
|
+
import { appendFile, chmod, mkdir, opendir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { addProviderModels, getGlobalSetupConfigPath, getProjectSetupConfigPath, getStatsDir, installStaticPlugins, isAcpModel, loadAlintConfig, loadAlintConfigWithMetadata, loadSetupConfig, mergeSetupConfigs, normalizeSetupConfig, pruneProviderModels, removeProviderModels, replaceSetupProvider, setProviderEndpoint, setProviderHeader, setStopGateConfig, unsetProviderHeader, writeSetupConfig } from "@alint-js/config";
|
|
6
|
+
import { AlintCachePersistenceError, AlintRunCancelledError, AlintRunError, benchmarkModels, defaultStopGateConfig, hasDiscoveryFilePatterns, matchesDiscoveryFile, maximumStopGateTimeoutMs, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, resolveStopGateConfig, runAlint, stableHash } from "@alint-js/core";
|
|
7
7
|
import { isAbsolute, join, relative, resolve } from "pathe";
|
|
8
|
-
import { errorMessageFrom } from "@moeru/std
|
|
8
|
+
import { errorMessageFrom } from "@moeru/std";
|
|
9
|
+
import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std/error";
|
|
9
10
|
import { getBorderCharacters, table } from "table";
|
|
10
11
|
import { createColors } from "tinyrainbow";
|
|
11
12
|
import { createCommandModel, startGateway } from "@alint-js/model-adapter-acp";
|
|
12
13
|
import cliSpinners from "cli-spinners";
|
|
13
14
|
import fastStringWidth from "fast-string-width";
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
15
|
+
import { parsePatch } from "diff";
|
|
16
|
+
import { x } from "tinyexec";
|
|
17
|
+
import { Buffer } from "node:buffer";
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join as join$1, relative as relative$1 } from "node:path";
|
|
21
|
+
import { isNodeErrorCode } from "@alint-js/utils/node";
|
|
17
22
|
import Gitignore from "gitignore-fs";
|
|
18
23
|
import { Minimatch, minimatch } from "minimatch";
|
|
19
|
-
import
|
|
24
|
+
import fastStringTruncatedWidth from "fast-string-truncated-width";
|
|
25
|
+
import { formatDuration, intervalToDuration } from "date-fns";
|
|
20
26
|
//#region package.json
|
|
21
|
-
var version = "0.
|
|
27
|
+
var version = "0.6.0";
|
|
22
28
|
//#endregion
|
|
23
29
|
//#region src/cli/output.ts
|
|
24
30
|
function escapeLineValue(value) {
|
|
@@ -47,6 +53,7 @@ function dispatchCommand(context, node, args, options, path) {
|
|
|
47
53
|
const [subcommand, ...restArgs] = args;
|
|
48
54
|
const child = node.children?.find((item) => item.name === subcommand || item.alias?.includes(subcommand ?? ""));
|
|
49
55
|
if (child) return dispatchCommand(context, child, restArgs, options, [...path, child.name]);
|
|
56
|
+
if (subcommand === void 0 && node.children?.length && !node.action) return Promise.resolve(reportMissingSubcommand(context, path, node.children));
|
|
50
57
|
if (!node.action) return Promise.resolve(reportUnknownCommand(context, path, args));
|
|
51
58
|
if (node.exactArguments === true) {
|
|
52
59
|
const unexpectedArgument = args[node.arguments?.split(/\s+/u).filter(Boolean).length ?? 0];
|
|
@@ -170,6 +177,19 @@ function registerRootCommand(cli, node, context, setPendingResult) {
|
|
|
170
177
|
return setPendingResult(node.children ? dispatchCommand(context, node, args[0] ?? [], options, [node.name]) : Promise.resolve(node.action?.(context, ...args.slice(0, -1), options) ?? 0));
|
|
171
178
|
});
|
|
172
179
|
}
|
|
180
|
+
function reportMissingSubcommand(context, path, children) {
|
|
181
|
+
const command = path.join(" ");
|
|
182
|
+
context.io.stderr.write([
|
|
183
|
+
`missing subcommand for "${command}"`,
|
|
184
|
+
"",
|
|
185
|
+
"available subcommands:",
|
|
186
|
+
...children.map((child) => ` ${child.name}`),
|
|
187
|
+
"",
|
|
188
|
+
`run \`alint ${command} --help\` for more information`,
|
|
189
|
+
""
|
|
190
|
+
].join("\n"));
|
|
191
|
+
return 2;
|
|
192
|
+
}
|
|
173
193
|
function reportUnknownCommand(context, path, args) {
|
|
174
194
|
context.io.stderr.write(`unknown command: ${formatUnknownCommand(path, args)}\n`);
|
|
175
195
|
return 2;
|
|
@@ -249,6 +269,115 @@ async function runConfigInspectCommand(path, configPath, io) {
|
|
|
249
269
|
return 0;
|
|
250
270
|
}
|
|
251
271
|
//#endregion
|
|
272
|
+
//#region src/cli/commands/config/integrations/stop-gate.ts
|
|
273
|
+
const set$1 = defineCommand({
|
|
274
|
+
async action(context, options) {
|
|
275
|
+
try {
|
|
276
|
+
let target;
|
|
277
|
+
if (options.target === void 0) target = void 0;
|
|
278
|
+
else if (options.target === "all" || options.target === "dirty-files") target = options.target;
|
|
279
|
+
else throw new Error("Stop Gate target must be \"all\" or \"dirty-files\".");
|
|
280
|
+
let timeoutMs;
|
|
281
|
+
if (options.timeoutMs !== void 0) {
|
|
282
|
+
timeoutMs = Number(options.timeoutMs);
|
|
283
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > maximumStopGateTimeoutMs) throw new Error(`Stop Gate timeout must be an integer from 1 to ${maximumStopGateTimeoutMs}.`);
|
|
284
|
+
}
|
|
285
|
+
const result = await setStopGateConfig({
|
|
286
|
+
configFile: options.config,
|
|
287
|
+
cwd: context.io.cwd,
|
|
288
|
+
target,
|
|
289
|
+
timeoutMs
|
|
290
|
+
});
|
|
291
|
+
context.io.stdout.write(`config: ${result.configFile}\n`);
|
|
292
|
+
writeResolvedConfig((chunk) => context.io.stdout.write(chunk), result.config);
|
|
293
|
+
return 0;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
context.io.stderr.write(`${errorMessageFrom(error) ?? "Failed to write Stop Gate config."}\n`);
|
|
296
|
+
return 2;
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
description: "Set Stop Gate config overrides in TOML",
|
|
300
|
+
exactArguments: true,
|
|
301
|
+
name: "set",
|
|
302
|
+
options: [{
|
|
303
|
+
description: "Lint target mode",
|
|
304
|
+
flags: "--target <target>"
|
|
305
|
+
}, {
|
|
306
|
+
description: "Overall Stop Gate timeout in milliseconds",
|
|
307
|
+
flags: "--timeout-ms <ms>"
|
|
308
|
+
}]
|
|
309
|
+
});
|
|
310
|
+
const enable = defineCommand({
|
|
311
|
+
action: (context) => setActivation(context, true),
|
|
312
|
+
description: "Enable Stop Gate for this repository",
|
|
313
|
+
exactArguments: true,
|
|
314
|
+
name: "enable"
|
|
315
|
+
});
|
|
316
|
+
const disable = defineCommand({
|
|
317
|
+
action: (context) => setActivation(context, false),
|
|
318
|
+
description: "Disable Stop Gate for this repository",
|
|
319
|
+
exactArguments: true,
|
|
320
|
+
name: "disable"
|
|
321
|
+
});
|
|
322
|
+
const stopGate$1 = defineCommand({
|
|
323
|
+
children: [
|
|
324
|
+
defineCommand({
|
|
325
|
+
async action(context, options) {
|
|
326
|
+
try {
|
|
327
|
+
const loaded = await loadAlintConfigWithMetadata(context.io.cwd, options.config);
|
|
328
|
+
const resolved = resolveStopGateConfig(loaded.config, context.io.cwd);
|
|
329
|
+
context.io.stdout.write(`config: ${loaded.configFile ?? "<not found>"}\n`);
|
|
330
|
+
writeResolvedConfig((chunk) => context.io.stdout.write(chunk), resolved);
|
|
331
|
+
return 0;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
context.io.stderr.write(`${errorMessageFrom(error) ?? "Failed to read Stop Gate config."}\n`);
|
|
334
|
+
return 2;
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
description: "Show resolved Stop Gate config",
|
|
338
|
+
exactArguments: true,
|
|
339
|
+
name: "show"
|
|
340
|
+
}),
|
|
341
|
+
set$1,
|
|
342
|
+
enable,
|
|
343
|
+
disable
|
|
344
|
+
],
|
|
345
|
+
description: "Manage Stop Gate integration config",
|
|
346
|
+
examples: [
|
|
347
|
+
"alint config integrations stop-gate show",
|
|
348
|
+
"alint config integrations stop-gate enable",
|
|
349
|
+
"alint config integrations stop-gate disable",
|
|
350
|
+
"alint config integrations stop-gate set --target all --timeout-ms 1800000"
|
|
351
|
+
],
|
|
352
|
+
name: "stop-gate"
|
|
353
|
+
});
|
|
354
|
+
async function setActivation(context, enabled) {
|
|
355
|
+
try {
|
|
356
|
+
const result = await setStopGateConfig({
|
|
357
|
+
cwd: context.io.cwd,
|
|
358
|
+
enabled
|
|
359
|
+
});
|
|
360
|
+
context.io.stdout.write(`config: ${result.configFile}\n`);
|
|
361
|
+
writeResolvedConfig((chunk) => context.io.stdout.write(chunk), result.config);
|
|
362
|
+
return 0;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
context.io.stderr.write(`${errorMessageFrom(error) ?? "Failed to write Stop Gate activation."}\n`);
|
|
365
|
+
return 2;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function writeResolvedConfig(write, config) {
|
|
369
|
+
write(`enabled: ${config.enabled}${config.enabled === defaultStopGateConfig.enabled ? " (default)" : ""}\n`);
|
|
370
|
+
write(`target: ${config.target}${config.target === defaultStopGateConfig.target ? " (default)" : ""}\n`);
|
|
371
|
+
write(`timeoutMs: ${config.timeoutMs}${config.timeoutMs === defaultStopGateConfig.timeoutMs ? " (default)" : ""}\n`);
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/cli/commands/config/integrations/integrations.ts
|
|
375
|
+
const integrations$1 = defineCommand({
|
|
376
|
+
children: [stopGate$1],
|
|
377
|
+
description: "Manage external integration configuration",
|
|
378
|
+
name: "integrations"
|
|
379
|
+
});
|
|
380
|
+
//#endregion
|
|
252
381
|
//#region src/cli/reporters/progress/bar.ts
|
|
253
382
|
const minBarWidth = 4;
|
|
254
383
|
const maxBarWidth = 16;
|
|
@@ -972,7 +1101,7 @@ async function runListModelsCommand(context, options) {
|
|
|
972
1101
|
try {
|
|
973
1102
|
providerConcurrency = resolveProviderBenchmarkConcurrency(config, concurrencyOverrides);
|
|
974
1103
|
} catch (error) {
|
|
975
|
-
context.io.stderr.write(`${errorMessageFrom(error) ?? "Invalid benchmark concurrency override."}\n`);
|
|
1104
|
+
context.io.stderr.write(`${errorMessageFrom$1(error) ?? "Invalid benchmark concurrency override."}\n`);
|
|
976
1105
|
return 2;
|
|
977
1106
|
}
|
|
978
1107
|
const runtime = await startModelAdapters(config, context.io);
|
|
@@ -1016,7 +1145,7 @@ const probe$1 = defineCommand({
|
|
|
1016
1145
|
context.io.stdout.write(`${models.join("\n")}${models.length > 0 ? "\n" : ""}`);
|
|
1017
1146
|
return 0;
|
|
1018
1147
|
} catch (error) {
|
|
1019
|
-
context.io.stderr.write(`failed to probe models: ${errorMessageFrom(error) ?? String(error)}\n`);
|
|
1148
|
+
context.io.stderr.write(`failed to probe models: ${errorMessageFrom$1(error) ?? String(error)}\n`);
|
|
1020
1149
|
return 2;
|
|
1021
1150
|
}
|
|
1022
1151
|
},
|
|
@@ -1153,7 +1282,7 @@ function applyPlans(config, plans) {
|
|
|
1153
1282
|
return plans.reduce((nextConfig, plan) => pruneProviderModels(nextConfig, plan.provider.id, plan.remoteModelIds), config);
|
|
1154
1283
|
}
|
|
1155
1284
|
function classifyProbeFailure(error) {
|
|
1156
|
-
const message = errorMessageFrom(error);
|
|
1285
|
+
const message = errorMessageFrom$1(error);
|
|
1157
1286
|
const httpStatus = message?.match(/^GET .+ returned ([1-5]\d\d)\.$/u)?.[1];
|
|
1158
1287
|
if (httpStatus !== void 0) return `provider returned HTTP ${httpStatus}`;
|
|
1159
1288
|
if (error instanceof TypeError && message === "Expected OpenAI-compatible models response with data array.") return "provider returned an invalid models response";
|
|
@@ -1363,7 +1492,7 @@ const probe = defineCommand({
|
|
|
1363
1492
|
context.io.stdout.write(`endpoint: ${options.endpoint}\nmodels: ${models.length}\n`);
|
|
1364
1493
|
return 0;
|
|
1365
1494
|
} catch (error) {
|
|
1366
|
-
context.io.stderr.write(`failed to probe provider: ${errorMessageFrom(error) ?? String(error)}\n`);
|
|
1495
|
+
context.io.stderr.write(`failed to probe provider: ${errorMessageFrom$1(error) ?? String(error)}\n`);
|
|
1367
1496
|
return 2;
|
|
1368
1497
|
}
|
|
1369
1498
|
},
|
|
@@ -1781,7 +1910,7 @@ function createProviderEditorPrompts(prompts) {
|
|
|
1781
1910
|
}
|
|
1782
1911
|
function formatProbeModelsFailure(endpoint, error) {
|
|
1783
1912
|
const hint = endpoint.startsWith("https://localhost:11434") ? " Ollama usually uses http://localhost:11434/v1." : "";
|
|
1784
|
-
return `Could not probe models: ${errorMessageFrom(error)}.${hint}`;
|
|
1913
|
+
return `Could not probe models: ${errorMessageFrom$1(error)}.${hint}`;
|
|
1785
1914
|
}
|
|
1786
1915
|
function isBackInput(value) {
|
|
1787
1916
|
return value.trim() === "..";
|
|
@@ -2163,7 +2292,7 @@ async function runNonInteractiveUpdate(context, options, providerEndpoint, exist
|
|
|
2163
2292
|
try {
|
|
2164
2293
|
parsedHeaders = parseHeaderList(toArray$1(options.providerHeader));
|
|
2165
2294
|
} catch (error) {
|
|
2166
|
-
context.io.stderr.write(`${errorMessageFrom(error) ?? "Invalid provider header."}\n`);
|
|
2295
|
+
context.io.stderr.write(`${errorMessageFrom$1(error) ?? "Invalid provider header."}\n`);
|
|
2167
2296
|
return 2;
|
|
2168
2297
|
}
|
|
2169
2298
|
const draft = {
|
|
@@ -2175,7 +2304,7 @@ async function runNonInteractiveUpdate(context, options, providerEndpoint, exist
|
|
|
2175
2304
|
try {
|
|
2176
2305
|
remoteModelIds = await probeModels(endpoint, draft.headers);
|
|
2177
2306
|
} catch (error) {
|
|
2178
|
-
context.io.stderr.write(`failed to probe provider: ${errorMessageFrom(error) ?? "Unknown error."}\n`);
|
|
2307
|
+
context.io.stderr.write(`failed to probe provider: ${errorMessageFrom$1(error) ?? "Unknown error."}\n`);
|
|
2179
2308
|
return 2;
|
|
2180
2309
|
}
|
|
2181
2310
|
const replaced = replaceSetupProvider(config, draft);
|
|
@@ -2208,7 +2337,7 @@ async function runUpdateProviderCommand(context, options) {
|
|
|
2208
2337
|
providerId = scalarOption(options.provider, "--provider");
|
|
2209
2338
|
providerEndpoint = scalarOption(options.providerEndpoint, "--provider-endpoint");
|
|
2210
2339
|
} catch (error) {
|
|
2211
|
-
context.io.stderr.write(`${errorMessageFrom(error) ?? "Invalid provider update option."}\n`);
|
|
2340
|
+
context.io.stderr.write(`${errorMessageFrom$1(error) ?? "Invalid provider update option."}\n`);
|
|
2212
2341
|
return 2;
|
|
2213
2342
|
}
|
|
2214
2343
|
if (providerId === void 0) {
|
|
@@ -2256,6 +2385,7 @@ function toArray$1(value) {
|
|
|
2256
2385
|
const config = defineCommand({
|
|
2257
2386
|
children: [
|
|
2258
2387
|
inspect$2,
|
|
2388
|
+
integrations$1,
|
|
2259
2389
|
models,
|
|
2260
2390
|
defineCommand({
|
|
2261
2391
|
children: [
|
|
@@ -2318,1377 +2448,1925 @@ const config = defineCommand({
|
|
|
2318
2448
|
name: "config"
|
|
2319
2449
|
});
|
|
2320
2450
|
//#endregion
|
|
2321
|
-
//#region src/cli/
|
|
2322
|
-
|
|
2323
|
-
|
|
2451
|
+
//#region src/cli/git.ts
|
|
2452
|
+
const gitTimeoutMs = 6e4;
|
|
2453
|
+
const gitlinkMode = "160000";
|
|
2454
|
+
/**
|
|
2455
|
+
* Collects staged, unstaged, and untracked changes relative to HEAD.
|
|
2456
|
+
* Deleted files and Git-controlled submodule worktrees stay outside the lint boundary.
|
|
2457
|
+
*/
|
|
2458
|
+
async function findDirtyChanges(cwd) {
|
|
2459
|
+
const [tracked, untracked, patch, stagedEntries] = await Promise.all([
|
|
2460
|
+
runGit(cwd, [
|
|
2461
|
+
"diff",
|
|
2462
|
+
"--name-only",
|
|
2463
|
+
"-z",
|
|
2464
|
+
"--diff-filter=ACMRTUXB",
|
|
2465
|
+
"--find-renames",
|
|
2466
|
+
"--ignore-submodules=none",
|
|
2467
|
+
"HEAD",
|
|
2468
|
+
"--"
|
|
2469
|
+
]),
|
|
2470
|
+
runGit(cwd, [
|
|
2471
|
+
"ls-files",
|
|
2472
|
+
"--others",
|
|
2473
|
+
"--exclude-standard",
|
|
2474
|
+
"-z"
|
|
2475
|
+
]),
|
|
2476
|
+
runGit(cwd, [
|
|
2477
|
+
"diff",
|
|
2478
|
+
"--no-color",
|
|
2479
|
+
"--no-ext-diff",
|
|
2480
|
+
"--unified=0",
|
|
2481
|
+
"--diff-filter=ACMRTUXB",
|
|
2482
|
+
"--find-renames",
|
|
2483
|
+
"--ignore-submodules=none",
|
|
2484
|
+
"--submodule=short",
|
|
2485
|
+
"HEAD",
|
|
2486
|
+
"--"
|
|
2487
|
+
]),
|
|
2488
|
+
runGit(cwd, [
|
|
2489
|
+
"ls-files",
|
|
2490
|
+
"--stage",
|
|
2491
|
+
"-z"
|
|
2492
|
+
])
|
|
2493
|
+
]);
|
|
2494
|
+
const gitlinks = parseGitlinks(stagedEntries);
|
|
2495
|
+
const trackedFiles = withoutGitlinks(parseNullSeparatedPaths(tracked), gitlinks);
|
|
2496
|
+
const untrackedFiles = withoutGitlinks(parseNullSeparatedPaths(untracked), gitlinks);
|
|
2497
|
+
const files = [.../* @__PURE__ */ new Set([...trackedFiles, ...untrackedFiles])].sort();
|
|
2498
|
+
const changedLines = changedLinesFromPatch(patch, gitlinks);
|
|
2499
|
+
await Promise.all(untrackedFiles.map(async (file) => {
|
|
2500
|
+
const content = await readFile(resolve(cwd, file), "utf8");
|
|
2501
|
+
const lineCount = content.length === 0 ? 0 : content.split("\n").length - (content.endsWith("\n") ? 1 : 0);
|
|
2502
|
+
if (lineCount > 0) changedLines.set(file, [{
|
|
2503
|
+
endLine: lineCount,
|
|
2504
|
+
startLine: 1
|
|
2505
|
+
}]);
|
|
2506
|
+
}));
|
|
2507
|
+
return {
|
|
2508
|
+
changedLines,
|
|
2509
|
+
files
|
|
2510
|
+
};
|
|
2324
2511
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
const
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2512
|
+
async function findGitRoot(cwd) {
|
|
2513
|
+
const root = (await runGit(cwd, ["rev-parse", "--show-toplevel"])).trim();
|
|
2514
|
+
if (root.length === 0) throw new Error("Git did not return a repository root.");
|
|
2515
|
+
return root;
|
|
2516
|
+
}
|
|
2517
|
+
function changedLinesFromPatch(patch, gitlinks) {
|
|
2518
|
+
const changedLines = /* @__PURE__ */ new Map();
|
|
2519
|
+
for (const file of parsePatch(patch)) {
|
|
2520
|
+
const path = file.newFileName === void 0 || file.newFileName === "/dev/null" ? void 0 : file.newFileName.startsWith("b/") ? file.newFileName.slice(2) : file.newFileName;
|
|
2521
|
+
if (path === void 0 || isWithinGitlink(path, gitlinks)) continue;
|
|
2522
|
+
const lines = [];
|
|
2523
|
+
for (const hunk of file.hunks) {
|
|
2524
|
+
let newLine = hunk.newStart;
|
|
2525
|
+
for (const line of hunk.lines) if (line.startsWith("+")) {
|
|
2526
|
+
lines.push(newLine);
|
|
2527
|
+
newLine += 1;
|
|
2528
|
+
} else if (!line.startsWith("-") && !line.startsWith("\\")) newLine += 1;
|
|
2339
2529
|
}
|
|
2340
|
-
|
|
2530
|
+
const ranges = lineRanges(lines);
|
|
2531
|
+
if (ranges.length > 0) changedLines.set(path, ranges);
|
|
2341
2532
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2533
|
+
return changedLines;
|
|
2534
|
+
}
|
|
2535
|
+
function isWithinGitlink(path, gitlinks) {
|
|
2536
|
+
for (const gitlink of gitlinks) if (path === gitlink || path.startsWith(`${gitlink}/`)) return true;
|
|
2537
|
+
return false;
|
|
2538
|
+
}
|
|
2539
|
+
function lineRanges(lines) {
|
|
2540
|
+
const ranges = [];
|
|
2541
|
+
for (const line of lines) {
|
|
2542
|
+
const previous = ranges.at(-1);
|
|
2543
|
+
if (previous && line === previous.endLine + 1) previous.endLine = line;
|
|
2544
|
+
else ranges.push({
|
|
2545
|
+
endLine: line,
|
|
2546
|
+
startLine: line
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
return ranges;
|
|
2550
|
+
}
|
|
2551
|
+
function parseGitlinks(output) {
|
|
2552
|
+
const gitlinks = /* @__PURE__ */ new Set();
|
|
2553
|
+
for (const entry of parseNullSeparatedPaths(output)) {
|
|
2554
|
+
const separator = entry.indexOf(" ");
|
|
2555
|
+
if (separator === -1 || !entry.startsWith(`${gitlinkMode} `)) continue;
|
|
2556
|
+
gitlinks.add(entry.slice(separator + 1));
|
|
2557
|
+
}
|
|
2558
|
+
return gitlinks;
|
|
2559
|
+
}
|
|
2560
|
+
function parseNullSeparatedPaths(output) {
|
|
2561
|
+
return output.split("\0").filter((path) => path.length > 0);
|
|
2562
|
+
}
|
|
2563
|
+
async function runGit(cwd, args) {
|
|
2564
|
+
return (await x("git", args, {
|
|
2565
|
+
nodeOptions: { cwd },
|
|
2566
|
+
nodePath: false,
|
|
2567
|
+
throwOnError: true,
|
|
2568
|
+
timeout: gitTimeoutMs
|
|
2569
|
+
})).stdout;
|
|
2570
|
+
}
|
|
2571
|
+
function withoutGitlinks(paths, gitlinks) {
|
|
2572
|
+
return paths.filter((path) => !isWithinGitlink(path, gitlinks));
|
|
2573
|
+
}
|
|
2574
|
+
//#endregion
|
|
2575
|
+
//#region src/cli/commands/integrations/stop-gate/envelope.ts
|
|
2576
|
+
function writeEnvelope(write, envelope) {
|
|
2577
|
+
write(`${JSON.stringify(envelope)}\n`);
|
|
2578
|
+
}
|
|
2579
|
+
//#endregion
|
|
2580
|
+
//#region src/cli/commands/integrations/stop-gate/findings.ts
|
|
2581
|
+
function fingerprintDiagnostics(diagnostics) {
|
|
2582
|
+
const diagnosticHashes = diagnostics.map((diagnostic) => stableHash({
|
|
2583
|
+
evidence: diagnostic.evidence,
|
|
2584
|
+
filePath: diagnostic.filePath,
|
|
2585
|
+
loc: diagnostic.loc,
|
|
2586
|
+
message: diagnostic.message,
|
|
2587
|
+
ruleId: diagnostic.ruleId,
|
|
2588
|
+
severity: diagnostic.severity
|
|
2589
|
+
})).sort();
|
|
2590
|
+
return stableHash({
|
|
2591
|
+
diagnosticHashes,
|
|
2592
|
+
schemaVersion: 1
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
//#endregion
|
|
2596
|
+
//#region src/cli/commands/integrations/stop-gate/report.ts
|
|
2597
|
+
const reportFileName = "report.json";
|
|
2598
|
+
const reportLimitBytes = 104857600;
|
|
2599
|
+
const stopGateTempRoot = join$1(tmpdir(), "alint-stop-gate");
|
|
2600
|
+
var StopGateReportTooLargeError = class extends Error {
|
|
2601
|
+
constructor() {
|
|
2602
|
+
super("Stop Gate report exceeds the 100 MB limit.");
|
|
2603
|
+
this.name = "StopGateReportTooLargeError";
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2606
|
+
async function removeStopGateReport(sessionId) {
|
|
2607
|
+
await rm(getReportPath(sessionId), { force: true });
|
|
2608
|
+
}
|
|
2609
|
+
async function writeStopGateReport(sessionId, report) {
|
|
2610
|
+
const content = `${JSON.stringify(report, null, 2)}\n`;
|
|
2611
|
+
if (Buffer.byteLength(content) > reportLimitBytes) throw new StopGateReportTooLargeError();
|
|
2612
|
+
const sessionDirectory = getSessionDirectory(sessionId);
|
|
2613
|
+
const reportPath = getReportPath(sessionId);
|
|
2614
|
+
const tempPath = join$1(sessionDirectory, `report-${randomUUID()}.tmp`);
|
|
2615
|
+
await mkdir(sessionDirectory, { recursive: true });
|
|
2616
|
+
await writeFile(tempPath, content, {
|
|
2617
|
+
encoding: "utf8",
|
|
2618
|
+
mode: 420
|
|
2619
|
+
});
|
|
2620
|
+
await chmod(tempPath, 420);
|
|
2621
|
+
await rename(tempPath, reportPath);
|
|
2622
|
+
await enforceReportBudget(reportPath);
|
|
2623
|
+
return reportPath;
|
|
2624
|
+
}
|
|
2625
|
+
async function enforceReportBudget(currentReportPath) {
|
|
2626
|
+
let entries;
|
|
2627
|
+
try {
|
|
2628
|
+
entries = await readdir(stopGateTempRoot, { withFileTypes: true });
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
if (isNodeErrorCode(error, "ENOENT")) return;
|
|
2631
|
+
throw error;
|
|
2632
|
+
}
|
|
2633
|
+
const reports = [];
|
|
2634
|
+
for (const entry of entries) {
|
|
2635
|
+
if (!entry.isDirectory()) continue;
|
|
2636
|
+
const path = join$1(stopGateTempRoot, entry.name, reportFileName);
|
|
2637
|
+
try {
|
|
2638
|
+
const stats = await stat(path);
|
|
2639
|
+
reports.push({
|
|
2640
|
+
mtimeMs: stats.mtimeMs,
|
|
2641
|
+
path,
|
|
2642
|
+
size: stats.size
|
|
2643
|
+
});
|
|
2644
|
+
} catch (error) {
|
|
2645
|
+
if (!isNodeErrorCode(error, "ENOENT")) throw error;
|
|
2350
2646
|
}
|
|
2351
|
-
lines.push("");
|
|
2352
2647
|
}
|
|
2353
|
-
|
|
2354
|
-
|
|
2648
|
+
let total = reports.reduce((sum, report) => sum + report.size, 0);
|
|
2649
|
+
for (const report of reports.sort((left, right) => left.mtimeMs - right.mtimeMs)) {
|
|
2650
|
+
if (total <= reportLimitBytes) break;
|
|
2651
|
+
if (report.path === currentReportPath) continue;
|
|
2652
|
+
await rm(report.path, { force: true });
|
|
2653
|
+
total -= report.size;
|
|
2654
|
+
}
|
|
2355
2655
|
}
|
|
2356
|
-
function
|
|
2357
|
-
return
|
|
2656
|
+
function getReportPath(sessionId) {
|
|
2657
|
+
return join$1(getSessionDirectory(sessionId), reportFileName);
|
|
2358
2658
|
}
|
|
2359
|
-
function
|
|
2360
|
-
|
|
2659
|
+
function getSessionDirectory(sessionId) {
|
|
2660
|
+
if (sessionId === "." || sessionId === ".." || !/^[\w.-]+$/u.test(sessionId)) throw new Error("Invalid Stop hook session id.");
|
|
2661
|
+
return join$1(stopGateTempRoot, sessionId);
|
|
2361
2662
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2663
|
+
//#endregion
|
|
2664
|
+
//#region src/cli/commands/lint/discovery.ts
|
|
2665
|
+
const minimatchOptions = { dot: true };
|
|
2666
|
+
const directoryBufferSize = 256;
|
|
2667
|
+
var NoFilesFoundError = class extends Error {
|
|
2668
|
+
globInputPaths;
|
|
2669
|
+
pattern;
|
|
2670
|
+
constructor(pattern, options) {
|
|
2671
|
+
super(`No files matching "${pattern}" were found${options.globInputPaths ? "" : " (glob input is disabled)"}.`);
|
|
2672
|
+
this.name = "NoFilesFoundError";
|
|
2673
|
+
this.globInputPaths = options.globInputPaths;
|
|
2674
|
+
this.pattern = pattern;
|
|
2675
|
+
}
|
|
2676
|
+
};
|
|
2677
|
+
async function findDirtyLintTargets(config, cwd) {
|
|
2678
|
+
const changes = await findDirtyChanges(cwd);
|
|
2679
|
+
if (changes.files.length === 0) return {
|
|
2680
|
+
changedLines: changes.changedLines,
|
|
2681
|
+
directories: [],
|
|
2682
|
+
files: []
|
|
2370
2683
|
};
|
|
2371
2684
|
return {
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2685
|
+
...await findLintTargets({
|
|
2686
|
+
config,
|
|
2687
|
+
cwd,
|
|
2688
|
+
errorOnUnmatchedPattern: false,
|
|
2689
|
+
globInputPaths: false,
|
|
2690
|
+
inputs: changes.files
|
|
2691
|
+
}),
|
|
2692
|
+
changedLines: changes.changedLines
|
|
2378
2693
|
};
|
|
2379
2694
|
}
|
|
2380
|
-
function
|
|
2381
|
-
|
|
2695
|
+
async function findLintTargets(options) {
|
|
2696
|
+
const { config, cwd, errorOnUnmatchedPattern = true, globInputPaths = true, inputs } = options;
|
|
2697
|
+
const gitignore = shouldFilterGitignoredFiles(config) ? new Gitignore() : void 0;
|
|
2698
|
+
const targets = await resolveInputFiles({
|
|
2699
|
+
config,
|
|
2700
|
+
cwd,
|
|
2701
|
+
errorOnUnmatchedPattern,
|
|
2702
|
+
gitignore,
|
|
2703
|
+
globInputPaths,
|
|
2704
|
+
inputs: inputs.length > 0 ? inputs : ["."]
|
|
2705
|
+
});
|
|
2706
|
+
if (!gitignore || targets.files.length === 0) return targets;
|
|
2707
|
+
const lintFiles = [];
|
|
2708
|
+
for (const file of targets.files) {
|
|
2709
|
+
if (await gitignore.ignores(resolve(cwd, file))) continue;
|
|
2710
|
+
lintFiles.push(file);
|
|
2711
|
+
}
|
|
2712
|
+
return {
|
|
2713
|
+
directories: targets.directories,
|
|
2714
|
+
files: lintFiles
|
|
2715
|
+
};
|
|
2382
2716
|
}
|
|
2383
|
-
function
|
|
2384
|
-
|
|
2385
|
-
return `${execution.cached}/${execution.planned} cached`;
|
|
2717
|
+
function collectGlobalIgnorePatterns(config) {
|
|
2718
|
+
return normalizeConfig(config).flatMap((item) => isGlobalIgnoreItem(item) ? [...item.ignores] : []);
|
|
2386
2719
|
}
|
|
2387
|
-
function
|
|
2388
|
-
const
|
|
2389
|
-
const
|
|
2390
|
-
const
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
return [
|
|
2397
|
-
problemSummary,
|
|
2398
|
-
style.summaryToken(tokens),
|
|
2399
|
-
execution
|
|
2400
|
-
].filter((segment) => segment !== void 0).join(" | ");
|
|
2720
|
+
function getGlobParent(input) {
|
|
2721
|
+
const segments = input.replaceAll("\\", "/").split("/");
|
|
2722
|
+
const parentSegments = [];
|
|
2723
|
+
for (const segment of segments) {
|
|
2724
|
+
if (isGlobPattern(segment)) break;
|
|
2725
|
+
parentSegments.push(segment);
|
|
2726
|
+
}
|
|
2727
|
+
const parent = parentSegments.join("/");
|
|
2728
|
+
return parent === "" ? "." : parent;
|
|
2401
2729
|
}
|
|
2402
|
-
function
|
|
2403
|
-
const
|
|
2404
|
-
|
|
2405
|
-
return `${liveTokens} tokens${cachedTokens > 0 ? ` (${cachedTokens.toLocaleString("en-US")} cached)` : ""}`;
|
|
2730
|
+
function isGlobalIgnoreItem(item) {
|
|
2731
|
+
const keys = Object.keys(item).filter((key) => item[key] !== void 0);
|
|
2732
|
+
return item.ignores !== void 0 && keys.every((key) => key === "ignores" || key === "name");
|
|
2406
2733
|
}
|
|
2407
|
-
function
|
|
2408
|
-
return
|
|
2734
|
+
function isGlobPattern(input) {
|
|
2735
|
+
return new Minimatch(input, minimatchOptions).hasMagic();
|
|
2409
2736
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
function formatDiagnostics(format, result, options = {}) {
|
|
2413
|
-
if (format === "json") return formatJson(result);
|
|
2414
|
-
if (format === "stylish") return formatStylish(result, { color: options.color });
|
|
2415
|
-
throw new Error(`Unknown reporter "${format}".`);
|
|
2737
|
+
function matchesGlob(filePath, pattern) {
|
|
2738
|
+
return minimatch(filePath, pattern.replaceAll("\\", "/"), minimatchOptions);
|
|
2416
2739
|
}
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
function createPlainProgressReporter(options) {
|
|
2420
|
-
const writeLine = (line) => options.write(`${line}\n`);
|
|
2421
|
-
return {
|
|
2422
|
-
onJobRetry: () => {},
|
|
2423
|
-
onJobStart: (payload) => {
|
|
2424
|
-
const target = payload.job.target.name ? `${payload.job.target.kind} ${payload.job.target.name}` : payload.job.target.kind;
|
|
2425
|
-
writeLine(`scan ${payload.job.inputPath} > ${target} > ${payload.job.ruleId}`);
|
|
2426
|
-
},
|
|
2427
|
-
onPrepareEnd: ({ filesTotal }) => writeLine(`alint prepared: ${filesTotal} files`),
|
|
2428
|
-
onPrepareStart: () => writeLine("alint preparing"),
|
|
2429
|
-
onRunEnd: (payload) => {
|
|
2430
|
-
const warnCount = countDiagnostics(payload.diagnostics, "warn");
|
|
2431
|
-
const errorCount = countDiagnostics(payload.diagnostics, "error");
|
|
2432
|
-
const state = payload.execution.failed > 0 ? "failed" : "finished";
|
|
2433
|
-
const { cached, cancelled, completed, failed, skipped } = payload.execution;
|
|
2434
|
-
writeLine(`alint ${state}: ${warnCount} warn, ${errorCount} error, ${payload.usage.totalTokens} tokens, ${completed} completed, ${cached} cached, ${failed} failed, ${cancelled} cancelled, ${skipped} skipped`);
|
|
2435
|
-
}
|
|
2436
|
-
};
|
|
2740
|
+
function matchesIgnoredDirectory(relativePath, patterns) {
|
|
2741
|
+
return patterns.some((pattern) => minimatch(relativePath, pattern, minimatchOptions) || minimatch(`${relativePath}/`, pattern, minimatchOptions) || minimatch(`${relativePath}/__alint__`, pattern, minimatchOptions));
|
|
2437
2742
|
}
|
|
2438
|
-
function
|
|
2439
|
-
return
|
|
2743
|
+
function normalizeGlobPattern(cwd, pattern) {
|
|
2744
|
+
return isAbsolute(pattern) ? normalizeRelativePath(cwd, pattern) : pattern.replaceAll("\\", "/");
|
|
2440
2745
|
}
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
const
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
const
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
state.totalTokens = Math.max(state.totalTokens - cachedTokens, 0);
|
|
2473
|
-
state.cachedTokens += cachedTokens;
|
|
2746
|
+
function normalizeRelativePath(cwd, filePath) {
|
|
2747
|
+
return relative(cwd, filePath).replaceAll("\\", "/");
|
|
2748
|
+
}
|
|
2749
|
+
async function resolveInputFiles(options) {
|
|
2750
|
+
const hasFilePatterns = hasDiscoveryFilePatterns(options.config);
|
|
2751
|
+
const ignoredPatterns = collectGlobalIgnorePatterns(options.config);
|
|
2752
|
+
const candidates = [];
|
|
2753
|
+
const directories = [];
|
|
2754
|
+
for (const input of options.inputs) {
|
|
2755
|
+
const path = resolve(options.cwd, input);
|
|
2756
|
+
const stats = await statPath(path);
|
|
2757
|
+
if (stats?.isFile()) {
|
|
2758
|
+
candidates.push(input);
|
|
2759
|
+
continue;
|
|
2760
|
+
}
|
|
2761
|
+
if (stats?.isDirectory()) {
|
|
2762
|
+
if (await shouldPruneDirectory(path, {
|
|
2763
|
+
cwd: options.cwd,
|
|
2764
|
+
gitignore: options.gitignore,
|
|
2765
|
+
ignoredPatterns
|
|
2766
|
+
})) continue;
|
|
2767
|
+
directories.push(input);
|
|
2768
|
+
if (!hasFilePatterns) continue;
|
|
2769
|
+
const directoryFiles = [];
|
|
2770
|
+
for await (const file of walkFiles(path, {
|
|
2771
|
+
cwd: options.cwd,
|
|
2772
|
+
gitignore: options.gitignore,
|
|
2773
|
+
ignoredPatterns
|
|
2774
|
+
})) {
|
|
2775
|
+
const relativePath = normalizeRelativePath(options.cwd, file);
|
|
2776
|
+
if (hasFilePatterns && matchesDiscoveryFile(relativePath, options.config, { cwd: options.cwd })) directoryFiles.push(relativePath);
|
|
2474
2777
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
});
|
|
2487
|
-
},
|
|
2488
|
-
onJobRetry: (payload) => {
|
|
2489
|
-
updateProgress(state, payload.progress);
|
|
2490
|
-
const current = state.activeJobs.get(payload.job.id);
|
|
2491
|
-
state.activeJobs.set(payload.job.id, {
|
|
2492
|
-
job: payload.job,
|
|
2493
|
-
retry: {
|
|
2494
|
-
attempt: payload.attempt,
|
|
2495
|
-
maxAttempts: payload.maxAttempts,
|
|
2496
|
-
startedAt: payload.startedAt
|
|
2497
|
-
},
|
|
2498
|
-
startedAt: current?.startedAt ?? payload.startedAt ?? now(),
|
|
2499
|
-
state: current?.state ?? "running"
|
|
2500
|
-
});
|
|
2501
|
-
},
|
|
2502
|
-
onJobStart: (payload) => {
|
|
2503
|
-
updateProgress(state, payload.progress);
|
|
2504
|
-
state.activeJobs.set(payload.job.id, {
|
|
2505
|
-
job: payload.job,
|
|
2506
|
-
startedAt: payload.startedAt ?? now(),
|
|
2507
|
-
state: "running"
|
|
2778
|
+
for (const file of directoryFiles.sort()) candidates.push(file);
|
|
2779
|
+
continue;
|
|
2780
|
+
}
|
|
2781
|
+
if (options.globInputPaths && isGlobPattern(input)) {
|
|
2782
|
+
const matches = await searchGlob({
|
|
2783
|
+
config: options.config,
|
|
2784
|
+
cwd: options.cwd,
|
|
2785
|
+
gitignore: options.gitignore,
|
|
2786
|
+
hasFilePatterns,
|
|
2787
|
+
ignoredPatterns,
|
|
2788
|
+
pattern: input
|
|
2508
2789
|
});
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
state.activeJobs.clear();
|
|
2513
|
-
state.errorCount = 0;
|
|
2514
|
-
state.execution = createCounts();
|
|
2515
|
-
state.progress = createProgress();
|
|
2516
|
-
state.ruleGroups.clear();
|
|
2517
|
-
state.runStartedAt = startedAt ?? now();
|
|
2518
|
-
state.animationTick = 0;
|
|
2519
|
-
state.cachedTokens = 0;
|
|
2520
|
-
state.jobTokens.clear();
|
|
2521
|
-
state.totalTokens = 0;
|
|
2522
|
-
state.warnCount = 0;
|
|
2523
|
-
},
|
|
2524
|
-
onRunEnd: (payload) => {
|
|
2525
|
-
state.warnCount = payload.diagnostics.filter((diagnostic) => diagnostic.severity === "warn").length;
|
|
2526
|
-
state.errorCount = payload.diagnostics.length - state.warnCount;
|
|
2527
|
-
updateProgress(state, payload.progress);
|
|
2528
|
-
state.totalTokens = payload.usage.totalTokens;
|
|
2529
|
-
state.cachedTokens = payload.usage.cached?.totalTokens ?? 0;
|
|
2530
|
-
state.runStartedAt = payload.startedAt ?? state.runStartedAt;
|
|
2531
|
-
},
|
|
2532
|
-
onUsage: ({ job, progress, record }) => {
|
|
2533
|
-
updateProgress(state, progress);
|
|
2534
|
-
if (record.totalTokens != null && Number.isFinite(record.totalTokens)) {
|
|
2535
|
-
state.jobTokens.set(job.id, (state.jobTokens.get(job.id) ?? 0) + record.totalTokens);
|
|
2536
|
-
state.totalTokens += record.totalTokens;
|
|
2537
|
-
}
|
|
2538
|
-
},
|
|
2539
|
-
/**
|
|
2540
|
-
* Advances the summary animation on each renderer interval.
|
|
2541
|
-
*
|
|
2542
|
-
* Triggering workflow:
|
|
2543
|
-
*
|
|
2544
|
-
* `createTtyProgressRenderer`
|
|
2545
|
-
* -> `createCliProgressReporter` `createInterval` callback
|
|
2546
|
-
* -> `SummaryProgressReporter.tick` (this handler)
|
|
2547
|
-
*
|
|
2548
|
-
* Upstream:
|
|
2549
|
-
* - `createCliProgressReporter` `createInterval` callback registered by `createTtyProgressRenderer`
|
|
2550
|
-
*
|
|
2551
|
-
* Downstream:
|
|
2552
|
-
* - Increments `state.animationTick`, consumed by {@link createRows}, {@link formatSpinnerFrame}, and {@link formatMiniBarSegment}.
|
|
2553
|
-
*/
|
|
2554
|
-
tick: () => {
|
|
2555
|
-
state.animationTick += 1;
|
|
2790
|
+
if (matches.length === 0 && options.errorOnUnmatchedPattern) throw new NoFilesFoundError(input, { globInputPaths: options.globInputPaths });
|
|
2791
|
+
for (const match of matches) candidates.push(match);
|
|
2792
|
+
continue;
|
|
2556
2793
|
}
|
|
2794
|
+
if (options.errorOnUnmatchedPattern) throw new NoFilesFoundError(input, { globInputPaths: options.globInputPaths });
|
|
2795
|
+
}
|
|
2796
|
+
return {
|
|
2797
|
+
directories: [...new Set(directories)],
|
|
2798
|
+
files: [...new Set(candidates)]
|
|
2557
2799
|
};
|
|
2558
2800
|
}
|
|
2559
|
-
function
|
|
2560
|
-
const
|
|
2561
|
-
const
|
|
2562
|
-
|
|
2563
|
-
if (
|
|
2564
|
-
|
|
2801
|
+
async function searchGlob(options) {
|
|
2802
|
+
const root = resolve(options.cwd, getGlobParent(options.pattern));
|
|
2803
|
+
const rootStats = await statPath(root);
|
|
2804
|
+
const pattern = normalizeGlobPattern(options.cwd, options.pattern);
|
|
2805
|
+
if (!rootStats?.isDirectory()) return [];
|
|
2806
|
+
const candidates = [];
|
|
2807
|
+
for await (const file of walkFiles(root, {
|
|
2808
|
+
cwd: options.cwd,
|
|
2809
|
+
gitignore: options.gitignore,
|
|
2810
|
+
ignoredPatterns: options.ignoredPatterns
|
|
2811
|
+
})) {
|
|
2812
|
+
const relativePath = normalizeRelativePath(options.cwd, file);
|
|
2813
|
+
if (matchesGlob(relativePath, pattern) && shouldIncludeGlobCandidate(relativePath, options.config, {
|
|
2814
|
+
cwd: options.cwd,
|
|
2815
|
+
hasFilePatterns: options.hasFilePatterns
|
|
2816
|
+
})) candidates.push(relativePath);
|
|
2817
|
+
}
|
|
2818
|
+
return candidates.sort();
|
|
2565
2819
|
}
|
|
2566
|
-
function
|
|
2567
|
-
|
|
2568
|
-
const rightStartedAt = right.startedAt ?? Number.POSITIVE_INFINITY;
|
|
2569
|
-
if (leftStartedAt !== rightStartedAt) return leftStartedAt - rightStartedAt;
|
|
2570
|
-
return left.job.index - right.job.index;
|
|
2820
|
+
function shouldFilterGitignoredFiles(config) {
|
|
2821
|
+
return normalizeConfig(config).some((item) => item.ignore?.gitignore === true);
|
|
2571
2822
|
}
|
|
2572
|
-
function
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2823
|
+
function shouldIncludeGlobCandidate(filePath, config, options) {
|
|
2824
|
+
if (options.hasFilePatterns) return matchesDiscoveryFile(filePath, config, { cwd: options.cwd });
|
|
2825
|
+
return !resolveConfigForFile(filePath, config, { cwd: options.cwd }).ignored;
|
|
2826
|
+
}
|
|
2827
|
+
async function shouldPruneDirectory(path, options) {
|
|
2828
|
+
if (matchesIgnoredDirectory(normalizeRelativePath(options.cwd, path), options.ignoredPatterns)) return true;
|
|
2829
|
+
return await options.gitignore?.ignores(`${path}/`) === true;
|
|
2830
|
+
}
|
|
2831
|
+
async function statPath(path) {
|
|
2832
|
+
try {
|
|
2833
|
+
return await stat(path);
|
|
2834
|
+
} catch (error) {
|
|
2835
|
+
if (isNodeErrorCode(error, "ENOENT") || isNodeErrorCode(error, "ENOTDIR")) return;
|
|
2836
|
+
throw error;
|
|
2578
2837
|
}
|
|
2579
|
-
return boundary;
|
|
2580
2838
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2839
|
+
/**
|
|
2840
|
+
* Streams directory entries so unmatched trees never accumulate into recursive child arrays.
|
|
2841
|
+
* The consumer owns result ordering because filesystem iteration order is not stable.
|
|
2842
|
+
*/
|
|
2843
|
+
async function* walkFiles(root, options) {
|
|
2844
|
+
const pendingDirectories = [root];
|
|
2845
|
+
while (pendingDirectories.length > 0) {
|
|
2846
|
+
const currentDirectory = pendingDirectories.pop();
|
|
2847
|
+
if (currentDirectory === void 0) continue;
|
|
2848
|
+
const childDirectories = [];
|
|
2849
|
+
const directory = await opendir(currentDirectory, { bufferSize: directoryBufferSize });
|
|
2850
|
+
for await (const entry of directory) {
|
|
2851
|
+
const path = resolve(currentDirectory, entry.name);
|
|
2852
|
+
if (entry.isDirectory()) {
|
|
2853
|
+
if (!await shouldPruneDirectory(path, options)) childDirectories.push(path);
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
if (entry.isFile()) yield path;
|
|
2857
|
+
}
|
|
2858
|
+
for (const path of childDirectories.reverse()) pendingDirectories.push(path);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
//#endregion
|
|
2862
|
+
//#region src/cli/runtime/runner.ts
|
|
2863
|
+
/**
|
|
2864
|
+
* Merges the setup config runner with the project config runner. No CLI flag is applied.
|
|
2865
|
+
*
|
|
2866
|
+
* `runner.cache` selects the cache file. The lint command and the language server must merge it
|
|
2867
|
+
* the same way, or the editor reads a different file and reports nothing.
|
|
2868
|
+
*/
|
|
2869
|
+
function mergeRunnerConfigs(setupRunner, configRunner) {
|
|
2870
|
+
const runner = {
|
|
2871
|
+
...setupRunner ?? {},
|
|
2872
|
+
...configRunner ?? {},
|
|
2873
|
+
cache: mergeCacheConfig(setupRunner?.cache, configRunner?.cache),
|
|
2874
|
+
ruleConcurrency: configRunner?.ruleConcurrency ?? setupRunner?.ruleConcurrency,
|
|
2875
|
+
stats: mergeStatsConfig(setupRunner?.stats, configRunner?.stats),
|
|
2876
|
+
timeoutMs: configRunner?.timeoutMs ?? setupRunner?.timeoutMs
|
|
2591
2877
|
};
|
|
2878
|
+
return Object.values(runner).some((value) => value !== void 0) ? runner : void 0;
|
|
2592
2879
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2880
|
+
/** The last `runner` block in the flattened config wins, key by key. */
|
|
2881
|
+
function resolveConfigRunner(config) {
|
|
2882
|
+
return normalizeConfig(config).reduce((merged, item) => item.runner ? {
|
|
2883
|
+
...merged,
|
|
2884
|
+
...item.runner
|
|
2885
|
+
} : merged, void 0);
|
|
2886
|
+
}
|
|
2887
|
+
function mergeCacheConfig(setupCache, configCache) {
|
|
2888
|
+
if (configCache === void 0) return setupCache;
|
|
2889
|
+
if (typeof configCache === "boolean") return configCache;
|
|
2890
|
+
if (typeof setupCache === "object") return {
|
|
2891
|
+
...setupCache,
|
|
2892
|
+
...configCache
|
|
2605
2893
|
};
|
|
2894
|
+
return configCache;
|
|
2606
2895
|
}
|
|
2607
|
-
function
|
|
2608
|
-
return
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
jobsStarted: 0,
|
|
2614
|
-
jobsTotal: 0,
|
|
2615
|
-
planningComplete: false
|
|
2896
|
+
function mergeStatsConfig(setupStats, configStats) {
|
|
2897
|
+
if (configStats === void 0) return setupStats;
|
|
2898
|
+
if (typeof configStats === "boolean") return configStats;
|
|
2899
|
+
if (typeof setupStats === "object") return {
|
|
2900
|
+
...setupStats,
|
|
2901
|
+
...configStats
|
|
2616
2902
|
};
|
|
2903
|
+
return configStats;
|
|
2617
2904
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2905
|
+
//#endregion
|
|
2906
|
+
//#region src/cli/runtime/session.ts
|
|
2907
|
+
/**
|
|
2908
|
+
* Loads config, setup config, and model adapters once, then runs any number of times.
|
|
2909
|
+
*
|
|
2910
|
+
* The lint command and the language server both use this function. A second implementation would
|
|
2911
|
+
* resolve config or cwd differently, and the editor would then read a different cache file.
|
|
2912
|
+
*/
|
|
2913
|
+
async function createRunSession(io, options = {}) {
|
|
2914
|
+
const [{ defaultModel, setupConfig: fileSetupConfig }, config] = await Promise.all([loadRunSetupConfig(io), options.config ?? loadAlintConfig(io.cwd, options.configPath)]);
|
|
2915
|
+
const runtime = await (options.startModelAdapters ?? startModelAdapters)(fileSetupConfig, io);
|
|
2916
|
+
const { setupConfig } = runtime;
|
|
2917
|
+
let shutdown;
|
|
2918
|
+
return {
|
|
2919
|
+
config,
|
|
2920
|
+
cwd: io.cwd,
|
|
2921
|
+
defaultModel,
|
|
2922
|
+
run: async (runOptions) => {
|
|
2923
|
+
const targets = runOptions.targets ?? await findLintTargets({
|
|
2924
|
+
config,
|
|
2925
|
+
cwd: io.cwd,
|
|
2926
|
+
errorOnUnmatchedPattern: true,
|
|
2927
|
+
globInputPaths: true,
|
|
2928
|
+
inputs: runOptions.inputs ?? []
|
|
2929
|
+
});
|
|
2930
|
+
return runAlint({
|
|
2931
|
+
cacheOnly: runOptions.cacheOnly,
|
|
2932
|
+
config,
|
|
2933
|
+
cwd: io.cwd,
|
|
2934
|
+
defaultModel,
|
|
2935
|
+
directories: targets.directories,
|
|
2936
|
+
files: targets.files,
|
|
2937
|
+
modelOverride: runOptions.modelOverride,
|
|
2938
|
+
outputLanguage: runOptions.outputLanguage,
|
|
2939
|
+
progress: runOptions.progress,
|
|
2940
|
+
projectTargets: runOptions.projectTargets,
|
|
2941
|
+
runner: runOptions.runner,
|
|
2942
|
+
setupConfig,
|
|
2943
|
+
signal: runOptions.signal
|
|
2944
|
+
});
|
|
2945
|
+
},
|
|
2946
|
+
runner: mergeRunnerConfigs(setupConfig.runner, resolveConfigRunner(config)),
|
|
2947
|
+
setupConfig,
|
|
2948
|
+
shutdown: () => shutdown ??= runtime.shutdown()
|
|
2949
|
+
};
|
|
2630
2950
|
}
|
|
2631
|
-
|
|
2951
|
+
//#endregion
|
|
2952
|
+
//#region src/cli/commands/lint/changed-lines.ts
|
|
2953
|
+
/** Keeps file-wide diagnostics and diagnostics whose locations intersect the current Git change. */
|
|
2954
|
+
function filterResultToChangedLines(result, options) {
|
|
2632
2955
|
return {
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2956
|
+
...result,
|
|
2957
|
+
diagnostics: result.diagnostics.filter((diagnostic) => {
|
|
2958
|
+
if (diagnostic.loc === void 0) return true;
|
|
2959
|
+
const changedLinesPath = (isAbsolute(diagnostic.filePath) ? relative(options.cwd, diagnostic.filePath) : diagnostic.filePath).replaceAll("\\", "/");
|
|
2960
|
+
const ranges = options.changedLines.get(changedLinesPath);
|
|
2961
|
+
if (ranges === void 0) return false;
|
|
2962
|
+
const startLine = diagnostic.loc.start.line;
|
|
2963
|
+
const endLine = diagnostic.loc.end?.line ?? startLine;
|
|
2964
|
+
return ranges.some((range) => startLine <= range.endLine && endLine >= range.startLine);
|
|
2965
|
+
})
|
|
2642
2966
|
};
|
|
2643
2967
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2968
|
+
//#endregion
|
|
2969
|
+
//#region src/cli/commands/lint/runner.ts
|
|
2970
|
+
/**
|
|
2971
|
+
* Applies this invocation's flags on top of the project's resolved runner.
|
|
2972
|
+
*
|
|
2973
|
+
* The setup/config merge itself lives in `runtime/runner.ts` because the language server needs the
|
|
2974
|
+
* same base without any flags; only the overrides below are CLI-shaped.
|
|
2975
|
+
*/
|
|
2976
|
+
function resolveRunnerConfig(baseRunner, options) {
|
|
2977
|
+
const ruleConcurrency = parsePositiveIntegerOption(options.ruleConcurrency, "--rule-concurrency");
|
|
2978
|
+
const timeoutMs = parsePositiveIntegerOption(options.timeoutMs, "--timeout-ms");
|
|
2979
|
+
const runner = {
|
|
2980
|
+
...baseRunner ?? {},
|
|
2981
|
+
cache: resolveCacheOption(baseRunner?.cache, options),
|
|
2982
|
+
ruleConcurrency: ruleConcurrency ?? baseRunner?.ruleConcurrency,
|
|
2983
|
+
stats: options.stats === false ? false : baseRunner?.stats,
|
|
2984
|
+
timeoutMs: timeoutMs ?? baseRunner?.timeoutMs
|
|
2985
|
+
};
|
|
2986
|
+
return Object.values(runner).some((value) => value !== void 0) ? runner : void 0;
|
|
2651
2987
|
}
|
|
2652
|
-
function
|
|
2653
|
-
if (
|
|
2654
|
-
|
|
2988
|
+
function parsePositiveIntegerOption(value, label) {
|
|
2989
|
+
if (value === void 0) return;
|
|
2990
|
+
const parsed = Number(value);
|
|
2991
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${label} must be a positive integer.`);
|
|
2992
|
+
return parsed;
|
|
2655
2993
|
}
|
|
2656
|
-
function
|
|
2657
|
-
if (
|
|
2658
|
-
if (
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
limit
|
|
2664
|
-
});
|
|
2665
|
-
const index = completeGraphemeBoundary(row, result.index);
|
|
2666
|
-
const visible = row.slice(0, index);
|
|
2667
|
-
const fitted = `${visible}${visible.includes("\x1B") ? "\x1B[0m" : ""}${result.ellipsed ? "…" : ""}`;
|
|
2668
|
-
const overflow = fastStringWidth(fitted) - columns;
|
|
2669
|
-
if (overflow <= 0) return fitted;
|
|
2670
|
-
limit -= overflow;
|
|
2671
|
-
}
|
|
2672
|
-
return "";
|
|
2994
|
+
function resolveCacheOption(baseCache, options) {
|
|
2995
|
+
if (options.cache === false) return false;
|
|
2996
|
+
if (options.cacheLocation !== void 0) return typeof baseCache === "object" ? {
|
|
2997
|
+
...baseCache,
|
|
2998
|
+
location: options.cacheLocation
|
|
2999
|
+
} : { location: options.cacheLocation };
|
|
3000
|
+
return baseCache;
|
|
2673
3001
|
}
|
|
2674
|
-
|
|
2675
|
-
|
|
3002
|
+
//#endregion
|
|
3003
|
+
//#region src/cli/stats/ci.ts
|
|
3004
|
+
function isCi(env = process.env) {
|
|
3005
|
+
const ci = env.CI;
|
|
3006
|
+
if (ci !== void 0 && ci !== "" && ci !== "0" && ci !== "false") return true;
|
|
3007
|
+
return Boolean(env.GITHUB_ACTIONS || env.GITLAB_CI || env.CIRCLECI || env.BUILDKITE || env.TF_BUILD);
|
|
2676
3008
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
3009
|
+
//#endregion
|
|
3010
|
+
//#region src/cli/stats/since.ts
|
|
3011
|
+
/**
|
|
3012
|
+
* Parse a `--since` time into an inclusive lower-bound timestamp.
|
|
3013
|
+
*
|
|
3014
|
+
* Supported formats:
|
|
3015
|
+
* - `Nh`/`Nd`/`Nw`/`Nm`/`Ny` — now minus N hours/days/weeks/months/years.
|
|
3016
|
+
* Weeks, months, and years are fixed approximations (7/30/365 days). `m` is
|
|
3017
|
+
* months here, not minutes — a day is the finest window this tool tracks.
|
|
3018
|
+
* - `YYYY-MM`/`YYYY-MM-DD` — start of that UTC month/day.
|
|
3019
|
+
*
|
|
3020
|
+
* Edge cases:
|
|
3021
|
+
* - An empty time means "no lower bound".
|
|
3022
|
+
* - An unrecognized time throws.
|
|
3023
|
+
*/
|
|
3024
|
+
const HOUR_MS = 36e5;
|
|
3025
|
+
const UNIT_MS = {
|
|
3026
|
+
d: 24 * HOUR_MS,
|
|
3027
|
+
h: HOUR_MS,
|
|
3028
|
+
m: 720 * HOUR_MS,
|
|
3029
|
+
w: 168 * HOUR_MS,
|
|
3030
|
+
y: 8760 * HOUR_MS
|
|
3031
|
+
};
|
|
3032
|
+
function parseSince(time, now) {
|
|
3033
|
+
if (time === void 0 || time === "") return;
|
|
3034
|
+
const relative = /^(\d+)([dhmwy])$/u.exec(time);
|
|
3035
|
+
if (relative) return now - Number(relative[1]) * UNIT_MS[relative[2]];
|
|
3036
|
+
const calendar = /^(\d{4})-(\d{2})(?:-(\d{2}))?$/u.exec(time);
|
|
3037
|
+
if (calendar) {
|
|
3038
|
+
const year = Number(calendar[1]);
|
|
3039
|
+
const month = Number(calendar[2]);
|
|
3040
|
+
const day = calendar[3] === void 0 ? 1 : Number(calendar[3]);
|
|
3041
|
+
return Date.UTC(year, month - 1, day);
|
|
2705
3042
|
}
|
|
2706
|
-
|
|
3043
|
+
throw new Error(`Invalid --since "${time}": use e.g. 24h, 7d, 2w, 3m, 1y, 2025-01, or 2025-01-15.`);
|
|
2707
3044
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
3045
|
+
//#endregion
|
|
3046
|
+
//#region src/cli/stats/jsonl.ts
|
|
3047
|
+
const DEFAULT_RETENTION_MONTHS = 12;
|
|
3048
|
+
const STATS_FILE_PATTERN = /^stats-(\d{4})-(\d{2})\.jsonl$/u;
|
|
3049
|
+
const DAY_MS = 864e5;
|
|
3050
|
+
const MONTH_LABELS$1 = [
|
|
3051
|
+
"Jan",
|
|
3052
|
+
"Feb",
|
|
3053
|
+
"Mar",
|
|
3054
|
+
"Apr",
|
|
3055
|
+
"May",
|
|
3056
|
+
"Jun",
|
|
3057
|
+
"Jul",
|
|
3058
|
+
"Aug",
|
|
3059
|
+
"Sep",
|
|
3060
|
+
"Oct",
|
|
3061
|
+
"Nov",
|
|
3062
|
+
"Dec"
|
|
3063
|
+
];
|
|
3064
|
+
function createJsonlStatsStore(options) {
|
|
3065
|
+
const retentionMonths = options.retentionMonths ?? DEFAULT_RETENTION_MONTHS;
|
|
3066
|
+
return {
|
|
3067
|
+
query: (filter) => query(options.dir, filter ?? {}, Date.now),
|
|
3068
|
+
querySeries: (filter) => querySeries(options.dir, filter ?? {}, Date.now),
|
|
3069
|
+
record: (input) => record(options.dir, input, Date.now(), retentionMonths)
|
|
3070
|
+
};
|
|
2715
3071
|
}
|
|
2716
|
-
function
|
|
2717
|
-
const
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
3072
|
+
function accumulate(rows, key, inTok, outTok, totalTok, countRun) {
|
|
3073
|
+
const row = rows.get(key) ?? {
|
|
3074
|
+
inTok: 0,
|
|
3075
|
+
key,
|
|
3076
|
+
outTok: 0,
|
|
3077
|
+
runs: 0,
|
|
3078
|
+
totalTok: 0
|
|
3079
|
+
};
|
|
3080
|
+
row.inTok += inTok;
|
|
3081
|
+
row.outTok += outTok;
|
|
3082
|
+
row.totalTok += totalTok;
|
|
3083
|
+
if (countRun) row.runs += 1;
|
|
3084
|
+
rows.set(key, row);
|
|
3085
|
+
return row;
|
|
2728
3086
|
}
|
|
2729
|
-
function
|
|
2730
|
-
|
|
2731
|
-
return
|
|
2732
|
-
|
|
2733
|
-
planned,
|
|
2734
|
-
tick,
|
|
2735
|
-
width: 10
|
|
2736
|
-
})}`;
|
|
3087
|
+
function bucketLabel(startMs, interval) {
|
|
3088
|
+
const date = new Date(startMs);
|
|
3089
|
+
if (interval === "month") return MONTH_LABELS$1[date.getUTCMonth()];
|
|
3090
|
+
return `${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
|
|
2737
3091
|
}
|
|
2738
|
-
function
|
|
2739
|
-
const
|
|
2740
|
-
|
|
2741
|
-
const
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
const failed = group.failed > 0 ? ` ${group.failed} failed` : "";
|
|
2745
|
-
const eta = estimateEta(group.terminalDurationMs, terminal, group.planned);
|
|
2746
|
-
return fitRow(`${spinner} ${group.ruleId} ${terminal}/${group.planned} ${percent}%${bar} ${eta} ${running} running${failed}`, options.columns);
|
|
3092
|
+
function bucketStart(ts, interval) {
|
|
3093
|
+
const date = new Date(ts);
|
|
3094
|
+
if (interval === "month") return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1);
|
|
3095
|
+
const dayStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
3096
|
+
if (interval === "week") return dayStart - (new Date(dayStart).getUTCDay() + 6) % 7 * DAY_MS;
|
|
3097
|
+
return dayStart;
|
|
2747
3098
|
}
|
|
2748
|
-
function
|
|
2749
|
-
|
|
3099
|
+
function chooseInterval(since, nowMs, timestamps) {
|
|
3100
|
+
if (since !== void 0) return intervalForDuration(nowMs - since);
|
|
3101
|
+
if (timestamps.length < 2) return "day";
|
|
3102
|
+
return intervalForDuration(Math.max(...timestamps) - Math.min(...timestamps));
|
|
2750
3103
|
}
|
|
2751
|
-
function
|
|
2752
|
-
|
|
2753
|
-
const
|
|
2754
|
-
const
|
|
2755
|
-
|
|
3104
|
+
function fillBuckets(buckets, interval) {
|
|
3105
|
+
if (buckets.size === 0) return [];
|
|
3106
|
+
const starts = [...buckets.keys()].sort((left, right) => left - right);
|
|
3107
|
+
const last = starts.at(-1);
|
|
3108
|
+
const filled = [];
|
|
3109
|
+
for (let startMs = starts[0]; startMs <= last; startMs = nextBucketStart(startMs, interval)) filled.push(buckets.get(startMs) ?? {
|
|
3110
|
+
inTok: 0,
|
|
3111
|
+
key: bucketLabel(startMs, interval),
|
|
3112
|
+
outTok: 0,
|
|
3113
|
+
runs: 0,
|
|
3114
|
+
startMs,
|
|
3115
|
+
totalTok: 0
|
|
3116
|
+
});
|
|
3117
|
+
return filled;
|
|
2756
3118
|
}
|
|
2757
|
-
function
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
if (
|
|
2761
|
-
return
|
|
3119
|
+
function intervalForDuration(ms) {
|
|
3120
|
+
const days = ms / DAY_MS;
|
|
3121
|
+
if (days <= 21) return "day";
|
|
3122
|
+
if (days <= 120) return "week";
|
|
3123
|
+
return "month";
|
|
2762
3124
|
}
|
|
2763
|
-
function
|
|
2764
|
-
return
|
|
3125
|
+
function isEnoent(error) {
|
|
3126
|
+
return error instanceof Error && error.code === "ENOENT";
|
|
2765
3127
|
}
|
|
2766
|
-
function
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
...progress,
|
|
2770
|
-
execution: { ...progress.execution }
|
|
2771
|
-
};
|
|
3128
|
+
function monthIndexOf(ts) {
|
|
3129
|
+
const date = new Date(ts);
|
|
3130
|
+
return date.getUTCFullYear() * 12 + date.getUTCMonth();
|
|
2772
3131
|
}
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
const
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
let previousRows = 0;
|
|
2780
|
-
const clearPreviousFrame = () => {
|
|
2781
|
-
if (previousRows === 0) return;
|
|
2782
|
-
let sequence = clearCurrentLine;
|
|
2783
|
-
for (let row = 1; row < previousRows; row += 1) sequence += clearPreviousLine;
|
|
2784
|
-
options.write(sequence);
|
|
2785
|
-
previousRows = 0;
|
|
2786
|
-
};
|
|
2787
|
-
const render = () => {
|
|
2788
|
-
clearPreviousFrame();
|
|
2789
|
-
const rows = options.getRows();
|
|
2790
|
-
if (rows.length === 0) return;
|
|
2791
|
-
options.write(rows.join("\n"));
|
|
2792
|
-
previousRows = rows.length;
|
|
2793
|
-
};
|
|
2794
|
-
const write = (chunk) => {
|
|
2795
|
-
const wasRendering = interval !== void 0;
|
|
2796
|
-
clearPreviousFrame();
|
|
2797
|
-
options.write(chunk);
|
|
2798
|
-
if (wasRendering) render();
|
|
2799
|
-
};
|
|
2800
|
-
return {
|
|
2801
|
-
finish: () => {
|
|
2802
|
-
if (interval) {
|
|
2803
|
-
options.clearInterval(interval);
|
|
2804
|
-
interval = void 0;
|
|
2805
|
-
}
|
|
2806
|
-
clearPreviousFrame();
|
|
2807
|
-
},
|
|
2808
|
-
render,
|
|
2809
|
-
start: () => {
|
|
2810
|
-
if (!interval) {
|
|
2811
|
-
interval = options.createInterval(render, options.intervalMs);
|
|
2812
|
-
if (isUnrefableInterval(interval)) interval.unref();
|
|
2813
|
-
}
|
|
2814
|
-
render();
|
|
2815
|
-
},
|
|
2816
|
-
write
|
|
2817
|
-
};
|
|
3132
|
+
function nextBucketStart(startMs, interval) {
|
|
3133
|
+
if (interval === "month") {
|
|
3134
|
+
const date = new Date(startMs);
|
|
3135
|
+
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1);
|
|
3136
|
+
}
|
|
3137
|
+
return startMs + (interval === "week" ? 7 * DAY_MS : DAY_MS);
|
|
2818
3138
|
}
|
|
2819
|
-
function
|
|
2820
|
-
|
|
2821
|
-
|
|
3139
|
+
function parseRunLine(line) {
|
|
3140
|
+
try {
|
|
3141
|
+
const value = JSON.parse(line);
|
|
3142
|
+
if (typeof value.ts === "number" && typeof value.cwd === "string" && value.usage != null) return value;
|
|
3143
|
+
return;
|
|
3144
|
+
} catch {
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
2822
3147
|
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
reporter: createPlainProgressReporter({ write: options.write }),
|
|
2829
|
-
write: options.write
|
|
2830
|
-
};
|
|
2831
|
-
const summary = createSummaryProgressReporter({
|
|
2832
|
-
color: options.color,
|
|
2833
|
-
columns: options.columns,
|
|
2834
|
-
cwd: options.cwd,
|
|
2835
|
-
rows: options.rows,
|
|
2836
|
-
spinnerFrames: cliSpinners.dots.frames
|
|
2837
|
-
});
|
|
2838
|
-
const renderer = createTtyProgressRenderer({
|
|
2839
|
-
clearInterval: (handle) => globalThis.clearInterval(handle),
|
|
2840
|
-
createInterval: (callback, intervalMs) => globalThis.setInterval(() => {
|
|
2841
|
-
summary.tick();
|
|
2842
|
-
callback();
|
|
2843
|
-
}, intervalMs),
|
|
2844
|
-
getRows: summary.getRows,
|
|
2845
|
-
intervalMs: 120,
|
|
2846
|
-
write: options.write
|
|
2847
|
-
});
|
|
2848
|
-
const reporter = createRenderingProgressReporter(summary, renderer);
|
|
2849
|
-
return {
|
|
2850
|
-
dispose: renderer.finish,
|
|
2851
|
-
reporter,
|
|
2852
|
-
write: renderer.write
|
|
2853
|
-
};
|
|
3148
|
+
async function prune(dir, ts, retentionMonths) {
|
|
3149
|
+
if (retentionMonths <= 0) return;
|
|
3150
|
+
const cutoff = monthIndexOf(ts) - (retentionMonths - 1);
|
|
3151
|
+
const files = await statsFiles(dir);
|
|
3152
|
+
for (const { month, name } of files) if (month < cutoff) await rm(join(dir, name));
|
|
2854
3153
|
}
|
|
2855
|
-
function
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
summary.onJobRetry?.(payload);
|
|
2883
|
-
renderer.render();
|
|
2884
|
-
},
|
|
2885
|
-
onJobStart: (payload) => {
|
|
2886
|
-
summary.onJobStart?.(payload);
|
|
2887
|
-
renderer.render();
|
|
2888
|
-
},
|
|
2889
|
-
onPrepareEnd: (payload) => {
|
|
2890
|
-
summary.onPrepareEnd?.(payload);
|
|
2891
|
-
renderer.render();
|
|
2892
|
-
},
|
|
2893
|
-
onPrepareStart: (payload) => {
|
|
2894
|
-
summary.onPrepareStart?.(payload);
|
|
2895
|
-
renderer.start();
|
|
2896
|
-
},
|
|
2897
|
-
onRunEnd: (payload) => {
|
|
2898
|
-
summary.onRunEnd?.(payload);
|
|
2899
|
-
renderer.render();
|
|
2900
|
-
},
|
|
2901
|
-
onUsage: (payload) => {
|
|
2902
|
-
summary.onUsage?.(payload);
|
|
2903
|
-
renderer.render();
|
|
3154
|
+
async function query(dir, filter, now) {
|
|
3155
|
+
const dimension = filter.by ?? "model";
|
|
3156
|
+
const since = parseSince(filter.since, now());
|
|
3157
|
+
const rules = ruleSet(filter.rules);
|
|
3158
|
+
const runs = await readRuns(dir);
|
|
3159
|
+
const rows = /* @__PURE__ */ new Map();
|
|
3160
|
+
let totalRuns = 0;
|
|
3161
|
+
let totalIn = 0;
|
|
3162
|
+
let totalOut = 0;
|
|
3163
|
+
let totalTok = 0;
|
|
3164
|
+
for (const run of runs) {
|
|
3165
|
+
if (!runInWindow(run, since, filter.cwd)) continue;
|
|
3166
|
+
const slice = sliceRun(run, rules);
|
|
3167
|
+
if (!slice.touched) continue;
|
|
3168
|
+
totalRuns += 1;
|
|
3169
|
+
totalIn += slice.inTok;
|
|
3170
|
+
totalOut += slice.outTok;
|
|
3171
|
+
totalTok += slice.totalTok;
|
|
3172
|
+
if (dimension === "dir") {
|
|
3173
|
+
accumulate(rows, run.cwd, slice.inTok, slice.outTok, slice.totalTok, true);
|
|
3174
|
+
continue;
|
|
3175
|
+
}
|
|
3176
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
3177
|
+
for (const usageRecord of slice.usageRecords) {
|
|
3178
|
+
const key = recordKey(usageRecord, dimension);
|
|
3179
|
+
accumulate(rows, key, usageRecord.inTok, usageRecord.outTok, usageRecord.totalTok, !seenKeys.has(key));
|
|
3180
|
+
seenKeys.add(key);
|
|
2904
3181
|
}
|
|
3182
|
+
}
|
|
3183
|
+
return {
|
|
3184
|
+
dimension,
|
|
3185
|
+
rows: [...rows.values()].sort((left, right) => right.totalTok - left.totalTok),
|
|
3186
|
+
totalIn,
|
|
3187
|
+
totalOut,
|
|
3188
|
+
totalRuns,
|
|
3189
|
+
totalTok
|
|
2905
3190
|
};
|
|
2906
3191
|
}
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
const
|
|
2910
|
-
const
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
3192
|
+
async function querySeries(dir, filter, now) {
|
|
3193
|
+
const nowMs = now();
|
|
3194
|
+
const since = parseSince(filter.since, nowMs);
|
|
3195
|
+
const rules = ruleSet(filter.rules);
|
|
3196
|
+
const runs = await readRuns(dir);
|
|
3197
|
+
const sliced = [];
|
|
3198
|
+
for (const run of runs) {
|
|
3199
|
+
if (!runInWindow(run, since, filter.cwd)) continue;
|
|
3200
|
+
const slice = sliceRun(run, rules);
|
|
3201
|
+
if (slice.touched) sliced.push({
|
|
3202
|
+
slice,
|
|
3203
|
+
ts: run.ts
|
|
3204
|
+
});
|
|
2919
3205
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
3206
|
+
const interval = filter.interval ?? chooseInterval(since, nowMs, sliced.map((entry) => entry.ts));
|
|
3207
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
3208
|
+
let totalRuns = 0;
|
|
3209
|
+
let totalIn = 0;
|
|
3210
|
+
let totalOut = 0;
|
|
3211
|
+
let totalTok = 0;
|
|
3212
|
+
for (const { slice, ts } of sliced) {
|
|
3213
|
+
const startMs = bucketStart(ts, interval);
|
|
3214
|
+
const bucket = buckets.get(startMs) ?? {
|
|
3215
|
+
inTok: 0,
|
|
3216
|
+
key: bucketLabel(startMs, interval),
|
|
3217
|
+
outTok: 0,
|
|
3218
|
+
runs: 0,
|
|
3219
|
+
startMs,
|
|
3220
|
+
totalTok: 0
|
|
3221
|
+
};
|
|
3222
|
+
bucket.runs += 1;
|
|
3223
|
+
bucket.inTok += slice.inTok;
|
|
3224
|
+
bucket.outTok += slice.outTok;
|
|
3225
|
+
bucket.totalTok += slice.totalTok;
|
|
3226
|
+
buckets.set(startMs, bucket);
|
|
3227
|
+
totalRuns += 1;
|
|
3228
|
+
totalIn += slice.inTok;
|
|
3229
|
+
totalOut += slice.outTok;
|
|
3230
|
+
totalTok += slice.totalTok;
|
|
2937
3231
|
}
|
|
2938
3232
|
return {
|
|
2939
|
-
|
|
2940
|
-
|
|
3233
|
+
buckets: fillBuckets(buckets, interval),
|
|
3234
|
+
interval,
|
|
3235
|
+
totalIn,
|
|
3236
|
+
totalOut,
|
|
3237
|
+
totalRuns,
|
|
3238
|
+
totalTok
|
|
2941
3239
|
};
|
|
2942
3240
|
}
|
|
2943
|
-
function
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3241
|
+
async function readRuns(dir) {
|
|
3242
|
+
const files = await statsFiles(dir);
|
|
3243
|
+
const runs = [];
|
|
3244
|
+
for (const { name } of files) {
|
|
3245
|
+
const content = await readFile(join(dir, name), "utf8");
|
|
3246
|
+
for (const line of content.split("\n")) {
|
|
3247
|
+
if (line.trim() === "") continue;
|
|
3248
|
+
const run = parseRunLine(line);
|
|
3249
|
+
if (run) runs.push(run);
|
|
3250
|
+
}
|
|
2952
3251
|
}
|
|
2953
|
-
|
|
2954
|
-
return parent === "" ? "." : parent;
|
|
3252
|
+
return runs;
|
|
2955
3253
|
}
|
|
2956
|
-
function
|
|
2957
|
-
const
|
|
2958
|
-
|
|
3254
|
+
async function record(dir, input, ts, retentionMonths) {
|
|
3255
|
+
const stat = {
|
|
3256
|
+
ts,
|
|
3257
|
+
...input
|
|
3258
|
+
};
|
|
3259
|
+
await mkdir(dir, { recursive: true });
|
|
3260
|
+
await appendFile(join(dir, statsFileName(ts)), `${JSON.stringify(stat)}\n`, "utf8");
|
|
3261
|
+
await prune(dir, ts, retentionMonths);
|
|
2959
3262
|
}
|
|
2960
|
-
function
|
|
2961
|
-
|
|
3263
|
+
function recordKey(record, dimension) {
|
|
3264
|
+
if (dimension === "rule") return record.ruleId;
|
|
3265
|
+
if (dimension === "operation") return record.operation ?? record.ruleId;
|
|
3266
|
+
return `${record.providerId}/${record.modelId}`;
|
|
2962
3267
|
}
|
|
2963
|
-
function
|
|
2964
|
-
return
|
|
3268
|
+
function ruleSet(rules) {
|
|
3269
|
+
return rules && rules.length > 0 ? new Set(rules) : void 0;
|
|
2965
3270
|
}
|
|
2966
|
-
function
|
|
2967
|
-
return
|
|
3271
|
+
function runInWindow(run, since, cwd) {
|
|
3272
|
+
return (since === void 0 || run.ts >= since) && (cwd === void 0 || run.cwd === cwd);
|
|
2968
3273
|
}
|
|
2969
|
-
function
|
|
2970
|
-
|
|
3274
|
+
function sliceRun(run, rules) {
|
|
3275
|
+
if (rules === void 0) return {
|
|
3276
|
+
inTok: run.usage.inTok,
|
|
3277
|
+
outTok: run.usage.outTok,
|
|
3278
|
+
totalTok: run.usage.totalTok,
|
|
3279
|
+
touched: true,
|
|
3280
|
+
usageRecords: run.usage.records
|
|
3281
|
+
};
|
|
3282
|
+
const usageRecords = run.usage.records.filter((record) => rules.has(record.ruleId));
|
|
3283
|
+
return {
|
|
3284
|
+
inTok: sumBy(usageRecords, (record) => record.inTok),
|
|
3285
|
+
outTok: sumBy(usageRecords, (record) => record.outTok),
|
|
3286
|
+
totalTok: sumBy(usageRecords, (record) => record.totalTok),
|
|
3287
|
+
touched: usageRecords.length > 0,
|
|
3288
|
+
usageRecords
|
|
3289
|
+
};
|
|
2971
3290
|
}
|
|
2972
|
-
function
|
|
2973
|
-
|
|
3291
|
+
function statsFileName(ts) {
|
|
3292
|
+
const date = new Date(ts);
|
|
3293
|
+
return `stats-${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}.jsonl`;
|
|
2974
3294
|
}
|
|
2975
|
-
function
|
|
2976
|
-
|
|
3295
|
+
async function statsFiles(dir) {
|
|
3296
|
+
let entries;
|
|
3297
|
+
try {
|
|
3298
|
+
entries = await readdir(dir);
|
|
3299
|
+
} catch (error) {
|
|
3300
|
+
if (isEnoent(error)) return [];
|
|
3301
|
+
throw error;
|
|
3302
|
+
}
|
|
3303
|
+
return entries.map((name) => {
|
|
3304
|
+
const match = STATS_FILE_PATTERN.exec(name);
|
|
3305
|
+
return match ? {
|
|
3306
|
+
month: Number(match[1]) * 12 + (Number(match[2]) - 1),
|
|
3307
|
+
name
|
|
3308
|
+
} : void 0;
|
|
3309
|
+
}).filter((entry) => entry !== void 0).sort((left, right) => left.month - right.month);
|
|
2977
3310
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
const
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
3311
|
+
function sumBy(items, select) {
|
|
3312
|
+
let total = 0;
|
|
3313
|
+
for (const item of items) total += select(item);
|
|
3314
|
+
return total;
|
|
3315
|
+
}
|
|
3316
|
+
//#endregion
|
|
3317
|
+
//#region src/cli/stats/record.ts
|
|
3318
|
+
function createRunStat(input) {
|
|
3319
|
+
return {
|
|
3320
|
+
cwd: input.cwd,
|
|
3321
|
+
durationMs: input.durationMs,
|
|
3322
|
+
ruleCounts: input.ruleCounts,
|
|
3323
|
+
usage: {
|
|
3324
|
+
inTok: input.usage.inputTokens,
|
|
3325
|
+
outTok: input.usage.outputTokens,
|
|
3326
|
+
records: input.usage.records.map(toStatsUsageRecord),
|
|
3327
|
+
totalTok: input.usage.totalTokens
|
|
2989
3328
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
function readOperation(metadata) {
|
|
3332
|
+
if (typeof metadata !== "object" || metadata === null || !("operation" in metadata)) return;
|
|
3333
|
+
const operation = metadata.operation;
|
|
3334
|
+
return typeof operation === "string" ? operation : void 0;
|
|
3335
|
+
}
|
|
3336
|
+
function toStatsUsageRecord(record) {
|
|
3337
|
+
return {
|
|
3338
|
+
filePath: record.filePath,
|
|
3339
|
+
inTok: record.inputTokens ?? 0,
|
|
3340
|
+
modelId: record.modelId,
|
|
3341
|
+
operation: readOperation(record.metadata),
|
|
3342
|
+
outTok: record.outputTokens ?? 0,
|
|
3343
|
+
providerId: record.providerId,
|
|
3344
|
+
ruleId: record.ruleId,
|
|
3345
|
+
totalTok: record.totalTokens ?? 0
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
//#endregion
|
|
3349
|
+
//#region src/cli/commands/lint/stats.ts
|
|
3350
|
+
function createStatsCollector() {
|
|
3351
|
+
const counts = {
|
|
3352
|
+
cached: 0,
|
|
3353
|
+
cancelled: 0,
|
|
3354
|
+
completed: 0,
|
|
3355
|
+
failed: 0,
|
|
3356
|
+
planned: 0
|
|
3357
|
+
};
|
|
3358
|
+
let startedAt;
|
|
3359
|
+
let endedAt;
|
|
3360
|
+
return {
|
|
3361
|
+
counts,
|
|
3362
|
+
durationMs: () => startedAt !== void 0 && endedAt !== void 0 ? endedAt - startedAt : void 0,
|
|
3363
|
+
reporter: {
|
|
3364
|
+
onPrepareStart: (payload) => {
|
|
3365
|
+
startedAt = payload.startedAt;
|
|
3366
|
+
},
|
|
3367
|
+
onRunEnd: (payload) => {
|
|
3368
|
+
counts.cached = payload.execution.cached;
|
|
3369
|
+
counts.cancelled = payload.execution.cancelled;
|
|
3370
|
+
counts.completed = payload.execution.completed;
|
|
3371
|
+
counts.failed = payload.execution.failed;
|
|
3372
|
+
counts.planned = payload.execution.planned;
|
|
3373
|
+
endedAt = payload.endedAt;
|
|
3006
3374
|
}
|
|
3007
|
-
for (const file of directoryFiles.sort()) candidates.push(file);
|
|
3008
|
-
continue;
|
|
3009
3375
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
function mergeProgressReporters(base, extra) {
|
|
3379
|
+
if (!base) return extra;
|
|
3380
|
+
if (!extra) return base;
|
|
3381
|
+
const baseState = {
|
|
3382
|
+
disabled: false,
|
|
3383
|
+
reporter: base
|
|
3384
|
+
};
|
|
3385
|
+
const extraState = {
|
|
3386
|
+
disabled: false,
|
|
3387
|
+
reporter: extra
|
|
3388
|
+
};
|
|
3389
|
+
let firstFailure;
|
|
3390
|
+
const deliver = (state, callback) => {
|
|
3391
|
+
if (state.disabled) return;
|
|
3392
|
+
try {
|
|
3393
|
+
callback(state.reporter);
|
|
3394
|
+
} catch (error) {
|
|
3395
|
+
state.disabled = true;
|
|
3396
|
+
firstFailure ??= normalizeReporterError(error);
|
|
3022
3397
|
}
|
|
3023
|
-
|
|
3024
|
-
|
|
3398
|
+
};
|
|
3399
|
+
const deliverBoth = (callback) => {
|
|
3400
|
+
deliver(baseState, callback);
|
|
3401
|
+
deliver(extraState, callback);
|
|
3402
|
+
};
|
|
3025
3403
|
return {
|
|
3026
|
-
|
|
3027
|
-
|
|
3404
|
+
onDiagnostic: (payload) => {
|
|
3405
|
+
deliverBoth((reporter) => reporter.onDiagnostic?.(payload));
|
|
3406
|
+
},
|
|
3407
|
+
onExecuteEnd: (payload) => {
|
|
3408
|
+
deliverBoth((reporter) => reporter.onExecuteEnd?.(payload));
|
|
3409
|
+
},
|
|
3410
|
+
onExecuteStart: (payload) => {
|
|
3411
|
+
deliverBoth((reporter) => reporter.onExecuteStart?.(payload));
|
|
3412
|
+
},
|
|
3413
|
+
onFileReady: (payload) => {
|
|
3414
|
+
deliverBoth((reporter) => reporter.onFileReady?.(payload));
|
|
3415
|
+
},
|
|
3416
|
+
onJobEnd: (payload) => {
|
|
3417
|
+
deliverBoth((reporter) => reporter.onJobEnd?.(payload));
|
|
3418
|
+
},
|
|
3419
|
+
onJobQueued: (payload) => {
|
|
3420
|
+
deliverBoth((reporter) => reporter.onJobQueued?.(payload));
|
|
3421
|
+
},
|
|
3422
|
+
onJobRetry: (payload) => {
|
|
3423
|
+
deliverBoth((reporter) => reporter.onJobRetry?.(payload));
|
|
3424
|
+
},
|
|
3425
|
+
onJobStart: (payload) => {
|
|
3426
|
+
deliverBoth((reporter) => reporter.onJobStart?.(payload));
|
|
3427
|
+
},
|
|
3428
|
+
onPlanningEnd: (payload) => {
|
|
3429
|
+
deliverBoth((reporter) => reporter.onPlanningEnd?.(payload));
|
|
3430
|
+
},
|
|
3431
|
+
onPrepareEnd: (payload) => {
|
|
3432
|
+
deliverBoth((reporter) => reporter.onPrepareEnd?.(payload));
|
|
3433
|
+
},
|
|
3434
|
+
onPrepareStart: (payload) => {
|
|
3435
|
+
deliverBoth((reporter) => reporter.onPrepareStart?.(payload));
|
|
3436
|
+
},
|
|
3437
|
+
onRunEnd: (payload) => {
|
|
3438
|
+
deliverBoth((reporter) => reporter.onRunEnd?.(payload));
|
|
3439
|
+
if (firstFailure) throw firstFailure;
|
|
3440
|
+
},
|
|
3441
|
+
onUsage: (payload) => {
|
|
3442
|
+
deliverBoth((reporter) => reporter.onUsage?.(payload));
|
|
3443
|
+
}
|
|
3028
3444
|
};
|
|
3029
3445
|
}
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
const
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
gitignore: options.gitignore,
|
|
3039
|
-
ignoredPatterns: options.ignoredPatterns
|
|
3040
|
-
})) {
|
|
3041
|
-
const relativePath = normalizeRelativePath(options.cwd, file);
|
|
3042
|
-
if (matchesGlob(relativePath, pattern) && shouldIncludeGlobCandidate(relativePath, options.config, {
|
|
3043
|
-
cwd: options.cwd,
|
|
3044
|
-
hasFilePatterns: options.hasFilePatterns
|
|
3045
|
-
})) candidates.push(relativePath);
|
|
3046
|
-
}
|
|
3047
|
-
return candidates.sort();
|
|
3048
|
-
}
|
|
3049
|
-
function shouldFilterGitignoredFiles(config) {
|
|
3050
|
-
return normalizeConfig(config).some((item) => item.ignore?.gitignore === true);
|
|
3446
|
+
function resolveStatsWrite(stats, env) {
|
|
3447
|
+
if (isCi(env)) return;
|
|
3448
|
+
if (!(stats === false ? false : stats === void 0 || stats === true ? true : stats.enabled !== false)) return;
|
|
3449
|
+
const config = typeof stats === "object" ? stats : void 0;
|
|
3450
|
+
return {
|
|
3451
|
+
dir: config?.location ?? getStatsDir(env),
|
|
3452
|
+
retentionMonths: config?.retentionMonths
|
|
3453
|
+
};
|
|
3051
3454
|
}
|
|
3052
|
-
function
|
|
3053
|
-
|
|
3054
|
-
|
|
3455
|
+
async function writeRunStats(target, collector, result, cwd) {
|
|
3456
|
+
const store = createJsonlStatsStore({
|
|
3457
|
+
dir: target.dir,
|
|
3458
|
+
retentionMonths: target.retentionMonths
|
|
3459
|
+
});
|
|
3460
|
+
try {
|
|
3461
|
+
await store.record(createRunStat({
|
|
3462
|
+
cwd,
|
|
3463
|
+
durationMs: collector.durationMs(),
|
|
3464
|
+
ruleCounts: {
|
|
3465
|
+
cached: result.execution.cached,
|
|
3466
|
+
cancelled: result.execution.cancelled,
|
|
3467
|
+
completed: result.execution.completed,
|
|
3468
|
+
failed: result.execution.failed,
|
|
3469
|
+
planned: result.execution.planned
|
|
3470
|
+
},
|
|
3471
|
+
usage: result.usage
|
|
3472
|
+
}));
|
|
3473
|
+
} catch {}
|
|
3055
3474
|
}
|
|
3056
|
-
|
|
3057
|
-
if (
|
|
3058
|
-
return
|
|
3475
|
+
function normalizeReporterError(error) {
|
|
3476
|
+
if (error instanceof Error) return error;
|
|
3477
|
+
return new Error(error != null ? String(error) : "Unknown progress reporter error.");
|
|
3059
3478
|
}
|
|
3060
|
-
|
|
3479
|
+
//#endregion
|
|
3480
|
+
//#region src/cli/commands/lint/execution.ts
|
|
3481
|
+
/** Runs one session while keeping CLI flags and stats behavior identical for every caller. */
|
|
3482
|
+
async function executeLint(options) {
|
|
3483
|
+
const runner = resolveRunnerConfig(options.session.runner, options.runnerOptions);
|
|
3484
|
+
const statsTarget = resolveStatsWrite(runner?.stats, options.io.env);
|
|
3485
|
+
const statsCollector = statsTarget ? createStatsCollector() : void 0;
|
|
3486
|
+
const persistStats = async (result) => {
|
|
3487
|
+
if (statsTarget && statsCollector) await writeRunStats(statsTarget, statsCollector, result, options.session.cwd);
|
|
3488
|
+
};
|
|
3489
|
+
const targetSelection = options.targets === void 0 ? { inputs: options.inputs } : { targets: options.targets };
|
|
3490
|
+
let result;
|
|
3061
3491
|
try {
|
|
3062
|
-
|
|
3492
|
+
result = await options.session.run({
|
|
3493
|
+
...targetSelection,
|
|
3494
|
+
cacheOnly: options.cacheOnly,
|
|
3495
|
+
modelOverride: options.modelOverride,
|
|
3496
|
+
outputLanguage: options.outputLanguage,
|
|
3497
|
+
progress: mergeProgressReporters(options.progress, statsCollector?.reporter),
|
|
3498
|
+
runner,
|
|
3499
|
+
signal: options.createSignal?.()
|
|
3500
|
+
});
|
|
3063
3501
|
} catch (error) {
|
|
3064
|
-
if (
|
|
3502
|
+
if (error instanceof AlintRunError || error instanceof AlintRunCancelledError || error instanceof AlintCachePersistenceError) await persistStats(error.result);
|
|
3065
3503
|
throw error;
|
|
3066
3504
|
}
|
|
3505
|
+
await persistStats(result);
|
|
3506
|
+
return result;
|
|
3067
3507
|
}
|
|
3508
|
+
//#endregion
|
|
3509
|
+
//#region src/cli/commands/integrations/stop-gate/timeout.ts
|
|
3510
|
+
const maximumTimerChunkMs = 2147483647;
|
|
3068
3511
|
/**
|
|
3069
|
-
*
|
|
3070
|
-
*
|
|
3512
|
+
* Node shortens an overflowing timer to one millisecond. The public Stop Gate limit is currently
|
|
3513
|
+
* below that boundary, while chunking here keeps this process-level safety helper correct if the
|
|
3514
|
+
* host budget changes independently later.
|
|
3071
3515
|
*/
|
|
3072
|
-
|
|
3073
|
-
const
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
const
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
continue;
|
|
3516
|
+
function createTimeoutSignal(timeoutMs) {
|
|
3517
|
+
const controller = new AbortController();
|
|
3518
|
+
let remainingMs = timeoutMs;
|
|
3519
|
+
let timer;
|
|
3520
|
+
const schedule = () => {
|
|
3521
|
+
const delay = Math.min(remainingMs, maximumTimerChunkMs);
|
|
3522
|
+
timer = setTimeout(() => {
|
|
3523
|
+
remainingMs -= delay;
|
|
3524
|
+
if (remainingMs > 0) {
|
|
3525
|
+
schedule();
|
|
3526
|
+
return;
|
|
3084
3527
|
}
|
|
3085
|
-
|
|
3528
|
+
controller.abort(new DOMException("Stop Gate timed out.", "TimeoutError"));
|
|
3529
|
+
}, delay);
|
|
3530
|
+
};
|
|
3531
|
+
schedule();
|
|
3532
|
+
return {
|
|
3533
|
+
dispose: () => clearTimeout(timer),
|
|
3534
|
+
signal: controller.signal
|
|
3535
|
+
};
|
|
3536
|
+
}
|
|
3537
|
+
//#endregion
|
|
3538
|
+
//#region src/cli/commands/integrations/stop-gate/run.ts
|
|
3539
|
+
async function runStopGateLint(options) {
|
|
3540
|
+
const dirtyTargets = options.stopGate.target === "dirty-files" ? await findDirtyLintTargets(options.config, options.cwd) : void 0;
|
|
3541
|
+
const targets = dirtyTargets ?? await findLintTargets({
|
|
3542
|
+
config: options.config,
|
|
3543
|
+
cwd: options.cwd,
|
|
3544
|
+
errorOnUnmatchedPattern: true,
|
|
3545
|
+
globInputPaths: true,
|
|
3546
|
+
inputs: ["."]
|
|
3547
|
+
});
|
|
3548
|
+
if (options.stopGate.target === "dirty-files" && targets.files.length === 0) return;
|
|
3549
|
+
const session = await createRunSession({
|
|
3550
|
+
...options.io,
|
|
3551
|
+
cwd: options.cwd
|
|
3552
|
+
}, { config: options.config });
|
|
3553
|
+
let timeout;
|
|
3554
|
+
try {
|
|
3555
|
+
let result;
|
|
3556
|
+
let runError;
|
|
3557
|
+
try {
|
|
3558
|
+
result = await executeLint({
|
|
3559
|
+
createSignal: () => {
|
|
3560
|
+
timeout = createTimeoutSignal(options.stopGate.timeoutMs);
|
|
3561
|
+
return timeout.signal;
|
|
3562
|
+
},
|
|
3563
|
+
io: options.io,
|
|
3564
|
+
runnerOptions: { format: "json" },
|
|
3565
|
+
session,
|
|
3566
|
+
targets
|
|
3567
|
+
});
|
|
3568
|
+
} catch (error) {
|
|
3569
|
+
if (!(error instanceof AlintRunError)) throw error;
|
|
3570
|
+
runError = error;
|
|
3571
|
+
result = error.result;
|
|
3086
3572
|
}
|
|
3087
|
-
|
|
3573
|
+
if (dirtyTargets !== void 0) result = filterResultToChangedLines(result, {
|
|
3574
|
+
changedLines: dirtyTargets.changedLines,
|
|
3575
|
+
cwd: options.cwd
|
|
3576
|
+
});
|
|
3577
|
+
return runError === void 0 ? {
|
|
3578
|
+
files: targets.files,
|
|
3579
|
+
result
|
|
3580
|
+
} : {
|
|
3581
|
+
error: runError,
|
|
3582
|
+
files: targets.files,
|
|
3583
|
+
result
|
|
3584
|
+
};
|
|
3585
|
+
} finally {
|
|
3586
|
+
timeout?.dispose();
|
|
3587
|
+
await session.shutdown();
|
|
3088
3588
|
}
|
|
3089
3589
|
}
|
|
3090
3590
|
//#endregion
|
|
3091
|
-
//#region src/cli/commands/
|
|
3092
|
-
const
|
|
3093
|
-
|
|
3094
|
-
|
|
3591
|
+
//#region src/cli/commands/integrations/stop-gate/stop-gate.ts
|
|
3592
|
+
const stopGate = defineCommand({
|
|
3593
|
+
action: (context, options) => runStopGateCommand(options, context.io),
|
|
3594
|
+
description: "Run the repository Stop Gate integration",
|
|
3595
|
+
exactArguments: true,
|
|
3596
|
+
name: "stop-gate",
|
|
3597
|
+
options: [{
|
|
3598
|
+
description: "Codex session id",
|
|
3599
|
+
flags: "--session-id <id>"
|
|
3600
|
+
}]
|
|
3601
|
+
});
|
|
3602
|
+
async function runStopGateCommand(options, io) {
|
|
3603
|
+
if (!options.sessionId) {
|
|
3604
|
+
io.stderr.write("Stop Gate requires --session-id.\n");
|
|
3605
|
+
return 2;
|
|
3606
|
+
}
|
|
3607
|
+
try {
|
|
3608
|
+
const cwd = await findGitRoot(io.cwd);
|
|
3609
|
+
const loaded = await loadAlintConfigWithMetadata(cwd);
|
|
3610
|
+
if (loaded.configFile === void 0) {
|
|
3611
|
+
writeEnvelope((chunk) => io.stdout.write(chunk), {
|
|
3612
|
+
errorCount: 0,
|
|
3613
|
+
schemaVersion: 2,
|
|
3614
|
+
status: "inactive",
|
|
3615
|
+
warningCount: 0
|
|
3616
|
+
});
|
|
3617
|
+
return 0;
|
|
3618
|
+
}
|
|
3619
|
+
const stopGate = resolveStopGateConfig(loaded.config, cwd);
|
|
3620
|
+
if (!stopGate.enabled) {
|
|
3621
|
+
await removeStopGateReport(options.sessionId);
|
|
3622
|
+
writeEnvelope((chunk) => io.stdout.write(chunk), {
|
|
3623
|
+
errorCount: 0,
|
|
3624
|
+
schemaVersion: 2,
|
|
3625
|
+
status: "inactive",
|
|
3626
|
+
warningCount: 0
|
|
3627
|
+
});
|
|
3628
|
+
return 0;
|
|
3629
|
+
}
|
|
3630
|
+
const lint = await runStopGateLint({
|
|
3631
|
+
config: loaded.config,
|
|
3632
|
+
cwd,
|
|
3633
|
+
io,
|
|
3634
|
+
stopGate
|
|
3635
|
+
});
|
|
3636
|
+
if (lint === void 0) {
|
|
3637
|
+
await removeStopGateReport(options.sessionId);
|
|
3638
|
+
writeEnvelope((chunk) => io.stdout.write(chunk), {
|
|
3639
|
+
errorCount: 0,
|
|
3640
|
+
schemaVersion: 2,
|
|
3641
|
+
status: "no-dirty-files",
|
|
3642
|
+
warningCount: 0
|
|
3643
|
+
});
|
|
3644
|
+
return 0;
|
|
3645
|
+
}
|
|
3646
|
+
if (lint.error !== void 0) {
|
|
3647
|
+
const reportPath = await writeStopGateReport(options.sessionId, {
|
|
3648
|
+
cwd,
|
|
3649
|
+
diagnostics: lint.result.diagnostics,
|
|
3650
|
+
execution: lint.result.execution,
|
|
3651
|
+
failure: {
|
|
3652
|
+
details: lint.error.failures,
|
|
3653
|
+
message: lint.error.message
|
|
3654
|
+
},
|
|
3655
|
+
files: lint.files,
|
|
3656
|
+
schemaVersion: 1,
|
|
3657
|
+
target: stopGate.target,
|
|
3658
|
+
usage: lint.result.usage
|
|
3659
|
+
});
|
|
3660
|
+
writeEnvelope((chunk) => io.stdout.write(chunk), {
|
|
3661
|
+
errorCount: 0,
|
|
3662
|
+
message: lint.error.message,
|
|
3663
|
+
reportPath,
|
|
3664
|
+
schemaVersion: 2,
|
|
3665
|
+
status: "runtime-error",
|
|
3666
|
+
warningCount: 0
|
|
3667
|
+
});
|
|
3668
|
+
return 1;
|
|
3669
|
+
}
|
|
3670
|
+
const errorCount = lint.result.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
3671
|
+
const warningCount = lint.result.diagnostics.length - errorCount;
|
|
3672
|
+
if (errorCount === 0 && warningCount === 0) {
|
|
3673
|
+
await removeStopGateReport(options.sessionId);
|
|
3674
|
+
writeEnvelope((chunk) => io.stdout.write(chunk), {
|
|
3675
|
+
errorCount,
|
|
3676
|
+
schemaVersion: 2,
|
|
3677
|
+
status: "clean",
|
|
3678
|
+
warningCount
|
|
3679
|
+
});
|
|
3680
|
+
return 0;
|
|
3681
|
+
}
|
|
3682
|
+
const reportPath = await writeStopGateReport(options.sessionId, {
|
|
3683
|
+
cwd,
|
|
3684
|
+
diagnostics: lint.result.diagnostics,
|
|
3685
|
+
execution: lint.result.execution,
|
|
3686
|
+
files: lint.files,
|
|
3687
|
+
schemaVersion: 1,
|
|
3688
|
+
target: stopGate.target,
|
|
3689
|
+
usage: lint.result.usage
|
|
3690
|
+
});
|
|
3691
|
+
writeEnvelope((chunk) => io.stdout.write(chunk), {
|
|
3692
|
+
errorCount,
|
|
3693
|
+
findingsHash: fingerprintDiagnostics(lint.result.diagnostics),
|
|
3694
|
+
reportPath,
|
|
3695
|
+
schemaVersion: 2,
|
|
3696
|
+
status: errorCount > 0 ? "errors" : "warnings",
|
|
3697
|
+
warningCount
|
|
3698
|
+
});
|
|
3699
|
+
return 0;
|
|
3700
|
+
} catch (error) {
|
|
3701
|
+
writeEnvelope((chunk) => io.stdout.write(chunk), {
|
|
3702
|
+
errorCount: 0,
|
|
3703
|
+
message: error instanceof StopGateReportTooLargeError ? error.message : error instanceof AlintRunCancelledError ? "Stop Gate timed out before lint completed." : errorMessageFrom(error) ?? "unknown error",
|
|
3704
|
+
schemaVersion: 2,
|
|
3705
|
+
status: "runtime-error",
|
|
3706
|
+
warningCount: 0
|
|
3707
|
+
});
|
|
3708
|
+
return 1;
|
|
3709
|
+
}
|
|
3095
3710
|
}
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
] : [],
|
|
3108
|
-
...groups.length > 0 ? [
|
|
3109
|
-
`Failed Rules ${groups.length}`,
|
|
3110
|
-
"",
|
|
3111
|
-
...formatFailureGroups(groups, color)
|
|
3112
|
-
] : []
|
|
3113
|
-
].join("\n");
|
|
3711
|
+
//#endregion
|
|
3712
|
+
//#region src/cli/commands/integrations/integrations.ts
|
|
3713
|
+
const integrations = defineCommand({
|
|
3714
|
+
children: [stopGate],
|
|
3715
|
+
description: "Run external integrations",
|
|
3716
|
+
name: "integrations"
|
|
3717
|
+
});
|
|
3718
|
+
//#endregion
|
|
3719
|
+
//#region src/cli/reporters/json.ts
|
|
3720
|
+
function formatJson(result) {
|
|
3721
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
3114
3722
|
}
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3723
|
+
//#endregion
|
|
3724
|
+
//#region src/cli/reporters/stylish.ts
|
|
3725
|
+
const colors$3 = createColors({ force: true });
|
|
3726
|
+
function formatStylish(input, options = {}) {
|
|
3727
|
+
const diagnostics = Array.isArray(input) ? input : input.diagnostics;
|
|
3728
|
+
const result = Array.isArray(input) ? void 0 : input;
|
|
3729
|
+
const style = createStyle(options.color === true);
|
|
3730
|
+
if (diagnostics.length === 0) return result?.execution?.cached ? `${formatSummary(diagnostics, result, style)}\n` : "";
|
|
3731
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
3732
|
+
for (const diagnostic of diagnostics) {
|
|
3733
|
+
const fileDiagnostics = diagnosticsByFile.get(diagnostic.filePath);
|
|
3734
|
+
if (fileDiagnostics) {
|
|
3735
|
+
fileDiagnostics.push(diagnostic);
|
|
3736
|
+
continue;
|
|
3128
3737
|
}
|
|
3129
|
-
|
|
3738
|
+
diagnosticsByFile.set(diagnostic.filePath, [diagnostic]);
|
|
3130
3739
|
}
|
|
3131
|
-
return lines;
|
|
3132
|
-
}
|
|
3133
|
-
function formatFileFailures(failures, color) {
|
|
3134
3740
|
const lines = [];
|
|
3135
|
-
for (const
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3741
|
+
for (const [filePath, fileDiagnostics] of diagnosticsByFile) {
|
|
3742
|
+
lines.push(style.file(filePath));
|
|
3743
|
+
for (const diagnostic of fileDiagnostics) {
|
|
3744
|
+
const line = diagnostic.loc?.start.line ?? 0;
|
|
3745
|
+
const column = diagnostic.loc?.start.column ?? 0;
|
|
3746
|
+
const severity = diagnostic.severity === "warn" ? style.warning("warning") : style.error("error");
|
|
3747
|
+
lines.push(` ${style.location(`${line}:${column}`)} ${severity} ${diagnostic.message} ${style.ruleId(diagnostic.ruleId)}`);
|
|
3748
|
+
}
|
|
3139
3749
|
lines.push("");
|
|
3140
3750
|
}
|
|
3141
|
-
|
|
3142
|
-
}
|
|
3143
|
-
function formatTarget(failure) {
|
|
3144
|
-
const target = failure.job.target.name ? `${failure.job.target.kind} ${failure.job.target.name}` : failure.job.target.kind;
|
|
3145
|
-
return `${failure.job.inputPath} > ${target}`;
|
|
3751
|
+
lines.push("", formatSummary(diagnostics, result, style));
|
|
3752
|
+
return `${lines.join("\n")}\n`;
|
|
3146
3753
|
}
|
|
3147
|
-
function
|
|
3148
|
-
|
|
3149
|
-
for (const failure of failures) {
|
|
3150
|
-
const group = groups.get(failure.job.ruleId) ?? [];
|
|
3151
|
-
group.push(failure);
|
|
3152
|
-
groups.set(failure.job.ruleId, group);
|
|
3153
|
-
}
|
|
3154
|
-
return [...groups];
|
|
3754
|
+
function countCachedDiagnostics(diagnostics, severity) {
|
|
3755
|
+
return diagnostics.filter((diagnostic) => diagnostic.severity === severity && diagnostic.cached === true).length;
|
|
3155
3756
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
function resolveConfigRunner(config) {
|
|
3159
|
-
return normalizeConfig(config).reduce((merged, item) => item.runner ? {
|
|
3160
|
-
...merged,
|
|
3161
|
-
...item.runner
|
|
3162
|
-
} : merged, void 0);
|
|
3757
|
+
function countDiagnostics$1(diagnostics, severity) {
|
|
3758
|
+
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
3163
3759
|
}
|
|
3164
|
-
function
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
cache,
|
|
3173
|
-
ruleConcurrency: ruleConcurrency ?? config.runner?.ruleConcurrency ?? setupConfig.runner?.ruleConcurrency,
|
|
3174
|
-
stats,
|
|
3175
|
-
timeoutMs: timeoutMs ?? config.runner?.timeoutMs ?? setupConfig.runner?.timeoutMs
|
|
3760
|
+
function createStyle(color) {
|
|
3761
|
+
if (!color) return {
|
|
3762
|
+
error: identity,
|
|
3763
|
+
file: identity,
|
|
3764
|
+
location: identity,
|
|
3765
|
+
ruleId: identity,
|
|
3766
|
+
summaryToken: identity,
|
|
3767
|
+
warning: identity
|
|
3176
3768
|
};
|
|
3177
|
-
return
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
...configCache
|
|
3769
|
+
return {
|
|
3770
|
+
error: colors$3.red,
|
|
3771
|
+
file: colors$3.underline,
|
|
3772
|
+
location: colors$3.dim,
|
|
3773
|
+
ruleId: colors$3.dim,
|
|
3774
|
+
summaryToken: colors$3.cyan,
|
|
3775
|
+
warning: colors$3.yellow
|
|
3185
3776
|
};
|
|
3186
|
-
return configCache;
|
|
3187
3777
|
}
|
|
3188
|
-
function
|
|
3189
|
-
|
|
3190
|
-
if (typeof configStats === "boolean") return configStats;
|
|
3191
|
-
if (typeof setupStats === "object") return {
|
|
3192
|
-
...setupStats,
|
|
3193
|
-
...configStats
|
|
3194
|
-
};
|
|
3195
|
-
return configStats;
|
|
3778
|
+
function formatDiagnosticCount(total, cached, label) {
|
|
3779
|
+
return `${total} ${label}${cached > 0 ? ` (${cached} cached)` : ""}`;
|
|
3196
3780
|
}
|
|
3197
|
-
function
|
|
3198
|
-
if (
|
|
3199
|
-
|
|
3200
|
-
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${label} must be a positive integer.`);
|
|
3201
|
-
return parsed;
|
|
3781
|
+
function formatExecutionSummary(execution) {
|
|
3782
|
+
if (!execution || execution.cached === 0) return;
|
|
3783
|
+
return `${execution.cached}/${execution.planned} cached`;
|
|
3202
3784
|
}
|
|
3203
|
-
function
|
|
3204
|
-
|
|
3205
|
-
const
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
return
|
|
3785
|
+
function formatSummary(diagnostics, result, style) {
|
|
3786
|
+
const warnCount = countDiagnostics$1(diagnostics, "warn");
|
|
3787
|
+
const errorCount = countDiagnostics$1(diagnostics, "error");
|
|
3788
|
+
const cachedWarnCount = countCachedDiagnostics(diagnostics, "warn");
|
|
3789
|
+
const cachedErrorCount = countCachedDiagnostics(diagnostics, "error");
|
|
3790
|
+
const tokens = result === void 0 ? void 0 : formatTokenSummary(result);
|
|
3791
|
+
const problemSummary = [style.warning(formatDiagnosticCount(warnCount, cachedWarnCount, "warn")), style.error(formatDiagnosticCount(errorCount, cachedErrorCount, "error"))].join(" / ");
|
|
3792
|
+
if (tokens === void 0) return problemSummary;
|
|
3793
|
+
const execution = formatExecutionSummary(result?.execution);
|
|
3794
|
+
return [
|
|
3795
|
+
problemSummary,
|
|
3796
|
+
style.summaryToken(tokens),
|
|
3797
|
+
execution
|
|
3798
|
+
].filter((segment) => segment !== void 0).join(" | ");
|
|
3211
3799
|
}
|
|
3212
|
-
function
|
|
3213
|
-
|
|
3214
|
-
|
|
3800
|
+
function formatTokenSummary(result) {
|
|
3801
|
+
const liveTokens = result.usage.totalTokens.toLocaleString("en-US");
|
|
3802
|
+
const cachedTokens = result.usage.cached?.totalTokens ?? 0;
|
|
3803
|
+
return `${liveTokens} tokens${cachedTokens > 0 ? ` (${cachedTokens.toLocaleString("en-US")} cached)` : ""}`;
|
|
3215
3804
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
function isCi(env = process.env) {
|
|
3219
|
-
const ci = env.CI;
|
|
3220
|
-
if (ci !== void 0 && ci !== "" && ci !== "0" && ci !== "false") return true;
|
|
3221
|
-
return Boolean(env.GITHUB_ACTIONS || env.GITLAB_CI || env.CIRCLECI || env.BUILDKITE || env.TF_BUILD);
|
|
3805
|
+
function identity(value) {
|
|
3806
|
+
return value;
|
|
3222
3807
|
}
|
|
3223
3808
|
//#endregion
|
|
3224
|
-
//#region src/cli/
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
* - `Nh`/`Nd`/`Nw`/`Nm`/`Ny` — now minus N hours/days/weeks/months/years.
|
|
3230
|
-
* Weeks, months, and years are fixed approximations (7/30/365 days). `m` is
|
|
3231
|
-
* months here, not minutes — a day is the finest window this tool tracks.
|
|
3232
|
-
* - `YYYY-MM`/`YYYY-MM-DD` — start of that UTC month/day.
|
|
3233
|
-
*
|
|
3234
|
-
* Edge cases:
|
|
3235
|
-
* - An empty time means "no lower bound".
|
|
3236
|
-
* - An unrecognized time throws.
|
|
3237
|
-
*/
|
|
3238
|
-
const HOUR_MS = 36e5;
|
|
3239
|
-
const UNIT_MS = {
|
|
3240
|
-
d: 24 * HOUR_MS,
|
|
3241
|
-
h: HOUR_MS,
|
|
3242
|
-
m: 720 * HOUR_MS,
|
|
3243
|
-
w: 168 * HOUR_MS,
|
|
3244
|
-
y: 8760 * HOUR_MS
|
|
3245
|
-
};
|
|
3246
|
-
function parseSince(time, now) {
|
|
3247
|
-
if (time === void 0 || time === "") return;
|
|
3248
|
-
const relative = /^(\d+)([dhmwy])$/u.exec(time);
|
|
3249
|
-
if (relative) return now - Number(relative[1]) * UNIT_MS[relative[2]];
|
|
3250
|
-
const calendar = /^(\d{4})-(\d{2})(?:-(\d{2}))?$/u.exec(time);
|
|
3251
|
-
if (calendar) {
|
|
3252
|
-
const year = Number(calendar[1]);
|
|
3253
|
-
const month = Number(calendar[2]);
|
|
3254
|
-
const day = calendar[3] === void 0 ? 1 : Number(calendar[3]);
|
|
3255
|
-
return Date.UTC(year, month - 1, day);
|
|
3256
|
-
}
|
|
3257
|
-
throw new Error(`Invalid --since "${time}": use e.g. 24h, 7d, 2w, 3m, 1y, 2025-01, or 2025-01-15.`);
|
|
3809
|
+
//#region src/cli/reporters/index.ts
|
|
3810
|
+
function formatDiagnostics(format, result, options = {}) {
|
|
3811
|
+
if (format === "json") return formatJson(result);
|
|
3812
|
+
if (format === "stylish") return formatStylish(result, { color: options.color });
|
|
3813
|
+
throw new Error(`Unknown reporter "${format}".`);
|
|
3258
3814
|
}
|
|
3259
3815
|
//#endregion
|
|
3260
|
-
//#region src/cli/
|
|
3261
|
-
|
|
3262
|
-
const
|
|
3263
|
-
const DAY_MS = 864e5;
|
|
3264
|
-
const MONTH_LABELS$1 = [
|
|
3265
|
-
"Jan",
|
|
3266
|
-
"Feb",
|
|
3267
|
-
"Mar",
|
|
3268
|
-
"Apr",
|
|
3269
|
-
"May",
|
|
3270
|
-
"Jun",
|
|
3271
|
-
"Jul",
|
|
3272
|
-
"Aug",
|
|
3273
|
-
"Sep",
|
|
3274
|
-
"Oct",
|
|
3275
|
-
"Nov",
|
|
3276
|
-
"Dec"
|
|
3277
|
-
];
|
|
3278
|
-
function createJsonlStatsStore(options) {
|
|
3279
|
-
const retentionMonths = options.retentionMonths ?? DEFAULT_RETENTION_MONTHS;
|
|
3816
|
+
//#region src/cli/reporters/progress/plain.ts
|
|
3817
|
+
function createPlainProgressReporter(options) {
|
|
3818
|
+
const writeLine = (line) => options.write(`${line}\n`);
|
|
3280
3819
|
return {
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3820
|
+
onJobRetry: () => {},
|
|
3821
|
+
onJobStart: (payload) => {
|
|
3822
|
+
const target = payload.job.target.name ? `${payload.job.target.kind} ${payload.job.target.name}` : payload.job.target.kind;
|
|
3823
|
+
writeLine(`scan ${payload.job.inputPath} > ${target} > ${payload.job.ruleId}`);
|
|
3824
|
+
},
|
|
3825
|
+
onPrepareEnd: ({ filesTotal }) => writeLine(`alint prepared: ${filesTotal} files`),
|
|
3826
|
+
onPrepareStart: () => writeLine("alint preparing"),
|
|
3827
|
+
onRunEnd: (payload) => {
|
|
3828
|
+
const warnCount = countDiagnostics(payload.diagnostics, "warn");
|
|
3829
|
+
const errorCount = countDiagnostics(payload.diagnostics, "error");
|
|
3830
|
+
const state = payload.execution.failed > 0 ? "failed" : "finished";
|
|
3831
|
+
const { cached, cancelled, completed, failed, skipped } = payload.execution;
|
|
3832
|
+
writeLine(`alint ${state}: ${warnCount} warn, ${errorCount} error, ${payload.usage.totalTokens} tokens, ${completed} completed, ${cached} cached, ${failed} failed, ${cancelled} cancelled, ${skipped} skipped`);
|
|
3833
|
+
}
|
|
3284
3834
|
};
|
|
3285
3835
|
}
|
|
3286
|
-
function
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3836
|
+
function countDiagnostics(diagnostics, severity) {
|
|
3837
|
+
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
3838
|
+
}
|
|
3839
|
+
//#endregion
|
|
3840
|
+
//#region src/cli/reporters/progress/summary.ts
|
|
3841
|
+
const colors$2 = createColors({ force: true });
|
|
3842
|
+
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
3843
|
+
function createSummaryProgressReporter(options) {
|
|
3844
|
+
const now = Date.now;
|
|
3845
|
+
const state = createInitialState();
|
|
3846
|
+
return {
|
|
3847
|
+
getRows: () => createRows(state, options, now()),
|
|
3848
|
+
onDiagnostic: ({ diagnostic, progress }) => {
|
|
3849
|
+
updateProgress(state, progress);
|
|
3850
|
+
if (diagnostic.severity === "warn") state.warnCount += 1;
|
|
3851
|
+
else state.errorCount += 1;
|
|
3852
|
+
},
|
|
3853
|
+
onExecuteEnd: ({ progress }) => {
|
|
3854
|
+
updateProgress(state, progress);
|
|
3855
|
+
},
|
|
3856
|
+
onExecuteStart: ({ progress, startedAt }) => {
|
|
3857
|
+
updateProgress(state, progress);
|
|
3858
|
+
state.runStartedAt = startedAt ?? state.runStartedAt ?? now();
|
|
3859
|
+
},
|
|
3860
|
+
onFileReady: ({ progress }) => updateProgress(state, progress),
|
|
3861
|
+
onJobEnd: (payload) => {
|
|
3862
|
+
updateProgress(state, payload.progress);
|
|
3863
|
+
const current = state.activeJobs.get(payload.job.id);
|
|
3864
|
+
const group = state.ruleGroups.get(payload.job.ruleId);
|
|
3865
|
+
if (!group) throw new Error(`Cannot finish unqueued progress job "${payload.job.id}".`);
|
|
3866
|
+
group[payload.state] += 1;
|
|
3867
|
+
if (payload.endedAt !== void 0 && current?.startedAt !== void 0 && payload.state !== "cached") group.terminalDurationMs += Math.max(payload.endedAt - current.startedAt, 0);
|
|
3868
|
+
if (payload.state === "cached") {
|
|
3869
|
+
const cachedTokens = state.jobTokens.get(payload.job.id) ?? 0;
|
|
3870
|
+
state.totalTokens = Math.max(state.totalTokens - cachedTokens, 0);
|
|
3871
|
+
state.cachedTokens += cachedTokens;
|
|
3872
|
+
}
|
|
3873
|
+
state.activeJobs.delete(payload.job.id);
|
|
3874
|
+
state.jobTokens.delete(payload.job.id);
|
|
3875
|
+
},
|
|
3876
|
+
onJobQueued: ({ job, progress }) => {
|
|
3877
|
+
updateProgress(state, progress);
|
|
3878
|
+
const group = state.ruleGroups.get(job.ruleId) ?? createRuleGroup(job);
|
|
3879
|
+
group.planned += 1;
|
|
3880
|
+
state.ruleGroups.set(job.ruleId, group);
|
|
3881
|
+
state.activeJobs.set(job.id, {
|
|
3882
|
+
job,
|
|
3883
|
+
state: "queued"
|
|
3884
|
+
});
|
|
3885
|
+
},
|
|
3886
|
+
onJobRetry: (payload) => {
|
|
3887
|
+
updateProgress(state, payload.progress);
|
|
3888
|
+
const current = state.activeJobs.get(payload.job.id);
|
|
3889
|
+
state.activeJobs.set(payload.job.id, {
|
|
3890
|
+
job: payload.job,
|
|
3891
|
+
retry: {
|
|
3892
|
+
attempt: payload.attempt,
|
|
3893
|
+
maxAttempts: payload.maxAttempts,
|
|
3894
|
+
startedAt: payload.startedAt
|
|
3895
|
+
},
|
|
3896
|
+
startedAt: current?.startedAt ?? payload.startedAt ?? now(),
|
|
3897
|
+
state: current?.state ?? "running"
|
|
3898
|
+
});
|
|
3899
|
+
},
|
|
3900
|
+
onJobStart: (payload) => {
|
|
3901
|
+
updateProgress(state, payload.progress);
|
|
3902
|
+
state.activeJobs.set(payload.job.id, {
|
|
3903
|
+
job: payload.job,
|
|
3904
|
+
startedAt: payload.startedAt ?? now(),
|
|
3905
|
+
state: "running"
|
|
3906
|
+
});
|
|
3907
|
+
},
|
|
3908
|
+
onPlanningEnd: ({ progress }) => updateProgress(state, progress),
|
|
3909
|
+
onPrepareStart: ({ startedAt }) => {
|
|
3910
|
+
state.activeJobs.clear();
|
|
3911
|
+
state.errorCount = 0;
|
|
3912
|
+
state.execution = createCounts();
|
|
3913
|
+
state.progress = createProgress();
|
|
3914
|
+
state.ruleGroups.clear();
|
|
3915
|
+
state.runStartedAt = startedAt ?? now();
|
|
3916
|
+
state.animationTick = 0;
|
|
3917
|
+
state.cachedTokens = 0;
|
|
3918
|
+
state.jobTokens.clear();
|
|
3919
|
+
state.totalTokens = 0;
|
|
3920
|
+
state.warnCount = 0;
|
|
3921
|
+
},
|
|
3922
|
+
onRunEnd: (payload) => {
|
|
3923
|
+
state.warnCount = payload.diagnostics.filter((diagnostic) => diagnostic.severity === "warn").length;
|
|
3924
|
+
state.errorCount = payload.diagnostics.length - state.warnCount;
|
|
3925
|
+
updateProgress(state, payload.progress);
|
|
3926
|
+
state.totalTokens = payload.usage.totalTokens;
|
|
3927
|
+
state.cachedTokens = payload.usage.cached?.totalTokens ?? 0;
|
|
3928
|
+
state.runStartedAt = payload.startedAt ?? state.runStartedAt;
|
|
3929
|
+
},
|
|
3930
|
+
onUsage: ({ job, progress, record }) => {
|
|
3931
|
+
updateProgress(state, progress);
|
|
3932
|
+
if (record.totalTokens != null && Number.isFinite(record.totalTokens)) {
|
|
3933
|
+
state.jobTokens.set(job.id, (state.jobTokens.get(job.id) ?? 0) + record.totalTokens);
|
|
3934
|
+
state.totalTokens += record.totalTokens;
|
|
3935
|
+
}
|
|
3936
|
+
},
|
|
3937
|
+
/**
|
|
3938
|
+
* Advances the summary animation on each renderer interval.
|
|
3939
|
+
*
|
|
3940
|
+
* Triggering workflow:
|
|
3941
|
+
*
|
|
3942
|
+
* `createTtyProgressRenderer`
|
|
3943
|
+
* -> `createCliProgressReporter` `createInterval` callback
|
|
3944
|
+
* -> `SummaryProgressReporter.tick` (this handler)
|
|
3945
|
+
*
|
|
3946
|
+
* Upstream:
|
|
3947
|
+
* - `createCliProgressReporter` `createInterval` callback registered by `createTtyProgressRenderer`
|
|
3948
|
+
*
|
|
3949
|
+
* Downstream:
|
|
3950
|
+
* - Increments `state.animationTick`, consumed by {@link createRows}, {@link formatSpinnerFrame}, and {@link formatMiniBarSegment}.
|
|
3951
|
+
*/
|
|
3952
|
+
tick: () => {
|
|
3953
|
+
state.animationTick += 1;
|
|
3954
|
+
}
|
|
3293
3955
|
};
|
|
3294
|
-
row.inTok += inTok;
|
|
3295
|
-
row.outTok += outTok;
|
|
3296
|
-
row.totalTok += totalTok;
|
|
3297
|
-
if (countRun) row.runs += 1;
|
|
3298
|
-
rows.set(key, row);
|
|
3299
|
-
return row;
|
|
3300
|
-
}
|
|
3301
|
-
function bucketLabel(startMs, interval) {
|
|
3302
|
-
const date = new Date(startMs);
|
|
3303
|
-
if (interval === "month") return MONTH_LABELS$1[date.getUTCMonth()];
|
|
3304
|
-
return `${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
|
|
3305
|
-
}
|
|
3306
|
-
function bucketStart(ts, interval) {
|
|
3307
|
-
const date = new Date(ts);
|
|
3308
|
-
if (interval === "month") return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1);
|
|
3309
|
-
const dayStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
3310
|
-
if (interval === "week") return dayStart - (new Date(dayStart).getUTCDay() + 6) % 7 * DAY_MS;
|
|
3311
|
-
return dayStart;
|
|
3312
|
-
}
|
|
3313
|
-
function chooseInterval(since, nowMs, timestamps) {
|
|
3314
|
-
if (since !== void 0) return intervalForDuration(nowMs - since);
|
|
3315
|
-
if (timestamps.length < 2) return "day";
|
|
3316
|
-
return intervalForDuration(Math.max(...timestamps) - Math.min(...timestamps));
|
|
3317
3956
|
}
|
|
3318
|
-
function
|
|
3319
|
-
|
|
3320
|
-
const
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
inTok: 0,
|
|
3325
|
-
key: bucketLabel(startMs, interval),
|
|
3326
|
-
outTok: 0,
|
|
3327
|
-
runs: 0,
|
|
3328
|
-
startMs,
|
|
3329
|
-
totalTok: 0
|
|
3330
|
-
});
|
|
3331
|
-
return filled;
|
|
3957
|
+
function compareRuleGroups(left, right) {
|
|
3958
|
+
const leftActive = left.jobs.some((job) => job.state === "running") ? 1 : 0;
|
|
3959
|
+
const rightActive = right.jobs.some((job) => job.state === "running") ? 1 : 0;
|
|
3960
|
+
if (leftActive !== rightActive) return rightActive - leftActive;
|
|
3961
|
+
if (left.failed !== right.failed) return right.failed - left.failed;
|
|
3962
|
+
return left.firstJobIndex - right.firstJobIndex;
|
|
3332
3963
|
}
|
|
3333
|
-
function
|
|
3334
|
-
const
|
|
3335
|
-
|
|
3336
|
-
if (
|
|
3337
|
-
return
|
|
3964
|
+
function compareRunningJobs(left, right) {
|
|
3965
|
+
const leftStartedAt = left.startedAt ?? Number.POSITIVE_INFINITY;
|
|
3966
|
+
const rightStartedAt = right.startedAt ?? Number.POSITIVE_INFINITY;
|
|
3967
|
+
if (leftStartedAt !== rightStartedAt) return leftStartedAt - rightStartedAt;
|
|
3968
|
+
return left.job.index - right.job.index;
|
|
3338
3969
|
}
|
|
3339
|
-
function
|
|
3340
|
-
|
|
3970
|
+
function completeGraphemeBoundary(input, maximumIndex) {
|
|
3971
|
+
let boundary = 0;
|
|
3972
|
+
for (const segment of graphemeSegmenter.segment(input)) {
|
|
3973
|
+
const end = segment.index + segment.segment.length;
|
|
3974
|
+
if (end > maximumIndex) break;
|
|
3975
|
+
boundary = end;
|
|
3976
|
+
}
|
|
3977
|
+
return boundary;
|
|
3341
3978
|
}
|
|
3342
|
-
function
|
|
3343
|
-
|
|
3344
|
-
|
|
3979
|
+
function createCounts(planned = 0) {
|
|
3980
|
+
return {
|
|
3981
|
+
cached: 0,
|
|
3982
|
+
cancelled: 0,
|
|
3983
|
+
completed: 0,
|
|
3984
|
+
failed: 0,
|
|
3985
|
+
planned,
|
|
3986
|
+
queued: 0,
|
|
3987
|
+
running: 0,
|
|
3988
|
+
skipped: 0
|
|
3989
|
+
};
|
|
3345
3990
|
}
|
|
3346
|
-
function
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3991
|
+
function createInitialState() {
|
|
3992
|
+
return {
|
|
3993
|
+
activeJobs: /* @__PURE__ */ new Map(),
|
|
3994
|
+
animationTick: 0,
|
|
3995
|
+
cachedTokens: 0,
|
|
3996
|
+
errorCount: 0,
|
|
3997
|
+
execution: createCounts(),
|
|
3998
|
+
jobTokens: /* @__PURE__ */ new Map(),
|
|
3999
|
+
progress: createProgress(),
|
|
4000
|
+
ruleGroups: /* @__PURE__ */ new Map(),
|
|
4001
|
+
totalTokens: 0,
|
|
4002
|
+
warnCount: 0
|
|
4003
|
+
};
|
|
3352
4004
|
}
|
|
3353
|
-
function
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
4005
|
+
function createProgress() {
|
|
4006
|
+
return {
|
|
4007
|
+
execution: createCounts(),
|
|
4008
|
+
filesPlanned: 0,
|
|
4009
|
+
filesTotal: 0,
|
|
4010
|
+
jobsCompleted: 0,
|
|
4011
|
+
jobsStarted: 0,
|
|
4012
|
+
jobsTotal: 0,
|
|
4013
|
+
planningComplete: false
|
|
4014
|
+
};
|
|
3361
4015
|
}
|
|
3362
|
-
|
|
3363
|
-
if (
|
|
3364
|
-
const
|
|
3365
|
-
const
|
|
3366
|
-
|
|
4016
|
+
function createRows(state, options, now) {
|
|
4017
|
+
if (options.rows !== void 0 && options.rows <= 0) return [];
|
|
4018
|
+
const groups = createRuleGroups(state);
|
|
4019
|
+
const footerRows = options.rows === void 0 ? 3 : Math.min(options.rows, 3);
|
|
4020
|
+
const separatorRows = options.rows === void 0 || options.rows - footerRows > 1 ? 1 : 0;
|
|
4021
|
+
const visible = formatContentRows(groups, state, options, now, options.rows === void 0 ? Number.POSITIVE_INFINITY : Math.max(options.rows - footerRows - separatorRows, 0));
|
|
4022
|
+
const footer = formatFooters(state, options, now);
|
|
4023
|
+
const actualSeparatorRows = visible.length > 0 ? separatorRows : 0;
|
|
4024
|
+
const visibleFooters = options.rows === void 0 ? footer : footer.slice(0, Math.max(options.rows - visible.length - actualSeparatorRows, 0));
|
|
4025
|
+
if (actualSeparatorRows === 1) visible.push("");
|
|
4026
|
+
visible.push(...visibleFooters);
|
|
4027
|
+
return options.color ? visible.map((row) => styleRow(row, state, state.warnCount, state.errorCount, options)) : visible;
|
|
3367
4028
|
}
|
|
3368
|
-
|
|
3369
|
-
const dimension = filter.by ?? "model";
|
|
3370
|
-
const since = parseSince(filter.since, now());
|
|
3371
|
-
const rules = ruleSet(filter.rules);
|
|
3372
|
-
const runs = await readRuns(dir);
|
|
3373
|
-
const rows = /* @__PURE__ */ new Map();
|
|
3374
|
-
let totalRuns = 0;
|
|
3375
|
-
let totalIn = 0;
|
|
3376
|
-
let totalOut = 0;
|
|
3377
|
-
let totalTok = 0;
|
|
3378
|
-
for (const run of runs) {
|
|
3379
|
-
if (!runInWindow(run, since, filter.cwd)) continue;
|
|
3380
|
-
const slice = sliceRun(run, rules);
|
|
3381
|
-
if (!slice.touched) continue;
|
|
3382
|
-
totalRuns += 1;
|
|
3383
|
-
totalIn += slice.inTok;
|
|
3384
|
-
totalOut += slice.outTok;
|
|
3385
|
-
totalTok += slice.totalTok;
|
|
3386
|
-
if (dimension === "dir") {
|
|
3387
|
-
accumulate(rows, run.cwd, slice.inTok, slice.outTok, slice.totalTok, true);
|
|
3388
|
-
continue;
|
|
3389
|
-
}
|
|
3390
|
-
const seenKeys = /* @__PURE__ */ new Set();
|
|
3391
|
-
for (const usageRecord of slice.usageRecords) {
|
|
3392
|
-
const key = recordKey(usageRecord, dimension);
|
|
3393
|
-
accumulate(rows, key, usageRecord.inTok, usageRecord.outTok, usageRecord.totalTok, !seenKeys.has(key));
|
|
3394
|
-
seenKeys.add(key);
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
4029
|
+
function createRuleGroup(job) {
|
|
3397
4030
|
return {
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
4031
|
+
cached: 0,
|
|
4032
|
+
cancelled: 0,
|
|
4033
|
+
completed: 0,
|
|
4034
|
+
failed: 0,
|
|
4035
|
+
firstJobIndex: job.index,
|
|
4036
|
+
planned: 0,
|
|
4037
|
+
ruleId: job.ruleId,
|
|
4038
|
+
skipped: 0,
|
|
4039
|
+
terminalDurationMs: 0
|
|
3404
4040
|
};
|
|
3405
4041
|
}
|
|
3406
|
-
|
|
3407
|
-
const
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
const
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
4042
|
+
function createRuleGroups(state) {
|
|
4043
|
+
const groups = new Map([...state.ruleGroups].map(([ruleId, group]) => [ruleId, {
|
|
4044
|
+
...group,
|
|
4045
|
+
jobs: []
|
|
4046
|
+
}]));
|
|
4047
|
+
for (const job of state.activeJobs.values()) groups.get(job.job.ruleId)?.jobs.push(job);
|
|
4048
|
+
return [...groups.values()].sort(compareRuleGroups);
|
|
4049
|
+
}
|
|
4050
|
+
function estimateEta(durationMs, terminal, planned) {
|
|
4051
|
+
if (terminal <= 0 || planned <= 0 || durationMs <= 0) return "eta ?";
|
|
4052
|
+
return `eta ~${formatDuration$1(durationMs * (planned - terminal) / terminal)}`;
|
|
4053
|
+
}
|
|
4054
|
+
function fitRow(row, columns) {
|
|
4055
|
+
if (columns <= 0) return "";
|
|
4056
|
+
if (fastStringWidth(row) <= columns) return row;
|
|
4057
|
+
let limit = columns;
|
|
4058
|
+
while (limit > 0) {
|
|
4059
|
+
const result = fastStringTruncatedWidth(row, {
|
|
4060
|
+
ellipsis: "…",
|
|
4061
|
+
limit
|
|
3418
4062
|
});
|
|
4063
|
+
const index = completeGraphemeBoundary(row, result.index);
|
|
4064
|
+
const visible = row.slice(0, index);
|
|
4065
|
+
const fitted = `${visible}${visible.includes("\x1B") ? "\x1B[0m" : ""}${result.ellipsed ? "…" : ""}`;
|
|
4066
|
+
const overflow = fastStringWidth(fitted) - columns;
|
|
4067
|
+
if (overflow <= 0) return fitted;
|
|
4068
|
+
limit -= overflow;
|
|
3419
4069
|
}
|
|
3420
|
-
|
|
3421
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
3422
|
-
let totalRuns = 0;
|
|
3423
|
-
let totalIn = 0;
|
|
3424
|
-
let totalOut = 0;
|
|
3425
|
-
let totalTok = 0;
|
|
3426
|
-
for (const { slice, ts } of sliced) {
|
|
3427
|
-
const startMs = bucketStart(ts, interval);
|
|
3428
|
-
const bucket = buckets.get(startMs) ?? {
|
|
3429
|
-
inTok: 0,
|
|
3430
|
-
key: bucketLabel(startMs, interval),
|
|
3431
|
-
outTok: 0,
|
|
3432
|
-
runs: 0,
|
|
3433
|
-
startMs,
|
|
3434
|
-
totalTok: 0
|
|
3435
|
-
};
|
|
3436
|
-
bucket.runs += 1;
|
|
3437
|
-
bucket.inTok += slice.inTok;
|
|
3438
|
-
bucket.outTok += slice.outTok;
|
|
3439
|
-
bucket.totalTok += slice.totalTok;
|
|
3440
|
-
buckets.set(startMs, bucket);
|
|
3441
|
-
totalRuns += 1;
|
|
3442
|
-
totalIn += slice.inTok;
|
|
3443
|
-
totalOut += slice.outTok;
|
|
3444
|
-
totalTok += slice.totalTok;
|
|
3445
|
-
}
|
|
3446
|
-
return {
|
|
3447
|
-
buckets: fillBuckets(buckets, interval),
|
|
3448
|
-
interval,
|
|
3449
|
-
totalIn,
|
|
3450
|
-
totalOut,
|
|
3451
|
-
totalRuns,
|
|
3452
|
-
totalTok
|
|
3453
|
-
};
|
|
4070
|
+
return "";
|
|
3454
4071
|
}
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
4072
|
+
function formatCollapsedRunningRow(hidden, options) {
|
|
4073
|
+
return fitRow(` └─ ${hidden} more running`, options.columns);
|
|
4074
|
+
}
|
|
4075
|
+
function formatContentRows(groups, state, options, now, contentBudget) {
|
|
4076
|
+
const rows = [];
|
|
4077
|
+
const renderableGroups = groups.filter((group) => group.jobs.some((job) => job.state === "running") || group.failed > 0);
|
|
4078
|
+
const runningCount = renderableGroups.reduce((sum, group) => sum + group.jobs.filter((job) => job.state === "running").length, 0);
|
|
4079
|
+
if (contentBudget === 1) {
|
|
4080
|
+
const failedGroup = renderableGroups.find((group) => group.failed > 0);
|
|
4081
|
+
if (failedGroup) return [formatRuleRow(failedGroup, state, options)];
|
|
4082
|
+
if (runningCount > 0) return [formatCollapsedRunningRow(runningCount, options)];
|
|
4083
|
+
}
|
|
4084
|
+
for (const [groupIndex, group] of renderableGroups.entries()) {
|
|
4085
|
+
if (rows.length >= contentBudget) break;
|
|
4086
|
+
const groupStart = rows.length;
|
|
4087
|
+
rows.push(formatRuleRow(group, state, options));
|
|
4088
|
+
const runningJobs = group.jobs.filter((job) => job.state === "running").sort(compareRunningJobs);
|
|
4089
|
+
const remainingGroups = renderableGroups.length - groupIndex - 1;
|
|
4090
|
+
for (const [index, job] of runningJobs.entries()) {
|
|
4091
|
+
const rowsReservedForLaterGroups = Math.min(remainingGroups, Math.max(contentBudget - rows.length, 0));
|
|
4092
|
+
if (rows.length >= contentBudget) {
|
|
4093
|
+
const hidden = runningJobs.length - index;
|
|
4094
|
+
if (rows.length > groupStart + 1 && hidden > 0) rows[rows.length - 1] = formatCollapsedRunningRow(hidden, options);
|
|
4095
|
+
break;
|
|
4096
|
+
}
|
|
4097
|
+
if (rows.length + 1 + rowsReservedForLaterGroups >= contentBudget) {
|
|
4098
|
+
if (runningJobs.length > 1) rows.push(formatCollapsedRunningRow(runningJobs.length - index, options));
|
|
4099
|
+
break;
|
|
4100
|
+
}
|
|
4101
|
+
rows.push(formatTargetRow(job, options, now, index === runningJobs.length - 1));
|
|
3464
4102
|
}
|
|
3465
4103
|
}
|
|
3466
|
-
return
|
|
4104
|
+
return rows;
|
|
3467
4105
|
}
|
|
3468
|
-
|
|
3469
|
-
const
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
await prune(dir, ts, retentionMonths);
|
|
4106
|
+
function formatDuration$1(ms) {
|
|
4107
|
+
const durationMs = Math.max(ms, 0);
|
|
4108
|
+
if (durationMs < 6e4) return `${(durationMs / 1e3).toFixed(1)}s`;
|
|
4109
|
+
return formatDuration(intervalToDuration({
|
|
4110
|
+
end: durationMs,
|
|
4111
|
+
start: 0
|
|
4112
|
+
}));
|
|
3476
4113
|
}
|
|
3477
|
-
function
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
4114
|
+
function formatFooters(state, options, now) {
|
|
4115
|
+
const terminal = terminalCount(state.execution);
|
|
4116
|
+
const elapsed = state.runStartedAt === void 0 ? 0 : now - state.runStartedAt;
|
|
4117
|
+
const progressBar = state.progress.planningComplete ? formatMiniBarSegment(terminal, state.execution.planned, state.animationTick, options) : "";
|
|
4118
|
+
const eta = state.progress.planningComplete ? estimateEta(elapsed, terminal, state.execution.planned) : "eta ?";
|
|
4119
|
+
const projectedTokens = state.progress.planningComplete && terminal > 0 && state.execution.planned > 0 ? Math.ceil((state.totalTokens + state.cachedTokens) * state.execution.planned / terminal) : void 0;
|
|
4120
|
+
const discovering = state.progress.planningComplete ? "" : " jobs (discovering)";
|
|
4121
|
+
return [
|
|
4122
|
+
fitRow(`${terminal}/${state.execution.planned}${discovering}${progressBar} ${formatDuration$1(elapsed)} -> ${eta === "eta ?" ? "~?" : eta.replace("eta ", "")}`, options.columns),
|
|
4123
|
+
fitRow(`${state.execution.running} concurrency / ${state.execution.queued} queued / ${state.execution.cached} cached / ${state.execution.failed} failed`, options.columns),
|
|
4124
|
+
fitRow(`${state.totalTokens.toLocaleString("en-US")} tokens (${state.cachedTokens.toLocaleString("en-US")} cached) -> ${projectedTokens === void 0 ? "~?" : `~${projectedTokens.toLocaleString("en-US")} tokens`}`, options.columns)
|
|
4125
|
+
];
|
|
3481
4126
|
}
|
|
3482
|
-
function
|
|
3483
|
-
|
|
4127
|
+
function formatMiniBarSegment(completed, planned, tick, options) {
|
|
4128
|
+
if (options.columns < 60) return "";
|
|
4129
|
+
return ` ${formatMiniBar({
|
|
4130
|
+
completed,
|
|
4131
|
+
planned,
|
|
4132
|
+
tick,
|
|
4133
|
+
width: 10
|
|
4134
|
+
})}`;
|
|
3484
4135
|
}
|
|
3485
|
-
function
|
|
3486
|
-
|
|
4136
|
+
function formatRuleRow(group, state, options) {
|
|
4137
|
+
const spinner = formatSpinnerFrame(state.animationTick, options.spinnerFrames);
|
|
4138
|
+
const terminal = group.completed + group.cached + group.failed + group.cancelled + group.skipped;
|
|
4139
|
+
const percent = group.planned > 0 ? Math.floor(terminal / group.planned * 100) : 0;
|
|
4140
|
+
const bar = formatMiniBarSegment(terminal, group.planned, state.animationTick, options);
|
|
4141
|
+
const running = group.jobs.filter((job) => job.state === "running").length;
|
|
4142
|
+
const failed = group.failed > 0 ? ` ${group.failed} failed` : "";
|
|
4143
|
+
const eta = estimateEta(group.terminalDurationMs, terminal, group.planned);
|
|
4144
|
+
return fitRow(`${spinner} ${group.ruleId} ${terminal}/${group.planned} ${percent}%${bar} ${eta} ${running} running${failed}`, options.columns);
|
|
3487
4145
|
}
|
|
3488
|
-
function
|
|
3489
|
-
|
|
3490
|
-
inTok: run.usage.inTok,
|
|
3491
|
-
outTok: run.usage.outTok,
|
|
3492
|
-
totalTok: run.usage.totalTok,
|
|
3493
|
-
touched: true,
|
|
3494
|
-
usageRecords: run.usage.records
|
|
3495
|
-
};
|
|
3496
|
-
const usageRecords = run.usage.records.filter((record) => rules.has(record.ruleId));
|
|
3497
|
-
return {
|
|
3498
|
-
inTok: sumBy(usageRecords, (record) => record.inTok),
|
|
3499
|
-
outTok: sumBy(usageRecords, (record) => record.outTok),
|
|
3500
|
-
totalTok: sumBy(usageRecords, (record) => record.totalTok),
|
|
3501
|
-
touched: usageRecords.length > 0,
|
|
3502
|
-
usageRecords
|
|
3503
|
-
};
|
|
4146
|
+
function formatSpinnerFrame(animationTick, spinnerFrames) {
|
|
4147
|
+
return spinnerFrames[animationTick % Math.max(spinnerFrames.length, 1)] ?? "";
|
|
3504
4148
|
}
|
|
3505
|
-
function
|
|
3506
|
-
const
|
|
3507
|
-
|
|
4149
|
+
function formatTargetRow(jobState, options, now, last) {
|
|
4150
|
+
const inputPath = options.cwd ? relative$1(options.cwd, jobState.job.inputPath) || "." : jobState.job.inputPath;
|
|
4151
|
+
const target = jobState.job.target.name ? `${jobState.job.target.kind} ${jobState.job.target.name}` : jobState.job.target.kind;
|
|
4152
|
+
const startedAt = jobState.retry?.startedAt ?? jobState.startedAt ?? now;
|
|
4153
|
+
return fitRow(`${last ? " └─" : " ├─"} ${inputPath} > ${target} ${jobState.retry ? `${jobState.retry.attempt}/${jobState.retry.maxAttempts} retrying elapsed ${formatDuration$1(now - startedAt)}` : `running ${formatDuration$1(now - startedAt)}`}`, options.columns);
|
|
3508
4154
|
}
|
|
3509
|
-
|
|
3510
|
-
let
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
}
|
|
3514
|
-
if (isEnoent(error)) return [];
|
|
3515
|
-
throw error;
|
|
3516
|
-
}
|
|
3517
|
-
return entries.map((name) => {
|
|
3518
|
-
const match = STATS_FILE_PATTERN.exec(name);
|
|
3519
|
-
return match ? {
|
|
3520
|
-
month: Number(match[1]) * 12 + (Number(match[2]) - 1),
|
|
3521
|
-
name
|
|
3522
|
-
} : void 0;
|
|
3523
|
-
}).filter((entry) => entry !== void 0).sort((left, right) => left.month - right.month);
|
|
4155
|
+
function styleRow(row, state, warnCount, errorCount, options) {
|
|
4156
|
+
let styledRow = row;
|
|
4157
|
+
const spinner = formatSpinnerFrame(state.animationTick, options.spinnerFrames);
|
|
4158
|
+
if (spinner) styledRow = styledRow.replace(spinner, colors$2.cyan(spinner));
|
|
4159
|
+
return styledRow.replace(`${state.execution.running} concurrency`, colors$2.cyan(`${state.execution.running} concurrency`)).replace(`${state.totalTokens.toLocaleString("en-US")} tokens`, colors$2.cyan(`${state.totalTokens.toLocaleString("en-US")} tokens`)).replace(`(${state.cachedTokens.toLocaleString("en-US")} cached)`, colors$2.dim(`(${state.cachedTokens.toLocaleString("en-US")} cached)`)).replace(` / ${state.execution.cached} cached / `, ` / ${colors$2.dim(`${state.execution.cached} cached`)} / `).replace(`${warnCount} warn`, colors$2.yellow(`${warnCount} warn`)).replace(`${errorCount} error`, (errorCount > 0 ? colors$2.red : colors$2.gray)(`${errorCount} error`)).replace(`${state.execution.failed} failed`, (state.execution.failed > 0 ? colors$2.red : colors$2.gray)(`${state.execution.failed} failed`)).replace(/\[([█▓░]*?)(░+)\]/g, (_, active, pending) => `[${active}${colors$2.gray(pending)}]`).replace(/\//g, colors$2.gray("/"));
|
|
3524
4160
|
}
|
|
3525
|
-
function
|
|
3526
|
-
|
|
3527
|
-
for (const item of items) total += select(item);
|
|
3528
|
-
return total;
|
|
4161
|
+
function terminalCount(counts) {
|
|
4162
|
+
return counts.completed + counts.failed + counts.cached + counts.skipped + counts.cancelled;
|
|
3529
4163
|
}
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
durationMs: input.durationMs,
|
|
3536
|
-
ruleCounts: input.ruleCounts,
|
|
3537
|
-
usage: {
|
|
3538
|
-
inTok: input.usage.inputTokens,
|
|
3539
|
-
outTok: input.usage.outputTokens,
|
|
3540
|
-
records: input.usage.records.map(toStatsUsageRecord),
|
|
3541
|
-
totalTok: input.usage.totalTokens
|
|
3542
|
-
}
|
|
4164
|
+
function updateProgress(state, progress) {
|
|
4165
|
+
state.execution = { ...progress.execution };
|
|
4166
|
+
state.progress = {
|
|
4167
|
+
...progress,
|
|
4168
|
+
execution: { ...progress.execution }
|
|
3543
4169
|
};
|
|
3544
4170
|
}
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
4171
|
+
//#endregion
|
|
4172
|
+
//#region src/cli/reporters/progress/tty.ts
|
|
4173
|
+
const clearCurrentLine = "\r\x1B[K";
|
|
4174
|
+
const clearPreviousLine = "\r\x1B[1A\x1B[K";
|
|
4175
|
+
function createTtyProgressRenderer(options) {
|
|
4176
|
+
let interval;
|
|
4177
|
+
let previousRows = 0;
|
|
4178
|
+
const clearPreviousFrame = () => {
|
|
4179
|
+
if (previousRows === 0) return;
|
|
4180
|
+
let sequence = clearCurrentLine;
|
|
4181
|
+
for (let row = 1; row < previousRows; row += 1) sequence += clearPreviousLine;
|
|
4182
|
+
options.write(sequence);
|
|
4183
|
+
previousRows = 0;
|
|
4184
|
+
};
|
|
4185
|
+
const render = () => {
|
|
4186
|
+
clearPreviousFrame();
|
|
4187
|
+
const rows = options.getRows();
|
|
4188
|
+
if (rows.length === 0) return;
|
|
4189
|
+
options.write(rows.join("\n"));
|
|
4190
|
+
previousRows = rows.length;
|
|
4191
|
+
};
|
|
4192
|
+
const write = (chunk) => {
|
|
4193
|
+
const wasRendering = interval !== void 0;
|
|
4194
|
+
clearPreviousFrame();
|
|
4195
|
+
options.write(chunk);
|
|
4196
|
+
if (wasRendering) render();
|
|
4197
|
+
};
|
|
3551
4198
|
return {
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
4199
|
+
finish: () => {
|
|
4200
|
+
if (interval) {
|
|
4201
|
+
options.clearInterval(interval);
|
|
4202
|
+
interval = void 0;
|
|
4203
|
+
}
|
|
4204
|
+
clearPreviousFrame();
|
|
4205
|
+
},
|
|
4206
|
+
render,
|
|
4207
|
+
start: () => {
|
|
4208
|
+
if (!interval) {
|
|
4209
|
+
interval = options.createInterval(render, options.intervalMs);
|
|
4210
|
+
if (isUnrefableInterval(interval)) interval.unref();
|
|
4211
|
+
}
|
|
4212
|
+
render();
|
|
4213
|
+
},
|
|
4214
|
+
write
|
|
3560
4215
|
};
|
|
3561
4216
|
}
|
|
4217
|
+
function isUnrefableInterval(interval) {
|
|
4218
|
+
if (typeof interval !== "object" || interval === null || !("unref" in interval)) return false;
|
|
4219
|
+
return typeof interval.unref === "function";
|
|
4220
|
+
}
|
|
3562
4221
|
//#endregion
|
|
3563
|
-
//#region src/cli/
|
|
3564
|
-
function
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
failed: 0,
|
|
3570
|
-
planned: 0
|
|
4222
|
+
//#region src/cli/reporters/progress/index.ts
|
|
4223
|
+
function createCliProgressReporter(options) {
|
|
4224
|
+
if (!options.isTty) return {
|
|
4225
|
+
dispose: () => {},
|
|
4226
|
+
reporter: createPlainProgressReporter({ write: options.write }),
|
|
4227
|
+
write: options.write
|
|
3571
4228
|
};
|
|
3572
|
-
|
|
3573
|
-
|
|
4229
|
+
const summary = createSummaryProgressReporter({
|
|
4230
|
+
color: options.color,
|
|
4231
|
+
columns: options.columns,
|
|
4232
|
+
cwd: options.cwd,
|
|
4233
|
+
rows: options.rows,
|
|
4234
|
+
spinnerFrames: cliSpinners.dots.frames
|
|
4235
|
+
});
|
|
4236
|
+
const renderer = createTtyProgressRenderer({
|
|
4237
|
+
clearInterval: (handle) => globalThis.clearInterval(handle),
|
|
4238
|
+
createInterval: (callback, intervalMs) => globalThis.setInterval(() => {
|
|
4239
|
+
summary.tick();
|
|
4240
|
+
callback();
|
|
4241
|
+
}, intervalMs),
|
|
4242
|
+
getRows: summary.getRows,
|
|
4243
|
+
intervalMs: 120,
|
|
4244
|
+
write: options.write
|
|
4245
|
+
});
|
|
4246
|
+
const reporter = createRenderingProgressReporter(summary, renderer);
|
|
3574
4247
|
return {
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
onPrepareStart: (payload) => {
|
|
3579
|
-
startedAt = payload.startedAt;
|
|
3580
|
-
},
|
|
3581
|
-
onRunEnd: (payload) => {
|
|
3582
|
-
counts.cached = payload.execution.cached;
|
|
3583
|
-
counts.cancelled = payload.execution.cancelled;
|
|
3584
|
-
counts.completed = payload.execution.completed;
|
|
3585
|
-
counts.failed = payload.execution.failed;
|
|
3586
|
-
counts.planned = payload.execution.planned;
|
|
3587
|
-
endedAt = payload.endedAt;
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
4248
|
+
dispose: renderer.finish,
|
|
4249
|
+
reporter,
|
|
4250
|
+
write: renderer.write
|
|
3590
4251
|
};
|
|
3591
4252
|
}
|
|
3592
|
-
function
|
|
3593
|
-
if (!base) return extra;
|
|
3594
|
-
if (!extra) return base;
|
|
3595
|
-
const baseState = {
|
|
3596
|
-
disabled: false,
|
|
3597
|
-
reporter: base
|
|
3598
|
-
};
|
|
3599
|
-
const extraState = {
|
|
3600
|
-
disabled: false,
|
|
3601
|
-
reporter: extra
|
|
3602
|
-
};
|
|
3603
|
-
let firstFailure;
|
|
3604
|
-
const deliver = (state, callback) => {
|
|
3605
|
-
if (state.disabled) return;
|
|
3606
|
-
try {
|
|
3607
|
-
callback(state.reporter);
|
|
3608
|
-
} catch (error) {
|
|
3609
|
-
state.disabled = true;
|
|
3610
|
-
firstFailure ??= normalizeReporterError(error);
|
|
3611
|
-
}
|
|
3612
|
-
};
|
|
3613
|
-
const deliverBoth = (callback) => {
|
|
3614
|
-
deliver(baseState, callback);
|
|
3615
|
-
deliver(extraState, callback);
|
|
3616
|
-
};
|
|
4253
|
+
function createRenderingProgressReporter(summary, renderer) {
|
|
3617
4254
|
return {
|
|
3618
4255
|
onDiagnostic: (payload) => {
|
|
3619
|
-
|
|
4256
|
+
summary.onDiagnostic?.(payload);
|
|
4257
|
+
renderer.render();
|
|
3620
4258
|
},
|
|
3621
4259
|
onExecuteEnd: (payload) => {
|
|
3622
|
-
|
|
4260
|
+
summary.onExecuteEnd?.(payload);
|
|
4261
|
+
renderer.render();
|
|
3623
4262
|
},
|
|
3624
4263
|
onExecuteStart: (payload) => {
|
|
3625
|
-
|
|
4264
|
+
summary.onExecuteStart?.(payload);
|
|
4265
|
+
renderer.render();
|
|
3626
4266
|
},
|
|
3627
4267
|
onFileReady: (payload) => {
|
|
3628
|
-
|
|
4268
|
+
summary.onFileReady?.(payload);
|
|
4269
|
+
renderer.render();
|
|
3629
4270
|
},
|
|
3630
4271
|
onJobEnd: (payload) => {
|
|
3631
|
-
|
|
4272
|
+
summary.onJobEnd?.(payload);
|
|
4273
|
+
renderer.render();
|
|
3632
4274
|
},
|
|
3633
4275
|
onJobQueued: (payload) => {
|
|
3634
|
-
|
|
4276
|
+
summary.onJobQueued?.(payload);
|
|
4277
|
+
renderer.render();
|
|
3635
4278
|
},
|
|
3636
4279
|
onJobRetry: (payload) => {
|
|
3637
|
-
|
|
4280
|
+
summary.onJobRetry?.(payload);
|
|
4281
|
+
renderer.render();
|
|
3638
4282
|
},
|
|
3639
4283
|
onJobStart: (payload) => {
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
onPlanningEnd: (payload) => {
|
|
3643
|
-
deliverBoth((reporter) => reporter.onPlanningEnd?.(payload));
|
|
4284
|
+
summary.onJobStart?.(payload);
|
|
4285
|
+
renderer.render();
|
|
3644
4286
|
},
|
|
3645
4287
|
onPrepareEnd: (payload) => {
|
|
3646
|
-
|
|
4288
|
+
summary.onPrepareEnd?.(payload);
|
|
4289
|
+
renderer.render();
|
|
3647
4290
|
},
|
|
3648
4291
|
onPrepareStart: (payload) => {
|
|
3649
|
-
|
|
4292
|
+
summary.onPrepareStart?.(payload);
|
|
4293
|
+
renderer.start();
|
|
3650
4294
|
},
|
|
3651
4295
|
onRunEnd: (payload) => {
|
|
3652
|
-
|
|
3653
|
-
|
|
4296
|
+
summary.onRunEnd?.(payload);
|
|
4297
|
+
renderer.render();
|
|
3654
4298
|
},
|
|
3655
4299
|
onUsage: (payload) => {
|
|
3656
|
-
|
|
4300
|
+
summary.onUsage?.(payload);
|
|
4301
|
+
renderer.render();
|
|
3657
4302
|
}
|
|
3658
4303
|
};
|
|
3659
4304
|
}
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
return {
|
|
3665
|
-
dir: config?.location ?? getStatsDir(env),
|
|
3666
|
-
retentionMonths: config?.retentionMonths
|
|
3667
|
-
};
|
|
4305
|
+
//#endregion
|
|
4306
|
+
//#region src/cli/commands/lint/errors.ts
|
|
4307
|
+
const colors$1 = createColors({ force: true });
|
|
4308
|
+
function formatCancelledError(error, color) {
|
|
4309
|
+
return `${color ? colors$1.red("error") : "error"} ${error.message}\n`;
|
|
3668
4310
|
}
|
|
3669
|
-
|
|
3670
|
-
const
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
} catch {}
|
|
4311
|
+
function formatRunError(error, color) {
|
|
4312
|
+
const label = color ? colors$1.red("error") : "error";
|
|
4313
|
+
const fileFailures = error.failures.filter((failure) => "file" in failure);
|
|
4314
|
+
const groups = groupFailuresByRule(error.failures.filter((failure) => "job" in failure));
|
|
4315
|
+
return [
|
|
4316
|
+
`${label} ${error.message}`,
|
|
4317
|
+
"",
|
|
4318
|
+
...fileFailures.length > 0 ? [
|
|
4319
|
+
`Failed Files ${fileFailures.length}`,
|
|
4320
|
+
"",
|
|
4321
|
+
...formatFileFailures(fileFailures, color)
|
|
4322
|
+
] : [],
|
|
4323
|
+
...groups.length > 0 ? [
|
|
4324
|
+
`Failed Rules ${groups.length}`,
|
|
4325
|
+
"",
|
|
4326
|
+
...formatFailureGroups(groups, color)
|
|
4327
|
+
] : []
|
|
4328
|
+
].join("\n");
|
|
3688
4329
|
}
|
|
3689
|
-
function
|
|
3690
|
-
|
|
3691
|
-
|
|
4330
|
+
function formatFailureGroups(groups, color) {
|
|
4331
|
+
const lines = [];
|
|
4332
|
+
for (const [ruleId, group] of groups) {
|
|
4333
|
+
const failLabel = color ? colors$1.bgRed(colors$1.bold(" FAIL ")) : "FAIL";
|
|
4334
|
+
const targetLabel = group.length === 1 ? "target" : "targets";
|
|
4335
|
+
lines.push(`${failLabel} ${ruleId} ${group.length} ${targetLabel}`);
|
|
4336
|
+
for (const failure of group) {
|
|
4337
|
+
lines.push(` ${formatTarget(failure)}`);
|
|
4338
|
+
lines.push(` [${failure.kind}] ${failure.message}`);
|
|
4339
|
+
for (const cause of failure.causes) {
|
|
4340
|
+
const code = cause.code === void 0 ? "" : `[${cause.code}] `;
|
|
4341
|
+
lines.push(` Caused by: ${code}${cause.message}`);
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
lines.push("");
|
|
4345
|
+
}
|
|
4346
|
+
return lines;
|
|
4347
|
+
}
|
|
4348
|
+
function formatFileFailures(failures, color) {
|
|
4349
|
+
const lines = [];
|
|
4350
|
+
for (const failure of failures) {
|
|
4351
|
+
const failLabel = color ? colors$1.bgRed(colors$1.bold(" FAIL ")) : "FAIL";
|
|
4352
|
+
lines.push(`${failLabel} ${failure.file.path}`);
|
|
4353
|
+
lines.push(` [${failure.kind}] ${failure.message}`);
|
|
4354
|
+
lines.push("");
|
|
4355
|
+
}
|
|
4356
|
+
return lines;
|
|
4357
|
+
}
|
|
4358
|
+
function formatTarget(failure) {
|
|
4359
|
+
const target = failure.job.target.name ? `${failure.job.target.kind} ${failure.job.target.name}` : failure.job.target.kind;
|
|
4360
|
+
return `${failure.job.inputPath} > ${target}`;
|
|
4361
|
+
}
|
|
4362
|
+
function groupFailuresByRule(failures) {
|
|
4363
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4364
|
+
for (const failure of failures) {
|
|
4365
|
+
const group = groups.get(failure.job.ruleId) ?? [];
|
|
4366
|
+
group.push(failure);
|
|
4367
|
+
groups.set(failure.job.ruleId, group);
|
|
4368
|
+
}
|
|
4369
|
+
return [...groups];
|
|
3692
4370
|
}
|
|
3693
4371
|
//#endregion
|
|
3694
4372
|
//#region src/cli/commands/lint/index.ts
|
|
@@ -3701,98 +4379,103 @@ const lint = defineCommand({
|
|
|
3701
4379
|
arguments: "[...files]",
|
|
3702
4380
|
default: true,
|
|
3703
4381
|
description: "Run alint",
|
|
3704
|
-
name: "lint"
|
|
4382
|
+
name: "lint",
|
|
4383
|
+
options: [{
|
|
4384
|
+
description: "Lint only staged, unstaged, and untracked files",
|
|
4385
|
+
flags: "--dirty"
|
|
4386
|
+
}]
|
|
3705
4387
|
});
|
|
3706
4388
|
async function assertConfigExists(cwd, configPath) {
|
|
3707
4389
|
const resolvedConfigPath = resolve(cwd, configPath);
|
|
3708
4390
|
try {
|
|
3709
4391
|
if (!(await stat(resolvedConfigPath)).isFile()) throw new Error(`Config file "${configPath}" is not a file.`);
|
|
3710
4392
|
} catch (error) {
|
|
3711
|
-
if (
|
|
4393
|
+
if (isNodeErrorCode(error, "ENOENT")) throw new Error(`Config file "${configPath}" does not exist.`);
|
|
3712
4394
|
throw error;
|
|
3713
4395
|
}
|
|
3714
4396
|
}
|
|
3715
|
-
function isNodeError(error) {
|
|
3716
|
-
return error instanceof Error && "code" in error;
|
|
3717
|
-
}
|
|
3718
4397
|
async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
3719
|
-
if (options.
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
try {
|
|
3723
|
-
lintTargets = await findLintTargets({
|
|
3724
|
-
config,
|
|
3725
|
-
cwd: io.cwd,
|
|
3726
|
-
errorOnUnmatchedPattern: true,
|
|
3727
|
-
globInputPaths: true,
|
|
3728
|
-
inputs: files
|
|
3729
|
-
});
|
|
3730
|
-
} catch (error) {
|
|
3731
|
-
if (error instanceof NoFilesFoundError) {
|
|
3732
|
-
io.stderr.write(`${error.message}\n`);
|
|
3733
|
-
return 2;
|
|
3734
|
-
}
|
|
3735
|
-
throw error;
|
|
4398
|
+
if (options.dirty && files.length > 0) {
|
|
4399
|
+
io.stderr.write("The --dirty option does not accept file arguments.\n");
|
|
4400
|
+
return 2;
|
|
3736
4401
|
}
|
|
3737
|
-
const
|
|
3738
|
-
const
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
cwd: io.cwd,
|
|
3742
|
-
isTty: io.stderr.isTTY === true,
|
|
3743
|
-
rows: io.stderr.rows,
|
|
3744
|
-
write: (chunk) => io.stderr.write(chunk)
|
|
3745
|
-
}) : void 0;
|
|
3746
|
-
const restoreProgressConsole = progress ? interceptConsoleOutput({ write: progress.write }) : void 0;
|
|
3747
|
-
const statsTarget = resolveStatsWrite(runner?.stats, io.env);
|
|
3748
|
-
const statsCollector = statsTarget ? createStatsCollector() : void 0;
|
|
3749
|
-
const persistStats = async (runResult) => {
|
|
3750
|
-
if (statsTarget && statsCollector) await writeRunStats(statsTarget, statsCollector, runResult, io.cwd);
|
|
4402
|
+
const cwd = options.dirty ? await findGitRoot(io.cwd) : io.cwd;
|
|
4403
|
+
const runIo = cwd === io.cwd ? io : {
|
|
4404
|
+
...io,
|
|
4405
|
+
cwd
|
|
3751
4406
|
};
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
4407
|
+
if (options.config) await assertConfigExists(cwd, options.config);
|
|
4408
|
+
let changedLines;
|
|
4409
|
+
let config;
|
|
4410
|
+
let targets;
|
|
4411
|
+
if (options.dirty) {
|
|
4412
|
+
config = await loadAlintConfig(cwd, options.config);
|
|
4413
|
+
const dirtyTargets = await findDirtyLintTargets(config, cwd);
|
|
4414
|
+
changedLines = dirtyTargets.changedLines;
|
|
4415
|
+
targets = dirtyTargets;
|
|
4416
|
+
if (targets.files.length === 0) return 0;
|
|
4417
|
+
}
|
|
4418
|
+
const session = await createRunSession(runIo, config === void 0 ? { configPath: options.config } : { config });
|
|
3757
4419
|
try {
|
|
4420
|
+
const progress = shouldEnableProgress(options, runIo) ? createCliProgressReporter({
|
|
4421
|
+
color: runIo.stderr.isTTY === true,
|
|
4422
|
+
columns: runIo.stderr.columns ?? 80,
|
|
4423
|
+
cwd,
|
|
4424
|
+
isTty: runIo.stderr.isTTY === true,
|
|
4425
|
+
rows: runIo.stderr.rows,
|
|
4426
|
+
write: (chunk) => runIo.stderr.write(chunk)
|
|
4427
|
+
}) : void 0;
|
|
4428
|
+
const restoreProgressConsole = progress ? interceptConsoleOutput({ write: progress.write }) : void 0;
|
|
4429
|
+
const visibleResult = (runResult) => changedLines === void 0 ? runResult : filterResultToChangedLines(runResult, {
|
|
4430
|
+
changedLines,
|
|
4431
|
+
cwd
|
|
4432
|
+
});
|
|
4433
|
+
const writeResult = (runResult) => {
|
|
4434
|
+
runIo.stdout.write(formatDiagnostics(options.format, visibleResult(runResult), { color: runIo.stdout.isTTY === true }));
|
|
4435
|
+
};
|
|
4436
|
+
const targetSelection = targets === void 0 ? { inputs: files } : { targets };
|
|
4437
|
+
let result;
|
|
3758
4438
|
try {
|
|
3759
|
-
result = await
|
|
4439
|
+
result = await executeLint({
|
|
4440
|
+
...targetSelection,
|
|
3760
4441
|
cacheOnly: options.cacheOnly,
|
|
3761
|
-
|
|
3762
|
-
cwd: io.cwd,
|
|
3763
|
-
defaultModel,
|
|
3764
|
-
directories: lintTargets.directories,
|
|
3765
|
-
files: lintTargets.files,
|
|
4442
|
+
io: runIo,
|
|
3766
4443
|
modelOverride: options.model,
|
|
3767
4444
|
outputLanguage: options.outputLanguage,
|
|
3768
|
-
progress:
|
|
3769
|
-
|
|
3770
|
-
|
|
4445
|
+
progress: progress?.reporter,
|
|
4446
|
+
runnerOptions: options,
|
|
4447
|
+
session
|
|
3771
4448
|
});
|
|
3772
4449
|
} catch (error) {
|
|
3773
|
-
|
|
3774
|
-
|
|
4450
|
+
if (error instanceof NoFilesFoundError) {
|
|
4451
|
+
runIo.stderr.write(`${error.message}\n`);
|
|
4452
|
+
return 2;
|
|
4453
|
+
}
|
|
4454
|
+
if (error instanceof AlintCachePersistenceError) {
|
|
4455
|
+
writeResult(error.result);
|
|
4456
|
+
const message = errorMessageFrom(error.cause) ?? error.message;
|
|
4457
|
+
runIo.stderr.write(`Cache persistence failed: ${message}\n`);
|
|
4458
|
+
return 1;
|
|
4459
|
+
}
|
|
3775
4460
|
if (error instanceof AlintRunError) {
|
|
3776
|
-
await persistStats(error.result);
|
|
3777
4461
|
writeResult(error.result);
|
|
3778
|
-
|
|
4462
|
+
runIo.stderr.write(formatRunError(error, runIo.stderr.isTTY === true));
|
|
3779
4463
|
return 2;
|
|
3780
4464
|
}
|
|
3781
4465
|
if (error instanceof AlintRunCancelledError) {
|
|
3782
|
-
await persistStats(error.result);
|
|
3783
4466
|
writeResult(error.result);
|
|
3784
|
-
|
|
4467
|
+
runIo.stderr.write(formatCancelledError(error, runIo.stderr.isTTY === true));
|
|
3785
4468
|
return 2;
|
|
3786
4469
|
}
|
|
3787
4470
|
throw error;
|
|
4471
|
+
} finally {
|
|
4472
|
+
restoreProgressConsole?.();
|
|
4473
|
+
progress?.dispose();
|
|
3788
4474
|
}
|
|
3789
|
-
restoreProgressConsole?.();
|
|
3790
|
-
progress?.dispose();
|
|
3791
|
-
await persistStats(result);
|
|
3792
4475
|
writeResult(result);
|
|
3793
|
-
return result.diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
|
|
4476
|
+
return visibleResult(result).diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
|
|
3794
4477
|
} finally {
|
|
3795
|
-
await
|
|
4478
|
+
await session.shutdown();
|
|
3796
4479
|
}
|
|
3797
4480
|
}
|
|
3798
4481
|
function shouldEnableProgress(options, io) {
|
|
@@ -3800,6 +4483,19 @@ function shouldEnableProgress(options, io) {
|
|
|
3800
4483
|
return options.format === "stylish" && io.stderr.isTTY === true;
|
|
3801
4484
|
}
|
|
3802
4485
|
//#endregion
|
|
4486
|
+
//#region src/cli/commands/lsp/index.ts
|
|
4487
|
+
const lsp = defineCommand({
|
|
4488
|
+
action: async (context) => (await import("./server-xx0w7UQL.mjs")).startLspServer(context.io),
|
|
4489
|
+
description: "Run alint as a language server over stdio",
|
|
4490
|
+
examples: [["# Point an editor at the project's own alint", "alint lsp"].join("\n")],
|
|
4491
|
+
help: ["Serve alint diagnostics over the Language Server Protocol on stdin/stdout.", "The server is cache-first: it publishes findings that are already cached and never calls a model on its own. Runs that spend tokens happen only through the `alint.runFile` and `alint.runWorkspace` commands."].join("\n\n"),
|
|
4492
|
+
name: "lsp",
|
|
4493
|
+
options: [{
|
|
4494
|
+
description: "Use stdio transport (default; accepted for client compatibility)",
|
|
4495
|
+
flags: "--stdio"
|
|
4496
|
+
}]
|
|
4497
|
+
});
|
|
4498
|
+
//#endregion
|
|
3803
4499
|
//#region src/cli/commands/output-inspect/inspect.ts
|
|
3804
4500
|
const inspect$1 = defineCommand({
|
|
3805
4501
|
action: (context, file, options) => inspectOutputFile(context.io, file, options),
|
|
@@ -3828,14 +4524,14 @@ async function inspectOutputFile(io, file, options) {
|
|
|
3828
4524
|
try {
|
|
3829
4525
|
text = await readFile(filePath, "utf8");
|
|
3830
4526
|
} catch (error) {
|
|
3831
|
-
io.stderr.write(`Could not read output file "${file}": ${errorMessageFrom
|
|
4527
|
+
io.stderr.write(`Could not read output file "${file}": ${errorMessageFrom(error)}\n`);
|
|
3832
4528
|
return 2;
|
|
3833
4529
|
}
|
|
3834
4530
|
let parsed;
|
|
3835
4531
|
try {
|
|
3836
4532
|
parsed = JSON.parse(text);
|
|
3837
4533
|
} catch (error) {
|
|
3838
|
-
io.stderr.write(`Could not parse output file "${file}": ${errorMessageFrom
|
|
4534
|
+
io.stderr.write(`Could not parse output file "${file}": ${errorMessageFrom(error)}\n`);
|
|
3839
4535
|
return 2;
|
|
3840
4536
|
}
|
|
3841
4537
|
if (!isRunResult(parsed)) {
|
|
@@ -3845,7 +4541,7 @@ async function inspectOutputFile(io, file, options) {
|
|
|
3845
4541
|
try {
|
|
3846
4542
|
io.stdout.write(formatDiagnostics(options.format, parsed, { color: io.stdout.isTTY === true }));
|
|
3847
4543
|
} catch (error) {
|
|
3848
|
-
io.stderr.write(`${errorMessageFrom
|
|
4544
|
+
io.stderr.write(`${errorMessageFrom(error)}\n`);
|
|
3849
4545
|
return 2;
|
|
3850
4546
|
}
|
|
3851
4547
|
return parsed.diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
|
|
@@ -4370,7 +5066,7 @@ async function runStatsCommand(options, io) {
|
|
|
4370
5066
|
}));
|
|
4371
5067
|
return 0;
|
|
4372
5068
|
} catch (error) {
|
|
4373
|
-
io.stderr.write(`${errorMessageFrom
|
|
5069
|
+
io.stderr.write(`${errorMessageFrom(error) ?? "Failed to read stats."}\n`);
|
|
4374
5070
|
return 2;
|
|
4375
5071
|
}
|
|
4376
5072
|
}
|
|
@@ -4406,6 +5102,7 @@ function statsLocation(stats) {
|
|
|
4406
5102
|
const commandTree = [
|
|
4407
5103
|
setup,
|
|
4408
5104
|
config,
|
|
5105
|
+
integrations,
|
|
4409
5106
|
plugin,
|
|
4410
5107
|
output,
|
|
4411
5108
|
defineCommand({
|
|
@@ -4459,6 +5156,7 @@ const commandTree = [
|
|
|
4459
5156
|
}
|
|
4460
5157
|
]
|
|
4461
5158
|
}),
|
|
5159
|
+
lsp,
|
|
4462
5160
|
lint
|
|
4463
5161
|
];
|
|
4464
5162
|
//#endregion
|
|
@@ -4545,4 +5243,4 @@ function shouldCaptureHelp(argv) {
|
|
|
4545
5243
|
return argv.includes("--help") || argv.includes("-h");
|
|
4546
5244
|
}
|
|
4547
5245
|
//#endregion
|
|
4548
|
-
export { formatJson as i, formatDiagnostics as n, formatStylish as r, executeCli as t };
|
|
5246
|
+
export { createRunSession as a, formatJson as i, formatDiagnostics as n, formatStylish as r, executeCli as t };
|