@alint-js/cli 0.0.5 → 0.0.7
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/dist/bin/index.mjs +1 -1
- package/dist/{cli-CoS-NVz_.mjs → cli-D7cUTZnE.mjs} +1369 -847
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +1 -1
- package/package.json +4 -3
|
@@ -1,16 +1,224 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
import { stat } from "node:fs/promises";
|
|
3
2
|
import { inspect } from "node:util";
|
|
4
|
-
import Gitignore from "gitignore-fs";
|
|
5
|
-
import c, { createColors } from "tinyrainbow";
|
|
6
|
-
import { getGlobalSetupConfigPath, getProjectSetupConfigPath, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, writeSetupConfig } from "@alint-js/config";
|
|
7
|
-
import { AlintRunError, runAlint } from "@alint-js/core";
|
|
8
|
-
import { errorMessageFrom } from "@moeru/std/error";
|
|
9
3
|
import { cac } from "cac";
|
|
10
|
-
import {
|
|
4
|
+
import { getGlobalSetupConfigPath, getProjectSetupConfigPath, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, writeSetupConfig } from "@alint-js/config";
|
|
5
|
+
import { AlintRunError, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForFile, runAlint } from "@alint-js/core";
|
|
6
|
+
import { relative, resolve } from "pathe";
|
|
11
7
|
import { getBorderCharacters, table } from "table";
|
|
8
|
+
import { errorMessageFrom } from "@moeru/std/error";
|
|
9
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
10
|
+
import c, { createColors } from "tinyrainbow";
|
|
12
11
|
import cliSpinners from "cli-spinners";
|
|
13
|
-
import { relative } from "node:path";
|
|
12
|
+
import { relative as relative$1 } from "node:path";
|
|
13
|
+
import Gitignore from "gitignore-fs";
|
|
14
|
+
import { minimatch } from "minimatch";
|
|
15
|
+
import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
|
|
16
|
+
//#region src/cli/commands/command.ts
|
|
17
|
+
function defineCommand(node) {
|
|
18
|
+
return node;
|
|
19
|
+
}
|
|
20
|
+
function registerCommandTree(cli, nodes, context, setPendingResult, help = {}) {
|
|
21
|
+
for (const node of nodes) registerRootCommand(cli, node, context, setPendingResult);
|
|
22
|
+
cli.globalCommand.helpCallback = (sections) => formatCommandHelp(sections, nodes, cli.rawArgs, help);
|
|
23
|
+
}
|
|
24
|
+
function collectCommandOptions(node) {
|
|
25
|
+
const options = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const option of node.options ?? []) options.set(option.flags, option);
|
|
27
|
+
for (const child of node.children ?? []) for (const option of collectCommandOptions(child)) options.set(option.flags, option);
|
|
28
|
+
return [...options.values()];
|
|
29
|
+
}
|
|
30
|
+
function commandPattern(node) {
|
|
31
|
+
if (node.default) return node.arguments ?? node.name;
|
|
32
|
+
return [node.name, node.children ? "[...args]" : node.arguments].filter(Boolean).join(" ");
|
|
33
|
+
}
|
|
34
|
+
function dispatchCommand(context, node, args, options, path) {
|
|
35
|
+
const [subcommand, ...restArgs] = args;
|
|
36
|
+
const child = node.children?.find((item) => item.name === subcommand || item.alias?.includes(subcommand ?? ""));
|
|
37
|
+
if (child) return dispatchCommand(context, child, restArgs, options, [...path, child.name]);
|
|
38
|
+
if (!node.action) return Promise.resolve(reportUnknownCommand(context, path, args));
|
|
39
|
+
return Promise.resolve(node.action(context, ...parseCommandArguments(node, args), options));
|
|
40
|
+
}
|
|
41
|
+
function formatChildCommandHelp(parentPath, child) {
|
|
42
|
+
const parts = [...parentPath, child.name];
|
|
43
|
+
if (!child.children && child.arguments) parts.push(child.arguments);
|
|
44
|
+
return {
|
|
45
|
+
description: child.description,
|
|
46
|
+
pattern: parts.join(" ")
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function formatCommandHelp(sections, nodes, argv, rootHelp) {
|
|
50
|
+
const helpPath = resolveHelpPath(nodes, argv);
|
|
51
|
+
const node = helpPath.at(-1)?.node;
|
|
52
|
+
if (!node) return insertExamplesSection(insertHelpSection(sections, rootHelp), rootHelp);
|
|
53
|
+
const path = helpPath.map((item) => item.node.name);
|
|
54
|
+
const normalizedSections = rewriteUsageSection(insertExamplesSection(insertHelpSection(sections.filter((section) => section.title !== "Options"), node), node), path, node);
|
|
55
|
+
if (!node.children) {
|
|
56
|
+
const optionSection = formatOptionsSection(node.options ?? []);
|
|
57
|
+
return optionSection ? [...normalizedSections, optionSection] : normalizedSections;
|
|
58
|
+
}
|
|
59
|
+
const commands = (node.children ?? []).map((child) => formatChildCommandHelp(path, child));
|
|
60
|
+
const longestCommand = Math.max(...commands.map((command) => command.pattern.length));
|
|
61
|
+
const commandSection = {
|
|
62
|
+
body: commands.map((command) => ` ${command.pattern.padEnd(longestCommand)} ${command.description}`).join("\n"),
|
|
63
|
+
title: "Commands"
|
|
64
|
+
};
|
|
65
|
+
const usageIndex = normalizedSections.findIndex((section) => section.title === "Usage");
|
|
66
|
+
if (usageIndex === -1) return [commandSection, ...normalizedSections];
|
|
67
|
+
return [
|
|
68
|
+
...normalizedSections.slice(0, usageIndex + 1),
|
|
69
|
+
commandSection,
|
|
70
|
+
...normalizedSections.slice(usageIndex + 1)
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
function formatExamples(examples) {
|
|
74
|
+
return examples.map((example) => example.split("\n").map((line) => ` ${line}`).join("\n")).join("\n\n");
|
|
75
|
+
}
|
|
76
|
+
function formatOptionDescription(option) {
|
|
77
|
+
if (option.config?.default === void 0) return option.description;
|
|
78
|
+
return `${option.description} (default: ${option.config.default})`;
|
|
79
|
+
}
|
|
80
|
+
function formatOptionsSection(options) {
|
|
81
|
+
if (options.length === 0) return;
|
|
82
|
+
const rows = options.map((option) => ({
|
|
83
|
+
description: formatOptionDescription(option),
|
|
84
|
+
flags: option.flags
|
|
85
|
+
}));
|
|
86
|
+
const longestFlag = Math.max(...rows.map((row) => row.flags.length));
|
|
87
|
+
return {
|
|
88
|
+
body: rows.map((row) => ` ${row.flags.padEnd(longestFlag)} ${row.description}`).join("\n"),
|
|
89
|
+
title: "Options"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function formatUnknownCommand(path, args) {
|
|
93
|
+
return [...path, ...args].filter(Boolean).join(" ");
|
|
94
|
+
}
|
|
95
|
+
function formatUsagePattern(path, node) {
|
|
96
|
+
const parts = [...path];
|
|
97
|
+
if (!node.children && node.arguments) parts.push(node.arguments);
|
|
98
|
+
return parts.join(" ");
|
|
99
|
+
}
|
|
100
|
+
function insertExamplesSection(sections, node) {
|
|
101
|
+
if (!node.examples?.length) return sections;
|
|
102
|
+
const usageIndex = sections.findIndex((section) => section.title === "Usage");
|
|
103
|
+
const examplesSection = {
|
|
104
|
+
body: formatExamples(node.examples),
|
|
105
|
+
title: "Examples"
|
|
106
|
+
};
|
|
107
|
+
if (usageIndex === -1) return [...sections, examplesSection];
|
|
108
|
+
return [
|
|
109
|
+
...sections.slice(0, usageIndex),
|
|
110
|
+
examplesSection,
|
|
111
|
+
...sections.slice(usageIndex)
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
function insertHelpSection(sections, node) {
|
|
115
|
+
const help = node.help ?? node.description;
|
|
116
|
+
if (!help) return sections;
|
|
117
|
+
const usageIndex = sections.findIndex((section) => section.title === "Usage");
|
|
118
|
+
const helpSection = { body: help };
|
|
119
|
+
if (usageIndex === -1) return [...sections, helpSection];
|
|
120
|
+
return [
|
|
121
|
+
...sections.slice(0, usageIndex),
|
|
122
|
+
helpSection,
|
|
123
|
+
...sections.slice(usageIndex)
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
function parseCommandArguments(node, args) {
|
|
127
|
+
if (!node.arguments) return [];
|
|
128
|
+
const parts = node.arguments.split(/\s+/u).filter(Boolean);
|
|
129
|
+
const values = [];
|
|
130
|
+
let argIndex = 0;
|
|
131
|
+
for (const part of parts) {
|
|
132
|
+
if (part.startsWith("[...") || part.startsWith("<...")) {
|
|
133
|
+
values.push(args.slice(argIndex));
|
|
134
|
+
argIndex = args.length;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (part.startsWith("<") && argIndex >= args.length) throw new Error(`Missing required argument ${part}.`);
|
|
138
|
+
values.push(args[argIndex]);
|
|
139
|
+
argIndex += 1;
|
|
140
|
+
}
|
|
141
|
+
return values;
|
|
142
|
+
}
|
|
143
|
+
function registerRootCommand(cli, node, context, setPendingResult) {
|
|
144
|
+
const command = cli.command(commandPattern(node), node.description);
|
|
145
|
+
if (node.allowUnknownOptions || node.children) command.allowUnknownOptions();
|
|
146
|
+
for (const alias of node.alias ?? []) command.alias(alias);
|
|
147
|
+
for (const option of collectCommandOptions(node)) command.option(option.flags, option.description, option.config);
|
|
148
|
+
command.action((...args) => {
|
|
149
|
+
const options = args.at(-1);
|
|
150
|
+
return setPendingResult(node.children ? dispatchCommand(context, node, args[0] ?? [], options, [node.name]) : Promise.resolve(node.action?.(context, ...args.slice(0, -1), options) ?? 0));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function reportUnknownCommand(context, path, args) {
|
|
154
|
+
context.io.stderr.write(`unknown command: ${formatUnknownCommand(path, args)}\n`);
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
157
|
+
function resolveHelpPath(nodes, argv) {
|
|
158
|
+
const path = [];
|
|
159
|
+
let currentNodes = nodes;
|
|
160
|
+
let skipNext = false;
|
|
161
|
+
for (const arg of argv.slice(2)) {
|
|
162
|
+
if (skipNext) {
|
|
163
|
+
skipNext = false;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (arg.startsWith("-")) {
|
|
167
|
+
skipNext = shouldSkipOptionValue(arg);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const node = currentNodes.find((item) => item.name === arg || item.alias?.includes(arg));
|
|
171
|
+
if (!node) break;
|
|
172
|
+
path.push({ node });
|
|
173
|
+
currentNodes = node.children ?? [];
|
|
174
|
+
}
|
|
175
|
+
return path;
|
|
176
|
+
}
|
|
177
|
+
function rewriteUsageSection(sections, path, node) {
|
|
178
|
+
return sections.map((section) => section.title === "Usage" ? {
|
|
179
|
+
...section,
|
|
180
|
+
body: ` $ alint ${formatUsagePattern(path, node)}`
|
|
181
|
+
} : section);
|
|
182
|
+
}
|
|
183
|
+
function shouldSkipOptionValue(arg) {
|
|
184
|
+
if (arg.includes("=")) return false;
|
|
185
|
+
return [
|
|
186
|
+
"--cache-location",
|
|
187
|
+
"--config",
|
|
188
|
+
"--file-concurrency",
|
|
189
|
+
"--format",
|
|
190
|
+
"--model",
|
|
191
|
+
"--provider-endpoint",
|
|
192
|
+
"--provider-header",
|
|
193
|
+
"--provider-id",
|
|
194
|
+
"--provider-model",
|
|
195
|
+
"--rule-concurrency",
|
|
196
|
+
"--timeout-ms",
|
|
197
|
+
"-f",
|
|
198
|
+
"-l"
|
|
199
|
+
].includes(arg);
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/cli/commands/config/inspect.ts
|
|
203
|
+
const inspect$2 = defineCommand({
|
|
204
|
+
action: (context, file, options) => runConfigInspectCommand(file, options.config, context.io),
|
|
205
|
+
arguments: "<file>",
|
|
206
|
+
description: "Inspect resolved config for a file",
|
|
207
|
+
name: "inspect"
|
|
208
|
+
});
|
|
209
|
+
async function runConfigInspectCommand(file, configPath, io) {
|
|
210
|
+
const config = await loadAlintConfig(io.cwd, configPath);
|
|
211
|
+
const result = resolveConfigForFile(resolve(io.cwd, file), config, { cwd: io.cwd });
|
|
212
|
+
io.stdout.write(`file: ${file}\n`);
|
|
213
|
+
io.stdout.write(`ignored: ${result.ignored ? "yes" : "no"}\n`);
|
|
214
|
+
io.stdout.write("matched:\n");
|
|
215
|
+
for (const item of result.matched) io.stdout.write(` - ${item.name ?? "<anonymous>"}\n`);
|
|
216
|
+
io.stdout.write(`language: ${result.config.language ?? "<inferred>"}\n`);
|
|
217
|
+
io.stdout.write("rules:\n");
|
|
218
|
+
for (const [id, entry] of Object.entries(result.config.rules)) io.stdout.write(` ${id}: ${Array.isArray(entry) ? entry[0] : entry}\n`);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
14
222
|
//#region src/cli/provider-registry.ts
|
|
15
223
|
function buildModelsUrl(endpoint) {
|
|
16
224
|
return new URL("models", endpoint.endsWith("/") ? endpoint : `${endpoint}/`).toString();
|
|
@@ -116,760 +324,1254 @@ function formatTable(rows) {
|
|
|
116
324
|
});
|
|
117
325
|
}
|
|
118
326
|
//#endregion
|
|
119
|
-
//#region src/cli/commands/setup
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
return
|
|
327
|
+
//#region src/cli/commands/config/setup-config.ts
|
|
328
|
+
async function loadMergedSetupConfig(io) {
|
|
329
|
+
const globalSetupConfigPath = getGlobalSetupConfigPath(io.env ?? process.env);
|
|
330
|
+
const projectSetupConfigPath = getProjectSetupConfigPath(io.cwd);
|
|
331
|
+
const [globalSetupConfig, projectSetupConfig] = await Promise.all([loadSetupConfig(globalSetupConfigPath), loadSetupConfig(projectSetupConfigPath)]);
|
|
332
|
+
return mergeSetupConfigs(globalSetupConfig, projectSetupConfig);
|
|
125
333
|
}
|
|
126
|
-
|
|
127
|
-
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/cli/commands/config/models/ls.ts
|
|
336
|
+
const ls$1 = defineCommand({
|
|
337
|
+
async action(context) {
|
|
338
|
+
context.io.stdout.write(formatModelList(await loadMergedSetupConfig(context.io)));
|
|
339
|
+
return 0;
|
|
340
|
+
},
|
|
341
|
+
alias: ["ls"],
|
|
342
|
+
description: "List configured models",
|
|
343
|
+
name: "list"
|
|
344
|
+
});
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/cli/commands/config/probe.ts
|
|
347
|
+
function providerHeadersFromOptions(options) {
|
|
348
|
+
return parseHeaderList(toArray$1(options.providerHeader)) ?? {};
|
|
128
349
|
}
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
350
|
+
function toArray$1(value) {
|
|
351
|
+
if (value === void 0) return [];
|
|
352
|
+
return (Array.isArray(value) ? value : [value]).filter((item) => typeof item === "string");
|
|
353
|
+
}
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/cli/commands/config/models/models.ts
|
|
356
|
+
const models = defineCommand({
|
|
357
|
+
children: [
|
|
358
|
+
defineCommand({
|
|
359
|
+
async action(context, options) {
|
|
360
|
+
if (!options.endpoint) {
|
|
361
|
+
context.io.stderr.write("config models probe requires --endpoint.\n");
|
|
362
|
+
return 2;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const models = await probeModels(options.endpoint, providerHeadersFromOptions(options));
|
|
366
|
+
context.io.stdout.write(`${models.join("\n")}${models.length > 0 ? "\n" : ""}`);
|
|
367
|
+
return 0;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
context.io.stderr.write(`failed to probe models: ${errorMessageFrom(error) ?? String(error)}\n`);
|
|
370
|
+
return 2;
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
description: "Probe OpenAI-compatible models",
|
|
374
|
+
examples: [["# Probe a provider endpoint and print available model ids", "alint config models probe --endpoint https://openrouter.ai/api/v1"].join("\n"), ["# Probe an endpoint that requires an authorization header", "alint config models probe --endpoint https://api.example.com/v1 --provider-header \"Authorization=Bearer $TOKEN\""].join("\n")],
|
|
375
|
+
help: ["Probe an OpenAI-compatible models endpoint before saving it in setup config.", "The command calls the provider models endpoint using the supplied endpoint and optional headers, then prints the model ids returned by that provider."].join("\n\n"),
|
|
376
|
+
name: "probe",
|
|
377
|
+
options: [{
|
|
378
|
+
description: "Provider endpoint",
|
|
379
|
+
flags: "--endpoint <url>"
|
|
380
|
+
}, {
|
|
381
|
+
description: "Provider header",
|
|
382
|
+
flags: "--provider-header <Key=Value>"
|
|
383
|
+
}]
|
|
384
|
+
}),
|
|
385
|
+
ls$1,
|
|
386
|
+
defineCommand({
|
|
387
|
+
async action(context, model) {
|
|
388
|
+
const candidate = findModel(await loadMergedSetupConfig(context.io), model);
|
|
389
|
+
if (candidate === void 0) {
|
|
390
|
+
context.io.stderr.write(`unknown model "${model}".\n`);
|
|
391
|
+
return 2;
|
|
392
|
+
}
|
|
393
|
+
context.io.stdout.write(formatModelShow(candidate));
|
|
394
|
+
return 0;
|
|
395
|
+
},
|
|
396
|
+
arguments: "<model>",
|
|
397
|
+
description: "Show configured model",
|
|
398
|
+
name: "show"
|
|
399
|
+
})
|
|
400
|
+
],
|
|
401
|
+
description: "Manage configured models",
|
|
402
|
+
help: ["Inspect, list, and probe model entries from alint setup configuration.", "Model entries describe the provider/model ids alint can use for model-backed rules. Probe endpoints before saving them when you want to verify what model ids a provider exposes."].join("\n\n"),
|
|
403
|
+
name: "models"
|
|
404
|
+
});
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/cli/commands/config/providers/ls.ts
|
|
407
|
+
const ls = defineCommand({
|
|
408
|
+
async action(context) {
|
|
409
|
+
context.io.stdout.write(formatProviderList(await loadMergedSetupConfig(context.io)));
|
|
410
|
+
return 0;
|
|
411
|
+
},
|
|
412
|
+
alias: ["ls"],
|
|
413
|
+
description: "List configured providers",
|
|
414
|
+
name: "list"
|
|
415
|
+
});
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/cli/commands/config/providers/probe.ts
|
|
418
|
+
const probe = defineCommand({
|
|
419
|
+
async action(context, options) {
|
|
420
|
+
if (!options.endpoint) {
|
|
421
|
+
context.io.stderr.write("config providers probe requires --endpoint.\n");
|
|
422
|
+
return 2;
|
|
158
423
|
}
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
424
|
+
try {
|
|
425
|
+
const models = await probeModels(options.endpoint, providerHeadersFromOptions(options));
|
|
426
|
+
context.io.stdout.write(`endpoint: ${options.endpoint}\nmodels: ${models.length}\n`);
|
|
427
|
+
return 0;
|
|
428
|
+
} catch (error) {
|
|
429
|
+
context.io.stderr.write(`failed to probe provider: ${errorMessageFrom(error) ?? String(error)}\n`);
|
|
430
|
+
return 2;
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
description: "Probe provider reachability",
|
|
434
|
+
name: "probe",
|
|
435
|
+
options: [{
|
|
436
|
+
description: "Provider endpoint",
|
|
437
|
+
flags: "--endpoint <url>"
|
|
438
|
+
}, {
|
|
439
|
+
description: "Provider header",
|
|
440
|
+
flags: "--provider-header <Key=Value>"
|
|
441
|
+
}]
|
|
442
|
+
});
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region src/cli/commands/config/config.ts
|
|
445
|
+
const config = defineCommand({
|
|
446
|
+
children: [
|
|
447
|
+
inspect$2,
|
|
448
|
+
models,
|
|
449
|
+
defineCommand({
|
|
450
|
+
children: [
|
|
451
|
+
ls,
|
|
452
|
+
defineCommand({
|
|
453
|
+
async action(context, providerId) {
|
|
454
|
+
const provider = (await loadMergedSetupConfig(context.io)).providers.find((item) => item.id === providerId);
|
|
455
|
+
if (provider === void 0) {
|
|
456
|
+
context.io.stderr.write(`unknown provider "${providerId}".\n`);
|
|
457
|
+
return 2;
|
|
458
|
+
}
|
|
459
|
+
context.io.stdout.write(formatProviderShow(provider));
|
|
460
|
+
return 0;
|
|
170
461
|
},
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
462
|
+
arguments: "<provider>",
|
|
463
|
+
description: "Show configured provider",
|
|
464
|
+
name: "show"
|
|
465
|
+
}),
|
|
466
|
+
probe
|
|
467
|
+
],
|
|
468
|
+
description: "Manage configured providers",
|
|
469
|
+
name: "providers"
|
|
470
|
+
})
|
|
471
|
+
],
|
|
472
|
+
description: "Manage alint configuration",
|
|
473
|
+
examples: [
|
|
474
|
+
["# Show the effective alint config for a file", "alint config inspect src/index.ts"].join("\n"),
|
|
475
|
+
["# Inspect a file using a custom config path", "alint --config alint.config.ts config inspect src/index.ts"].join("\n"),
|
|
476
|
+
[
|
|
477
|
+
"# List configured providers and models",
|
|
478
|
+
"alint config providers list",
|
|
479
|
+
"alint config models list"
|
|
480
|
+
].join("\n"),
|
|
481
|
+
[
|
|
482
|
+
"# Show one configured provider or model by id",
|
|
483
|
+
"alint config providers show openrouter",
|
|
484
|
+
"alint config models show z-ai/glm-5.2"
|
|
485
|
+
].join("\n"),
|
|
486
|
+
[
|
|
487
|
+
"# Probe an OpenAI-compatible endpoint before saving it",
|
|
488
|
+
"alint config providers probe --endpoint https://openrouter.ai/api/v1",
|
|
489
|
+
"alint config models probe --endpoint https://openrouter.ai/api/v1"
|
|
490
|
+
].join("\n")
|
|
491
|
+
],
|
|
492
|
+
help: ["Inspect and update alint setup/configuration state.", "Use these commands to understand the effective config for a file, inspect saved provider/model setup, and probe OpenAI-compatible endpoints before using them in model-backed rules."].join("\n\n"),
|
|
493
|
+
name: "config"
|
|
494
|
+
});
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/cli/reporters/json.ts
|
|
497
|
+
function formatJson(result) {
|
|
498
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
499
|
+
}
|
|
500
|
+
//#endregion
|
|
501
|
+
//#region src/cli/reporters/stylish.ts
|
|
502
|
+
const colors$1 = createColors({ force: true });
|
|
503
|
+
function formatStylish(input, options = {}) {
|
|
504
|
+
const diagnostics = Array.isArray(input) ? input : input.diagnostics;
|
|
505
|
+
const totalTokens = Array.isArray(input) ? void 0 : input.usage.totalTokens;
|
|
506
|
+
if (diagnostics.length === 0) return "";
|
|
507
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
508
|
+
for (const diagnostic of diagnostics) {
|
|
509
|
+
const fileDiagnostics = diagnosticsByFile.get(diagnostic.filePath);
|
|
510
|
+
if (fileDiagnostics) {
|
|
511
|
+
fileDiagnostics.push(diagnostic);
|
|
184
512
|
continue;
|
|
185
513
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
514
|
+
diagnosticsByFile.set(diagnostic.filePath, [diagnostic]);
|
|
515
|
+
}
|
|
516
|
+
const lines = [];
|
|
517
|
+
const style = createStyle(options.color === true);
|
|
518
|
+
for (const [filePath, fileDiagnostics] of diagnosticsByFile) {
|
|
519
|
+
lines.push(style.file(filePath));
|
|
520
|
+
for (const diagnostic of fileDiagnostics) {
|
|
521
|
+
const line = diagnostic.loc?.start.line ?? 0;
|
|
522
|
+
const column = diagnostic.loc?.start.column ?? 0;
|
|
523
|
+
const severity = diagnostic.severity === "warn" ? style.warning("warning") : style.error("error");
|
|
524
|
+
lines.push(` ${style.location(`${line}:${column}`)} ${severity} ${diagnostic.message} ${style.ruleId(diagnostic.ruleId)}`);
|
|
197
525
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
526
|
+
lines.push("");
|
|
527
|
+
}
|
|
528
|
+
lines.push("", formatSummary(diagnostics, totalTokens, style));
|
|
529
|
+
return `${lines.join("\n")}\n`;
|
|
530
|
+
}
|
|
531
|
+
function countDiagnostics$2(diagnostics, severity) {
|
|
532
|
+
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
533
|
+
}
|
|
534
|
+
function createStyle(color) {
|
|
535
|
+
if (!color) return {
|
|
536
|
+
error: identity,
|
|
537
|
+
file: identity,
|
|
538
|
+
location: identity,
|
|
539
|
+
ruleId: identity,
|
|
540
|
+
summaryToken: identity,
|
|
541
|
+
warning: identity
|
|
542
|
+
};
|
|
543
|
+
return {
|
|
544
|
+
error: colors$1.red,
|
|
545
|
+
file: colors$1.underline,
|
|
546
|
+
location: colors$1.dim,
|
|
547
|
+
ruleId: colors$1.dim,
|
|
548
|
+
summaryToken: colors$1.cyan,
|
|
549
|
+
warning: colors$1.yellow
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function formatSummary(diagnostics, totalTokens, style) {
|
|
553
|
+
const warnCount = countDiagnostics$2(diagnostics, "warn");
|
|
554
|
+
const errorCount = countDiagnostics$2(diagnostics, "error");
|
|
555
|
+
const tokens = totalTokens === void 0 ? void 0 : `${totalTokens.toLocaleString("en-US")} tokens`;
|
|
556
|
+
const problemSummary = [style.warning(`${warnCount} warn`), style.error(`${errorCount} error`)].join(" / ");
|
|
557
|
+
if (tokens === void 0) return problemSummary;
|
|
558
|
+
return `${problemSummary} | ${style.summaryToken(tokens)}`;
|
|
559
|
+
}
|
|
560
|
+
function identity(value) {
|
|
561
|
+
return value;
|
|
562
|
+
}
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/cli/reporters/index.ts
|
|
565
|
+
function formatDiagnostics(format, result, options = {}) {
|
|
566
|
+
if (format === "json") return formatJson(result);
|
|
567
|
+
if (format === "stylish") return formatStylish(result, { color: options.color });
|
|
568
|
+
throw new Error(`Unknown reporter "${format}".`);
|
|
569
|
+
}
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/cli/reporters/progress/plain.ts
|
|
572
|
+
function createPlainProgressReporter(options) {
|
|
573
|
+
const writeLine = (line) => options.write(`${line}\n`);
|
|
574
|
+
return {
|
|
575
|
+
onRuleStart: (payload) => {
|
|
576
|
+
const target = payload.path.target.name ? `${payload.path.target.kind} ${payload.path.target.name}` : payload.path.target.kind;
|
|
577
|
+
writeLine(`scan ${payload.path.file.path} > ${target} > ${payload.path.rule.id}`);
|
|
578
|
+
},
|
|
579
|
+
onRunEnd: (payload) => {
|
|
580
|
+
const warnCount = countDiagnostics$1(payload.diagnostics, "warn");
|
|
581
|
+
const errorCount = countDiagnostics$1(payload.diagnostics, "error");
|
|
582
|
+
const state = payload.errored > 0 ? "failed" : "finished";
|
|
583
|
+
const cached = payload.cached > 0 ? `, ${payload.cached} cached` : "";
|
|
584
|
+
const errored = payload.errored > 0 ? `, ${payload.errored} errored` : "";
|
|
585
|
+
writeLine(`alint ${state}: ${warnCount} warn, ${errorCount} error, ${payload.usage.totalTokens} tokens${cached}${errored}`);
|
|
586
|
+
},
|
|
587
|
+
onRunStart: (payload) => {
|
|
588
|
+
writeLine(`alint started: ${payload.filesTotal} files, ${payload.rulesTotal} rules, ${payload.planned} planned executions`);
|
|
215
589
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function countDiagnostics$1(diagnostics, severity) {
|
|
593
|
+
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
594
|
+
}
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region src/cli/reporters/progress/summary.ts
|
|
597
|
+
const colors = createColors({ force: true });
|
|
598
|
+
function createSummaryProgressReporter(options) {
|
|
599
|
+
const now = () => options.clock?.() ?? Date.now();
|
|
600
|
+
const state = {
|
|
601
|
+
cached: 0,
|
|
602
|
+
completed: 0,
|
|
603
|
+
diagnostics: [],
|
|
604
|
+
errored: 0,
|
|
605
|
+
files: /* @__PURE__ */ new Map(),
|
|
606
|
+
planned: 0,
|
|
607
|
+
spinnerIndex: 0,
|
|
608
|
+
totalTokens: 0
|
|
609
|
+
};
|
|
610
|
+
return {
|
|
611
|
+
getRows: () => createRows(state, options, now()),
|
|
612
|
+
onDiagnostic: (payload) => {
|
|
613
|
+
state.diagnostics = payload.diagnostics;
|
|
614
|
+
},
|
|
615
|
+
onFileEnd: (payload) => {
|
|
616
|
+
const file = getFileState(state, payload.file);
|
|
617
|
+
file.endedAt = payload.endedAt ?? now();
|
|
618
|
+
file.rule = void 0;
|
|
619
|
+
file.target = void 0;
|
|
620
|
+
},
|
|
621
|
+
onFileStart: (payload) => {
|
|
622
|
+
const file = getFileState(state, payload.file);
|
|
623
|
+
file.startedAt = payload.startedAt ?? now();
|
|
624
|
+
file.endedAt = void 0;
|
|
625
|
+
},
|
|
626
|
+
onRuleEnd: (payload) => {
|
|
627
|
+
const file = getFileState(state, payload.path.file);
|
|
628
|
+
if (payload.cache === "hit") {
|
|
629
|
+
state.cached += 1;
|
|
630
|
+
file.cached += 1;
|
|
236
631
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
step = "models";
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (step === "models") {
|
|
244
|
-
const selectedModels = await promptModels(prompts, draft.discoveredModels ?? []);
|
|
245
|
-
if (prompts.isCancel(selectedModels)) return cancelPrompt();
|
|
246
|
-
if (selectedModels === backValue) {
|
|
247
|
-
step = "headers";
|
|
248
|
-
continue;
|
|
632
|
+
if (payload.state === "completed") {
|
|
633
|
+
state.completed += 1;
|
|
634
|
+
file.completed += 1;
|
|
249
635
|
}
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
if (step === "defaultAlias") {
|
|
256
|
-
const addDefaultAlias = await prompts.select({
|
|
257
|
-
message: `Add alias "default" to ${draft.selectedModels?.[0]}?`,
|
|
258
|
-
options: withBackOption([{
|
|
259
|
-
label: "Yes",
|
|
260
|
-
value: "yes"
|
|
261
|
-
}, {
|
|
262
|
-
label: "No",
|
|
263
|
-
value: "no"
|
|
264
|
-
}])
|
|
265
|
-
});
|
|
266
|
-
if (prompts.isCancel(addDefaultAlias)) return cancelPrompt();
|
|
267
|
-
if (addDefaultAlias === backValue) {
|
|
268
|
-
step = "models";
|
|
269
|
-
continue;
|
|
636
|
+
if (payload.state === "errored") {
|
|
637
|
+
state.errored += 1;
|
|
638
|
+
file.errored += 1;
|
|
270
639
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
640
|
+
if (file.rule?.id === payload.path.rule.id) file.rule = void 0;
|
|
641
|
+
},
|
|
642
|
+
onRuleStart: (payload) => {
|
|
643
|
+
const file = getFileState(state, payload.path.file);
|
|
644
|
+
file.startedAt ??= payload.startedAt ?? now();
|
|
645
|
+
file.rule = {
|
|
646
|
+
id: payload.path.rule.id,
|
|
647
|
+
startedAt: payload.startedAt ?? now(),
|
|
648
|
+
target: formatTarget(payload)
|
|
649
|
+
};
|
|
650
|
+
file.target = file.rule.target;
|
|
651
|
+
},
|
|
652
|
+
onRunEnd: (payload) => {
|
|
653
|
+
state.cached = payload.cached;
|
|
654
|
+
state.completed = payload.completed;
|
|
655
|
+
state.diagnostics = payload.diagnostics;
|
|
656
|
+
state.endedAt = payload.endedAt ?? now();
|
|
657
|
+
state.errored = payload.errored;
|
|
658
|
+
state.planned = payload.planned;
|
|
659
|
+
state.runStartedAt = payload.startedAt ?? state.runStartedAt;
|
|
660
|
+
state.totalTokens = payload.usage.totalTokens;
|
|
661
|
+
},
|
|
662
|
+
onRunStart: (payload) => {
|
|
663
|
+
state.cached = 0;
|
|
664
|
+
state.completed = 0;
|
|
665
|
+
state.diagnostics = [];
|
|
666
|
+
state.endedAt = void 0;
|
|
667
|
+
state.errored = 0;
|
|
668
|
+
state.files = /* @__PURE__ */ new Map();
|
|
669
|
+
state.planned = payload.planned;
|
|
670
|
+
state.runStartedAt = payload.startedAt ?? now();
|
|
671
|
+
state.spinnerIndex = 0;
|
|
672
|
+
state.totalTokens = 0;
|
|
673
|
+
for (const file of payload.files ?? []) state.files.set(file.path, createFileState(file));
|
|
674
|
+
},
|
|
675
|
+
onTargetEnd: (payload) => {
|
|
676
|
+
const file = getFileState(state, payload.path.file);
|
|
677
|
+
const target = formatTarget(payload);
|
|
678
|
+
if (file.target === target) file.target = void 0;
|
|
679
|
+
},
|
|
680
|
+
onTargetStart: (payload) => {
|
|
681
|
+
const file = getFileState(state, payload.path.file);
|
|
682
|
+
file.startedAt ??= payload.startedAt ?? now();
|
|
683
|
+
file.target = formatTarget(payload);
|
|
684
|
+
},
|
|
685
|
+
onUsage: (payload) => {
|
|
686
|
+
state.totalTokens = payload.total.totalTokens;
|
|
687
|
+
},
|
|
688
|
+
tick: () => {
|
|
689
|
+
state.spinnerIndex = (state.spinnerIndex + 1) % Math.max(options.spinnerFrames.length, 1);
|
|
295
690
|
}
|
|
296
|
-
|
|
297
|
-
const configPath = getConfigPath(io, draft.scope ?? "global");
|
|
298
|
-
await writeSetupConfig(configPath, mergeSetupConfigs(await loadSetupConfig(configPath), {
|
|
299
|
-
providers: [nextProvider],
|
|
300
|
-
version: 1
|
|
301
|
-
}));
|
|
302
|
-
prompts.outro(`Wrote ${configPath}`);
|
|
303
|
-
return 0;
|
|
304
|
-
}
|
|
691
|
+
};
|
|
305
692
|
}
|
|
306
|
-
function
|
|
307
|
-
return
|
|
308
|
-
label: "Back",
|
|
309
|
-
value: backValue
|
|
310
|
-
}];
|
|
693
|
+
function countDiagnostics(diagnostics, severity) {
|
|
694
|
+
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
311
695
|
}
|
|
312
|
-
function
|
|
696
|
+
function countQueuedFiles(state) {
|
|
697
|
+
return [...state.files.values()].filter((file) => file.startedAt === void 0 && file.endedAt === void 0 && (file.file.planned ?? 0) > 0).length;
|
|
698
|
+
}
|
|
699
|
+
function createFileState(file) {
|
|
313
700
|
return {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
aliases: index === 0 && addDefaultAlias ? ["default"] : void 0,
|
|
319
|
-
id: modelId,
|
|
320
|
-
name: modelId
|
|
321
|
-
})),
|
|
322
|
-
type: "openai-compatible"
|
|
701
|
+
cached: 0,
|
|
702
|
+
completed: 0,
|
|
703
|
+
errored: 0,
|
|
704
|
+
file
|
|
323
705
|
};
|
|
324
706
|
}
|
|
325
|
-
function
|
|
326
|
-
|
|
707
|
+
function createRows(state, options, now) {
|
|
708
|
+
const rows = getActiveFiles(state).flatMap((file) => formatFileRows(file, state, options, now));
|
|
709
|
+
const queued = countQueuedFiles(state);
|
|
710
|
+
const warnCount = countDiagnostics(state.diagnostics, "warn");
|
|
711
|
+
const errorCount = countDiagnostics(state.diagnostics, "error");
|
|
712
|
+
const footer = formatFooter(state, warnCount, errorCount, queued, options, now);
|
|
713
|
+
if (queued > 0) rows.push(formatQueuedRow(queued, options));
|
|
714
|
+
if (rows.length === 0) rows.push(formatIdleRow(state, options));
|
|
715
|
+
rows.push("", footer);
|
|
716
|
+
return options.color ? rows.map((row) => styleRow(row, state, warnCount, errorCount, options)) : rows;
|
|
327
717
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
spinner.start("Probing models");
|
|
331
|
-
try {
|
|
332
|
-
const models = await probeModels(endpoint, headers ?? {});
|
|
333
|
-
spinner.stop(models.length > 0 ? `Found ${models.length} models` : "No models discovered");
|
|
334
|
-
return models;
|
|
335
|
-
} catch (error) {
|
|
336
|
-
spinner.stop(formatProbeModelsFailure(endpoint, error));
|
|
337
|
-
return [];
|
|
338
|
-
}
|
|
718
|
+
function escapeRegExp(value) {
|
|
719
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
339
720
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
message: "Provider endpoint",
|
|
344
|
-
placeholder: source === "ollama" ? "http://localhost:11434/v1; type .. to go back" : "https://example.test/v1; type .. to go back",
|
|
345
|
-
validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider endpoint is required."
|
|
346
|
-
});
|
|
721
|
+
function estimateTotal(elapsedMs, completed, planned) {
|
|
722
|
+
if (completed <= 0 || planned <= 0) return;
|
|
723
|
+
return elapsedMs * planned / completed;
|
|
347
724
|
}
|
|
348
|
-
|
|
349
|
-
if (
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
label: model,
|
|
354
|
-
value: model
|
|
355
|
-
}))),
|
|
356
|
-
required: true
|
|
357
|
-
});
|
|
358
|
-
return Array.isArray(selectedModels) && selectedModels.includes(backValue) ? backValue : selectedModels;
|
|
359
|
-
}
|
|
360
|
-
const modelInput = await prompts.text({
|
|
361
|
-
message: "Models",
|
|
362
|
-
placeholder: "qwen:8b, qwen:32b; type .. to go back",
|
|
363
|
-
validate: (value) => isBackInput(value ?? "") || splitModelInput(value ?? "").length > 0 ? void 0 : "At least one model is required."
|
|
364
|
-
});
|
|
365
|
-
if (prompts.isCancel(modelInput)) return modelInput;
|
|
366
|
-
return isBackInput(modelInput) ? backValue : splitModelInput(modelInput);
|
|
725
|
+
function fitRow(row, columns) {
|
|
726
|
+
if (columns <= 0) return "";
|
|
727
|
+
if (row.length <= columns) return row;
|
|
728
|
+
if (columns === 1) return "…";
|
|
729
|
+
return `${row.slice(0, columns - 1)}…`;
|
|
367
730
|
}
|
|
368
|
-
function
|
|
369
|
-
|
|
731
|
+
function formatDuration(ms) {
|
|
732
|
+
if (ms === void 0 || !Number.isFinite(ms)) return "?";
|
|
733
|
+
return `${(Math.max(ms, 0) / 1e3).toFixed(1)}s`;
|
|
370
734
|
}
|
|
371
|
-
function
|
|
372
|
-
return
|
|
735
|
+
function formatEstimatedDuration(ms) {
|
|
736
|
+
return `~${formatDuration(ms)}`;
|
|
373
737
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!options.providerEndpoint) {
|
|
378
|
-
if (options.noInteractive !== true) return runInteractiveSetup({
|
|
379
|
-
...io,
|
|
380
|
-
stdin: io.stdin ?? process.stdin
|
|
381
|
-
});
|
|
382
|
-
io.stderr.write("setup requires --provider-endpoint in --no-interactive mode.\n");
|
|
383
|
-
return 2;
|
|
384
|
-
}
|
|
385
|
-
if (!options.providerId) {
|
|
386
|
-
io.stderr.write("setup requires --provider-id in --no-interactive mode.\n");
|
|
387
|
-
return 2;
|
|
388
|
-
}
|
|
389
|
-
const setupConfigPath = options.local ? getProjectSetupConfigPath(io.cwd) : getGlobalSetupConfigPath(io.env ?? process.env);
|
|
390
|
-
await writeSetupConfig(setupConfigPath, mergeSetupConfigs(await loadSetupConfig(setupConfigPath), createSetupConfig(options.providerId, options.providerEndpoint, options)));
|
|
391
|
-
return 0;
|
|
738
|
+
function formatFilePath(filePath, cwd) {
|
|
739
|
+
if (!cwd) return filePath;
|
|
740
|
+
return relative$1(cwd, filePath) || filePath;
|
|
392
741
|
}
|
|
393
|
-
function
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
endpoint: providerEndpoint,
|
|
401
|
-
headers: parseHeaderList(toArray$1(options.providerHeader)),
|
|
402
|
-
id: providerId,
|
|
403
|
-
models,
|
|
404
|
-
type: "openai-compatible"
|
|
405
|
-
}],
|
|
406
|
-
version: 1
|
|
407
|
-
};
|
|
742
|
+
function formatFileRows(file, state, options, now) {
|
|
743
|
+
const firstRow = formatFileSummaryRow(file, state, options);
|
|
744
|
+
if (!file.rule) return [firstRow];
|
|
745
|
+
const elapsed = now - file.rule.startedAt;
|
|
746
|
+
const done = file.completed + file.cached + file.errored;
|
|
747
|
+
const estimated = estimateTotal(file.startedAt === void 0 ? elapsed : now - file.startedAt, done, file.file.planned ?? 0);
|
|
748
|
+
return [firstRow, fitRow(` ${file.rule.target} > ${file.rule.id} (${formatDuration(elapsed)}, ${formatEstimatedDuration(estimated)})`, options.columns)];
|
|
408
749
|
}
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
|
|
750
|
+
function formatFileSummaryRow(file, state, options) {
|
|
751
|
+
const prefix = `${options.spinnerFrames[state.spinnerIndex] ?? ""} ${formatFilePath(file.file.path, options.cwd)}`;
|
|
752
|
+
const counter = `${file.completed}/${file.cached}/${file.errored}/${file.file.planned ?? 0}`;
|
|
753
|
+
return fitRow(`${prefix}${" ".repeat(Math.max(1, options.columns - prefix.length - counter.length))}${counter}`, options.columns);
|
|
412
754
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
755
|
+
function formatFooter(state, warnCount, errorCount, queued, options, now) {
|
|
756
|
+
const endedAt = state.endedAt ?? now;
|
|
757
|
+
const elapsed = state.runStartedAt === void 0 ? void 0 : endedAt - state.runStartedAt;
|
|
758
|
+
const completed = state.completed + state.cached + state.errored;
|
|
759
|
+
const estimated = elapsed === void 0 ? void 0 : estimateTotal(elapsed, completed, state.planned);
|
|
760
|
+
const estimatedTokens = completed > 0 && state.planned > 0 ? Math.ceil(state.totalTokens * state.planned / completed).toLocaleString("en-US") : "?";
|
|
761
|
+
return fitRow([
|
|
762
|
+
`${formatDuration(elapsed)} -> ${formatEstimatedDuration(estimated)}`,
|
|
763
|
+
`${state.totalTokens.toLocaleString("en-US")} tokens -> ~${estimatedTokens} tokens`,
|
|
764
|
+
`${queued} queued / ${state.cached} cached / ${warnCount} warn / ${errorCount} error`
|
|
765
|
+
].join(" | "), options.columns);
|
|
417
766
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
const diagnostics = Array.isArray(input) ? input : input.diagnostics;
|
|
423
|
-
const totalTokens = Array.isArray(input) ? void 0 : input.usage.totalTokens;
|
|
424
|
-
if (diagnostics.length === 0) return "";
|
|
425
|
-
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
426
|
-
for (const diagnostic of diagnostics) {
|
|
427
|
-
const fileDiagnostics = diagnosticsByFile.get(diagnostic.filePath);
|
|
428
|
-
if (fileDiagnostics) {
|
|
429
|
-
fileDiagnostics.push(diagnostic);
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
diagnosticsByFile.set(diagnostic.filePath, [diagnostic]);
|
|
433
|
-
}
|
|
434
|
-
const lines = [];
|
|
435
|
-
const style = createStyle(options.color === true);
|
|
436
|
-
for (const [filePath, fileDiagnostics] of diagnosticsByFile) {
|
|
437
|
-
lines.push(style.file(filePath));
|
|
438
|
-
for (const diagnostic of fileDiagnostics) {
|
|
439
|
-
const line = diagnostic.loc?.start.line ?? 0;
|
|
440
|
-
const column = diagnostic.loc?.start.column ?? 0;
|
|
441
|
-
const severity = diagnostic.severity === "warn" ? style.warning("warning") : style.error("error");
|
|
442
|
-
lines.push(` ${style.location(`${line}:${column}`)} ${severity} ${diagnostic.message} ${style.ruleId(diagnostic.ruleId)}`);
|
|
443
|
-
}
|
|
444
|
-
lines.push("");
|
|
445
|
-
}
|
|
446
|
-
lines.push("", formatSummary(diagnostics, totalTokens, style));
|
|
447
|
-
return `${lines.join("\n")}\n`;
|
|
767
|
+
function formatIdleRow(state, options) {
|
|
768
|
+
const prefix = `${options.spinnerFrames[state.spinnerIndex] ?? ""} alint`;
|
|
769
|
+
const counter = `${state.completed}/${state.cached}/${state.errored}/${state.planned}`;
|
|
770
|
+
return fitRow(`${prefix}${" ".repeat(Math.max(1, options.columns - prefix.length - counter.length))}${counter}`, options.columns);
|
|
448
771
|
}
|
|
449
|
-
function
|
|
450
|
-
return
|
|
772
|
+
function formatQueuedRow(queued, options) {
|
|
773
|
+
return fitRow(` ${queued} ${queued === 1 ? "file" : "files"} queued`, options.columns);
|
|
451
774
|
}
|
|
452
|
-
function
|
|
453
|
-
|
|
454
|
-
error: identity,
|
|
455
|
-
file: identity,
|
|
456
|
-
location: identity,
|
|
457
|
-
ruleId: identity,
|
|
458
|
-
summaryToken: identity,
|
|
459
|
-
warning: identity
|
|
460
|
-
};
|
|
461
|
-
return {
|
|
462
|
-
error: colors$1.red,
|
|
463
|
-
file: colors$1.underline,
|
|
464
|
-
location: colors$1.dim,
|
|
465
|
-
ruleId: colors$1.dim,
|
|
466
|
-
summaryToken: colors$1.cyan,
|
|
467
|
-
warning: colors$1.yellow
|
|
468
|
-
};
|
|
775
|
+
function formatTarget(payload) {
|
|
776
|
+
return payload.path.target.name ? `${payload.path.target.kind} ${payload.path.target.name}` : payload.path.target.kind;
|
|
469
777
|
}
|
|
470
|
-
function
|
|
471
|
-
|
|
472
|
-
const errorCount = countDiagnostics$2(diagnostics, "error");
|
|
473
|
-
const tokens = totalTokens === void 0 ? void 0 : `${totalTokens.toLocaleString("en-US")} tokens`;
|
|
474
|
-
const problemSummary = [style.warning(`${warnCount} warn`), style.error(`${errorCount} error`)].join(" / ");
|
|
475
|
-
if (tokens === void 0) return problemSummary;
|
|
476
|
-
return `${problemSummary} | ${style.summaryToken(tokens)}`;
|
|
778
|
+
function getActiveFiles(state) {
|
|
779
|
+
return [...state.files.values()].filter((file) => file.startedAt !== void 0 && file.endedAt === void 0).sort((left, right) => left.file.index - right.file.index);
|
|
477
780
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
781
|
+
function getFileState(state, file) {
|
|
782
|
+
const existingFile = state.files.get(file.path);
|
|
783
|
+
if (existingFile) {
|
|
784
|
+
existingFile.file = {
|
|
785
|
+
...existingFile.file,
|
|
786
|
+
...file,
|
|
787
|
+
planned: file.planned ?? existingFile.file.planned
|
|
788
|
+
};
|
|
789
|
+
return existingFile;
|
|
790
|
+
}
|
|
791
|
+
const nextFile = createFileState({
|
|
792
|
+
...file,
|
|
793
|
+
planned: file.planned ?? (state.files.size === 0 ? state.planned : void 0)
|
|
794
|
+
});
|
|
795
|
+
state.files.set(file.path, nextFile);
|
|
796
|
+
return nextFile;
|
|
480
797
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
798
|
+
function replaceFirst(row, search, replacement) {
|
|
799
|
+
if (search.length === 0) return row;
|
|
800
|
+
return row.replace(new RegExp(escapeRegExp(search)), replacement);
|
|
801
|
+
}
|
|
802
|
+
function styleRow(row, state, warnCount, errorCount, options) {
|
|
803
|
+
let styledRow = row;
|
|
804
|
+
const spinner = options.spinnerFrames[state.spinnerIndex] ?? "";
|
|
805
|
+
if (spinner) styledRow = replaceFirst(styledRow, spinner, colors.cyan(spinner));
|
|
806
|
+
styledRow = styledRow.replace(/\|/g, colors.gray("|")).replace(`${warnCount} warn`, colors.yellow(`${warnCount} warn`)).replace(`${errorCount} error`, (errorCount > 0 ? colors.red : colors.gray)(`${errorCount} error`)).replace(/(\d+\/\d+\/\d+\/\d+)/, (match) => state.errored > 0 ? colors.red(match) : colors.gray(match)).replace(/(\d[\d,]* tokens)/g, (match) => colors.cyan(match));
|
|
807
|
+
return styledRow;
|
|
487
808
|
}
|
|
488
809
|
//#endregion
|
|
489
|
-
//#region src/cli/reporters/progress/
|
|
490
|
-
|
|
491
|
-
|
|
810
|
+
//#region src/cli/reporters/progress/tty.ts
|
|
811
|
+
const clearCurrentLine = "\r\x1B[K";
|
|
812
|
+
const clearPreviousLine = "\r\x1B[1A\x1B[K";
|
|
813
|
+
function createTtyProgressRenderer(options) {
|
|
814
|
+
let interval;
|
|
815
|
+
let previousRows = 0;
|
|
816
|
+
const clearPreviousFrame = () => {
|
|
817
|
+
if (previousRows === 0) return;
|
|
818
|
+
let sequence = clearCurrentLine;
|
|
819
|
+
for (let row = 1; row < previousRows; row += 1) sequence += clearPreviousLine;
|
|
820
|
+
options.write(sequence);
|
|
821
|
+
previousRows = 0;
|
|
822
|
+
};
|
|
823
|
+
const render = () => {
|
|
824
|
+
clearPreviousFrame();
|
|
825
|
+
const rows = options.getRows();
|
|
826
|
+
if (rows.length === 0) return;
|
|
827
|
+
options.write(rows.join("\n"));
|
|
828
|
+
previousRows = rows.length;
|
|
829
|
+
};
|
|
830
|
+
const write = (chunk) => {
|
|
831
|
+
const wasRendering = interval !== void 0;
|
|
832
|
+
clearPreviousFrame();
|
|
833
|
+
options.write(chunk);
|
|
834
|
+
if (wasRendering) render();
|
|
835
|
+
};
|
|
492
836
|
return {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
837
|
+
finish: () => {
|
|
838
|
+
if (interval) {
|
|
839
|
+
options.clearInterval(interval);
|
|
840
|
+
interval = void 0;
|
|
841
|
+
}
|
|
842
|
+
clearPreviousFrame();
|
|
496
843
|
},
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
844
|
+
render,
|
|
845
|
+
start: () => {
|
|
846
|
+
if (!interval) {
|
|
847
|
+
interval = options.createInterval(render, options.intervalMs);
|
|
848
|
+
if (isUnrefableInterval(interval)) interval.unref();
|
|
849
|
+
}
|
|
850
|
+
render();
|
|
504
851
|
},
|
|
505
|
-
|
|
506
|
-
writeLine(`alint started: ${payload.filesTotal} files, ${payload.rulesTotal} rules, ${payload.planned} planned executions`);
|
|
507
|
-
}
|
|
852
|
+
write
|
|
508
853
|
};
|
|
509
854
|
}
|
|
510
|
-
function
|
|
511
|
-
|
|
855
|
+
function isUnrefableInterval(interval) {
|
|
856
|
+
if (typeof interval !== "object" || interval === null || !("unref" in interval)) return false;
|
|
857
|
+
return typeof interval.unref === "function";
|
|
512
858
|
}
|
|
513
859
|
//#endregion
|
|
514
|
-
//#region src/cli/reporters/progress/
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
860
|
+
//#region src/cli/reporters/progress/index.ts
|
|
861
|
+
function createCliProgressReporter(options) {
|
|
862
|
+
if (!options.isTty) return {
|
|
863
|
+
dispose: () => {},
|
|
864
|
+
reporter: createPlainProgressReporter({ write: options.write }),
|
|
865
|
+
write: options.write
|
|
866
|
+
};
|
|
867
|
+
const summary = createSummaryProgressReporter({
|
|
868
|
+
color: options.color,
|
|
869
|
+
columns: options.columns,
|
|
870
|
+
cwd: options.cwd,
|
|
871
|
+
spinnerFrames: cliSpinners.dots.frames
|
|
872
|
+
});
|
|
873
|
+
const renderer = createTtyProgressRenderer({
|
|
874
|
+
clearInterval: (handle) => globalThis.clearInterval(handle),
|
|
875
|
+
createInterval: (callback, intervalMs) => globalThis.setInterval(() => {
|
|
876
|
+
summary.tick();
|
|
877
|
+
callback();
|
|
878
|
+
}, intervalMs),
|
|
879
|
+
getRows: summary.getRows,
|
|
880
|
+
intervalMs: 120,
|
|
881
|
+
write: options.write
|
|
882
|
+
});
|
|
883
|
+
const reporter = createRenderingProgressReporter(summary, renderer);
|
|
884
|
+
return {
|
|
885
|
+
dispose: renderer.finish,
|
|
886
|
+
reporter,
|
|
887
|
+
write: renderer.write
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function createRenderingProgressReporter(summary, renderer) {
|
|
528
891
|
return {
|
|
529
|
-
getRows: () => createRows(state, options, now()),
|
|
530
892
|
onDiagnostic: (payload) => {
|
|
531
|
-
|
|
893
|
+
summary.onDiagnostic?.(payload);
|
|
894
|
+
renderer.render();
|
|
532
895
|
},
|
|
533
896
|
onFileEnd: (payload) => {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
file.rule = void 0;
|
|
537
|
-
file.target = void 0;
|
|
897
|
+
summary.onFileEnd?.(payload);
|
|
898
|
+
renderer.render();
|
|
538
899
|
},
|
|
539
900
|
onFileStart: (payload) => {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
file.endedAt = void 0;
|
|
901
|
+
summary.onFileStart?.(payload);
|
|
902
|
+
renderer.render();
|
|
543
903
|
},
|
|
544
904
|
onRuleEnd: (payload) => {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
state.cached += 1;
|
|
548
|
-
file.cached += 1;
|
|
549
|
-
}
|
|
550
|
-
if (payload.state === "completed") {
|
|
551
|
-
state.completed += 1;
|
|
552
|
-
file.completed += 1;
|
|
553
|
-
}
|
|
554
|
-
if (payload.state === "errored") {
|
|
555
|
-
state.errored += 1;
|
|
556
|
-
file.errored += 1;
|
|
557
|
-
}
|
|
558
|
-
if (file.rule?.id === payload.path.rule.id) file.rule = void 0;
|
|
905
|
+
summary.onRuleEnd?.(payload);
|
|
906
|
+
renderer.render();
|
|
559
907
|
},
|
|
560
908
|
onRuleStart: (payload) => {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
file.rule = {
|
|
564
|
-
id: payload.path.rule.id,
|
|
565
|
-
startedAt: payload.startedAt ?? now(),
|
|
566
|
-
target: formatTarget(payload)
|
|
567
|
-
};
|
|
568
|
-
file.target = file.rule.target;
|
|
909
|
+
summary.onRuleStart?.(payload);
|
|
910
|
+
renderer.render();
|
|
569
911
|
},
|
|
570
912
|
onRunEnd: (payload) => {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
state.diagnostics = payload.diagnostics;
|
|
574
|
-
state.endedAt = payload.endedAt ?? now();
|
|
575
|
-
state.errored = payload.errored;
|
|
576
|
-
state.planned = payload.planned;
|
|
577
|
-
state.runStartedAt = payload.startedAt ?? state.runStartedAt;
|
|
578
|
-
state.totalTokens = payload.usage.totalTokens;
|
|
913
|
+
summary.onRunEnd?.(payload);
|
|
914
|
+
renderer.render();
|
|
579
915
|
},
|
|
580
916
|
onRunStart: (payload) => {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
state.diagnostics = [];
|
|
584
|
-
state.endedAt = void 0;
|
|
585
|
-
state.errored = 0;
|
|
586
|
-
state.files = /* @__PURE__ */ new Map();
|
|
587
|
-
state.planned = payload.planned;
|
|
588
|
-
state.runStartedAt = payload.startedAt ?? now();
|
|
589
|
-
state.spinnerIndex = 0;
|
|
590
|
-
state.totalTokens = 0;
|
|
591
|
-
for (const file of payload.files ?? []) state.files.set(file.path, createFileState(file));
|
|
917
|
+
summary.onRunStart?.(payload);
|
|
918
|
+
renderer.start();
|
|
592
919
|
},
|
|
593
920
|
onTargetEnd: (payload) => {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (file.target === target) file.target = void 0;
|
|
921
|
+
summary.onTargetEnd?.(payload);
|
|
922
|
+
renderer.render();
|
|
597
923
|
},
|
|
598
924
|
onTargetStart: (payload) => {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
file.target = formatTarget(payload);
|
|
925
|
+
summary.onTargetStart?.(payload);
|
|
926
|
+
renderer.render();
|
|
602
927
|
},
|
|
603
928
|
onUsage: (payload) => {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
tick: () => {
|
|
607
|
-
state.spinnerIndex = (state.spinnerIndex + 1) % Math.max(options.spinnerFrames.length, 1);
|
|
929
|
+
summary.onUsage?.(payload);
|
|
930
|
+
renderer.render();
|
|
608
931
|
}
|
|
609
932
|
};
|
|
610
933
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
function createRows(state, options, now) {
|
|
626
|
-
const rows = getActiveFiles(state).flatMap((file) => formatFileRows(file, state, options, now));
|
|
627
|
-
const queued = countQueuedFiles(state);
|
|
628
|
-
const warnCount = countDiagnostics(state.diagnostics, "warn");
|
|
629
|
-
const errorCount = countDiagnostics(state.diagnostics, "error");
|
|
630
|
-
const footer = formatFooter(state, warnCount, errorCount, queued, options, now);
|
|
631
|
-
if (queued > 0) rows.push(formatQueuedRow(queued, options));
|
|
632
|
-
if (rows.length === 0) rows.push(formatIdleRow(state, options));
|
|
633
|
-
rows.push("", footer);
|
|
634
|
-
return options.color ? rows.map((row) => styleRow(row, state, warnCount, errorCount, options)) : rows;
|
|
635
|
-
}
|
|
636
|
-
function escapeRegExp(value) {
|
|
637
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
638
|
-
}
|
|
639
|
-
function estimateTotal(elapsedMs, completed, planned) {
|
|
640
|
-
if (completed <= 0 || planned <= 0) return;
|
|
641
|
-
return elapsedMs * planned / completed;
|
|
642
|
-
}
|
|
643
|
-
function fitRow(row, columns) {
|
|
644
|
-
if (columns <= 0) return "";
|
|
645
|
-
if (row.length <= columns) return row;
|
|
646
|
-
if (columns === 1) return "…";
|
|
647
|
-
return `${row.slice(0, columns - 1)}…`;
|
|
934
|
+
//#endregion
|
|
935
|
+
//#region src/cli/commands/lint/discovery.ts
|
|
936
|
+
async function resolveLintFiles(files, config, cwd) {
|
|
937
|
+
const gitignore = shouldFilterGitignoredFiles(config) ? new Gitignore() : void 0;
|
|
938
|
+
const candidates = files.length > 0 ? files : await discoverLintFiles(config, cwd, gitignore);
|
|
939
|
+
if (!gitignore || candidates.length === 0) return candidates;
|
|
940
|
+
const lintFiles = [];
|
|
941
|
+
for (const file of candidates) {
|
|
942
|
+
if (await gitignore.ignores(resolve(cwd, file))) continue;
|
|
943
|
+
lintFiles.push(file);
|
|
944
|
+
}
|
|
945
|
+
return lintFiles;
|
|
648
946
|
}
|
|
649
|
-
function
|
|
650
|
-
|
|
651
|
-
|
|
947
|
+
function collectGlobalIgnorePatterns(config) {
|
|
948
|
+
return normalizeConfig(config).flatMap((item) => isGlobalIgnoreItem(item) ? [...item.ignores] : []);
|
|
949
|
+
}
|
|
950
|
+
async function discoverLintFiles(config, cwd, gitignore) {
|
|
951
|
+
if (!hasDiscoveryFilePatterns(config)) return [];
|
|
952
|
+
const candidates = (await walkFiles(cwd, {
|
|
953
|
+
cwd,
|
|
954
|
+
gitignore,
|
|
955
|
+
ignoredPatterns: collectGlobalIgnorePatterns(config)
|
|
956
|
+
})).map((file) => normalizeRelativePath(cwd, file)).filter((file) => matchesDiscoveryFile(file, config, { cwd }));
|
|
957
|
+
return [...new Set(candidates)].sort();
|
|
958
|
+
}
|
|
959
|
+
function isGlobalIgnoreItem(item) {
|
|
960
|
+
const keys = Object.keys(item).filter((key) => item[key] !== void 0);
|
|
961
|
+
return item.ignores !== void 0 && keys.every((key) => key === "ignores" || key === "name");
|
|
962
|
+
}
|
|
963
|
+
function matchesIgnoredDirectory(relativePath, patterns) {
|
|
964
|
+
return patterns.some((pattern) => minimatch(relativePath, pattern, { dot: true }) || minimatch(`${relativePath}/`, pattern, { dot: true }) || minimatch(`${relativePath}/__alint__`, pattern, { dot: true }));
|
|
965
|
+
}
|
|
966
|
+
function normalizeRelativePath(cwd, filePath) {
|
|
967
|
+
return relative(cwd, filePath).replaceAll("\\", "/");
|
|
968
|
+
}
|
|
969
|
+
function shouldFilterGitignoredFiles(config) {
|
|
970
|
+
return normalizeConfig(config).some((item) => item.ignore?.gitignore === true);
|
|
971
|
+
}
|
|
972
|
+
async function shouldPruneDirectory(path, options) {
|
|
973
|
+
if (matchesIgnoredDirectory(normalizeRelativePath(options.cwd, path), options.ignoredPatterns)) return true;
|
|
974
|
+
return await options.gitignore?.ignores(path) === true;
|
|
975
|
+
}
|
|
976
|
+
async function walkFiles(root, options) {
|
|
977
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
978
|
+
const files = [];
|
|
979
|
+
for (const entry of entries) {
|
|
980
|
+
const path = resolve(root, entry.name);
|
|
981
|
+
if (entry.isDirectory()) {
|
|
982
|
+
if (!await shouldPruneDirectory(path, options)) files.push(...await walkFiles(path, options));
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
if (entry.isFile()) files.push(path);
|
|
986
|
+
}
|
|
987
|
+
return files;
|
|
652
988
|
}
|
|
653
|
-
|
|
654
|
-
|
|
989
|
+
//#endregion
|
|
990
|
+
//#region src/cli/commands/lint/errors.ts
|
|
991
|
+
function formatRunError(error, color) {
|
|
992
|
+
return `${color ? c.red("error") : "error"} ${formatRunErrorContext(error)}\n Rule running failed due to ${error.failure?.message ?? error.message}\n`;
|
|
655
993
|
}
|
|
656
|
-
function
|
|
657
|
-
|
|
658
|
-
|
|
994
|
+
function formatRunErrorContext(error) {
|
|
995
|
+
const failure = error.failure;
|
|
996
|
+
if (!failure) return "alint run failed";
|
|
997
|
+
const target = failure.target ? failure.target.name ? `${failure.target.kind} ${failure.target.name}` : failure.target.kind : void 0;
|
|
998
|
+
return [
|
|
999
|
+
failure.filePath,
|
|
1000
|
+
target,
|
|
1001
|
+
failure.ruleId
|
|
1002
|
+
].filter(Boolean).join(" > ");
|
|
659
1003
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1004
|
+
//#endregion
|
|
1005
|
+
//#region src/cli/commands/lint/runner.ts
|
|
1006
|
+
function resolveConfigRunner(config) {
|
|
1007
|
+
return normalizeConfig(config).reduce((merged, item) => item.runner ? {
|
|
1008
|
+
...merged,
|
|
1009
|
+
...item.runner
|
|
1010
|
+
} : merged, void 0);
|
|
667
1011
|
}
|
|
668
|
-
function
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
|
|
1012
|
+
function resolveRunnerConfig(setupConfig, config, options) {
|
|
1013
|
+
const cache = resolveRunnerCacheConfig(setupConfig.runner?.cache, config.runner?.cache, options);
|
|
1014
|
+
const fileConcurrency = parsePositiveIntegerOption(options.fileConcurrency, "--file-concurrency");
|
|
1015
|
+
const ruleConcurrency = parsePositiveIntegerOption(options.ruleConcurrency, "--rule-concurrency");
|
|
1016
|
+
const timeoutMs = parsePositiveIntegerOption(options.timeoutMs, "--timeout-ms");
|
|
1017
|
+
const runner = {
|
|
1018
|
+
...setupConfig.runner ?? {},
|
|
1019
|
+
...config.runner ?? {},
|
|
1020
|
+
cache,
|
|
1021
|
+
fileConcurrency: fileConcurrency ?? config.runner?.fileConcurrency ?? setupConfig.runner?.fileConcurrency,
|
|
1022
|
+
ruleConcurrency: ruleConcurrency ?? config.runner?.ruleConcurrency ?? setupConfig.runner?.ruleConcurrency,
|
|
1023
|
+
timeoutMs: timeoutMs ?? config.runner?.timeoutMs ?? setupConfig.runner?.timeoutMs
|
|
1024
|
+
};
|
|
1025
|
+
return Object.values(runner).some((value) => value !== void 0) ? runner : void 0;
|
|
672
1026
|
}
|
|
673
|
-
function
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
`${state.totalTokens.toLocaleString("en-US")} tokens -> ~${estimatedTokens} tokens`,
|
|
682
|
-
`${queued} queued / ${state.cached} cached / ${warnCount} warn / ${errorCount} error`
|
|
683
|
-
].join(" | "), options.columns);
|
|
1027
|
+
function mergeRunnerCacheConfig(setupCache, configCache) {
|
|
1028
|
+
if (configCache === void 0) return setupCache;
|
|
1029
|
+
if (typeof configCache === "boolean") return configCache;
|
|
1030
|
+
if (typeof setupCache === "object") return {
|
|
1031
|
+
...setupCache,
|
|
1032
|
+
...configCache
|
|
1033
|
+
};
|
|
1034
|
+
return configCache;
|
|
684
1035
|
}
|
|
685
|
-
function
|
|
686
|
-
|
|
687
|
-
const
|
|
688
|
-
|
|
1036
|
+
function parsePositiveIntegerOption(value, label) {
|
|
1037
|
+
if (value === void 0) return;
|
|
1038
|
+
const parsed = Number(value);
|
|
1039
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${label} must be a positive integer.`);
|
|
1040
|
+
return parsed;
|
|
689
1041
|
}
|
|
690
|
-
function
|
|
691
|
-
|
|
1042
|
+
function resolveRunnerCacheConfig(setupCache, configCache, options) {
|
|
1043
|
+
if (options.cache === false) return false;
|
|
1044
|
+
const configuredCache = mergeRunnerCacheConfig(setupCache, configCache);
|
|
1045
|
+
if (options.cacheLocation !== void 0) return typeof configuredCache === "object" ? {
|
|
1046
|
+
...configuredCache,
|
|
1047
|
+
location: options.cacheLocation
|
|
1048
|
+
} : { location: options.cacheLocation };
|
|
1049
|
+
return configuredCache;
|
|
692
1050
|
}
|
|
693
|
-
|
|
694
|
-
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region src/cli/commands/lint/index.ts
|
|
1053
|
+
const lint = defineCommand({
|
|
1054
|
+
action: (context, files = [], options) => runLintCommand(files, {
|
|
1055
|
+
...options,
|
|
1056
|
+
outputLanguage: options.lang ?? context.globalOptions.outputLanguage
|
|
1057
|
+
}, context.io, context.interceptConsoleOutput),
|
|
1058
|
+
alias: ["!"],
|
|
1059
|
+
arguments: "[...files]",
|
|
1060
|
+
default: true,
|
|
1061
|
+
description: "Run alint",
|
|
1062
|
+
name: "lint"
|
|
1063
|
+
});
|
|
1064
|
+
async function assertConfigExists(cwd, configPath) {
|
|
1065
|
+
const resolvedConfigPath = resolve(cwd, configPath);
|
|
1066
|
+
try {
|
|
1067
|
+
if (!(await stat(resolvedConfigPath)).isFile()) throw new Error(`Config file "${configPath}" is not a file.`);
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
if (isNodeError(error) && error.code === "ENOENT") throw new Error(`Config file "${configPath}" does not exist.`);
|
|
1070
|
+
throw error;
|
|
1071
|
+
}
|
|
695
1072
|
}
|
|
696
|
-
function
|
|
697
|
-
return
|
|
1073
|
+
function isNodeError(error) {
|
|
1074
|
+
return error instanceof Error && "code" in error;
|
|
698
1075
|
}
|
|
699
|
-
function
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1076
|
+
async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
1077
|
+
if (options.config) await assertConfigExists(io.cwd, options.config);
|
|
1078
|
+
const [setupConfig, config] = await Promise.all([loadMergedSetupConfig(io), loadAlintConfig(io.cwd, options.config)]);
|
|
1079
|
+
const lintFiles = await resolveLintFiles(files, config, io.cwd);
|
|
1080
|
+
const runner = resolveRunnerConfig(setupConfig, { runner: resolveConfigRunner(config) }, options);
|
|
1081
|
+
const progress = shouldEnableProgress(options, io) ? createCliProgressReporter({
|
|
1082
|
+
color: io.stderr.isTTY === true,
|
|
1083
|
+
columns: io.stderr.columns ?? 80,
|
|
1084
|
+
cwd: io.cwd,
|
|
1085
|
+
isTty: io.stderr.isTTY === true,
|
|
1086
|
+
write: (chunk) => io.stderr.write(chunk)
|
|
1087
|
+
}) : void 0;
|
|
1088
|
+
const restoreProgressConsole = progress ? interceptConsoleOutput({ write: progress.write }) : void 0;
|
|
1089
|
+
let result;
|
|
1090
|
+
try {
|
|
1091
|
+
result = await runAlint({
|
|
1092
|
+
config,
|
|
1093
|
+
cwd: io.cwd,
|
|
1094
|
+
files: lintFiles,
|
|
1095
|
+
modelOverride: options.model,
|
|
1096
|
+
outputLanguage: options.outputLanguage,
|
|
1097
|
+
progress: progress?.reporter,
|
|
1098
|
+
runner,
|
|
1099
|
+
setupConfig
|
|
1100
|
+
});
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
restoreProgressConsole?.();
|
|
1103
|
+
progress?.dispose();
|
|
1104
|
+
if (error instanceof AlintRunError) {
|
|
1105
|
+
io.stderr.write(formatRunError(error, io.stderr.isTTY === true));
|
|
1106
|
+
return 2;
|
|
1107
|
+
}
|
|
1108
|
+
throw error;
|
|
708
1109
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
state.files.set(file.path, nextFile);
|
|
714
|
-
return nextFile;
|
|
1110
|
+
restoreProgressConsole?.();
|
|
1111
|
+
progress?.dispose();
|
|
1112
|
+
io.stdout.write(formatDiagnostics(options.format, result, { color: io.stdout.isTTY === true }));
|
|
1113
|
+
return result.diagnostics.length > 0 ? 1 : 0;
|
|
715
1114
|
}
|
|
716
|
-
function
|
|
717
|
-
if (
|
|
718
|
-
return
|
|
1115
|
+
function shouldEnableProgress(options, io) {
|
|
1116
|
+
if (options.progress !== void 0) return options.progress;
|
|
1117
|
+
return options.format === "stylish" && io.stderr.isTTY === true;
|
|
719
1118
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region src/cli/commands/output-inspect/inspect.ts
|
|
1121
|
+
const inspect$1 = defineCommand({
|
|
1122
|
+
action: (context, file, options) => inspectOutputFile(context.io, file, options),
|
|
1123
|
+
arguments: "<file>",
|
|
1124
|
+
description: "Inspect saved alint JSON output",
|
|
1125
|
+
examples: [
|
|
1126
|
+
["# Pretty-print saved JSON output", "alint output inspect alint-output.json"].join("\n"),
|
|
1127
|
+
["# Validate and reprint normalized JSON", "alint output inspect alint-output.json --format json"].join("\n"),
|
|
1128
|
+
[
|
|
1129
|
+
"# Save a run as JSON, then inspect it later",
|
|
1130
|
+
"alint --format json src > alint-output.json",
|
|
1131
|
+
"alint output inspect alint-output.json"
|
|
1132
|
+
].join("\n")
|
|
1133
|
+
],
|
|
1134
|
+
help: ["Read a saved alint JSON run result and render it with a reporter.", "Defaults to the human-friendly stylish reporter, which groups diagnostics by file and prints the same summary style as a normal alint run. Use `--format json` to validate the file and reprint normalized JSON."].join("\n\n"),
|
|
1135
|
+
name: "inspect",
|
|
1136
|
+
options: [{
|
|
1137
|
+
config: { default: "stylish" },
|
|
1138
|
+
description: "Reporter used to render the parsed run result. One of: stylish, json",
|
|
1139
|
+
flags: "-f, --format <format>"
|
|
1140
|
+
}]
|
|
1141
|
+
});
|
|
1142
|
+
async function inspectOutputFile(io, file, options) {
|
|
1143
|
+
const filePath = resolve(io.cwd, file);
|
|
1144
|
+
let text;
|
|
1145
|
+
try {
|
|
1146
|
+
text = await readFile(filePath, "utf8");
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
io.stderr.write(`Could not read output file "${file}": ${errorMessageFrom$1(error)}\n`);
|
|
1149
|
+
return 2;
|
|
1150
|
+
}
|
|
1151
|
+
let parsed;
|
|
1152
|
+
try {
|
|
1153
|
+
parsed = JSON.parse(text);
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
io.stderr.write(`Could not parse output file "${file}": ${errorMessageFrom$1(error)}\n`);
|
|
1156
|
+
return 2;
|
|
1157
|
+
}
|
|
1158
|
+
if (!isRunResult(parsed)) {
|
|
1159
|
+
io.stderr.write(`Invalid alint output "${file}": expected a run result with diagnostics and usage.\n`);
|
|
1160
|
+
return 2;
|
|
1161
|
+
}
|
|
1162
|
+
try {
|
|
1163
|
+
io.stdout.write(formatDiagnostics(options.format, parsed, { color: io.stdout.isTTY === true }));
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
io.stderr.write(`${errorMessageFrom$1(error)}\n`);
|
|
1166
|
+
return 2;
|
|
1167
|
+
}
|
|
1168
|
+
return parsed.diagnostics.length > 0 ? 1 : 0;
|
|
1169
|
+
}
|
|
1170
|
+
function isDiagnostic(value) {
|
|
1171
|
+
if (!isRecord(value)) return false;
|
|
1172
|
+
if (typeof value.filePath !== "string" || typeof value.message !== "string" || typeof value.ruleId !== "string" || value.severity !== "warn" && value.severity !== "error") return false;
|
|
1173
|
+
if (value.loc === void 0) return true;
|
|
1174
|
+
if (!isRecord(value.loc)) return false;
|
|
1175
|
+
if (value.loc.start === void 0) return true;
|
|
1176
|
+
if (!isRecord(value.loc.start)) return false;
|
|
1177
|
+
return (value.loc.start.line === void 0 || typeof value.loc.start.line === "number") && (value.loc.start.column === void 0 || typeof value.loc.start.column === "number");
|
|
1178
|
+
}
|
|
1179
|
+
function isRecord(value) {
|
|
1180
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1181
|
+
}
|
|
1182
|
+
function isRunResult(value) {
|
|
1183
|
+
if (!isRecord(value)) return false;
|
|
1184
|
+
if (!Array.isArray(value.diagnostics) || !value.diagnostics.every(isDiagnostic)) return false;
|
|
1185
|
+
return isUsage(value.usage);
|
|
1186
|
+
}
|
|
1187
|
+
function isUsage(value) {
|
|
1188
|
+
return isRecord(value) && typeof value.inputTokens === "number" && typeof value.outputTokens === "number" && Array.isArray(value.records) && typeof value.totalTokens === "number";
|
|
726
1189
|
}
|
|
727
1190
|
//#endregion
|
|
728
|
-
//#region src/cli/
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1191
|
+
//#region src/cli/commands/output-inspect/output.ts
|
|
1192
|
+
const output = defineCommand({
|
|
1193
|
+
children: [inspect$1],
|
|
1194
|
+
description: "Inspect alint output files",
|
|
1195
|
+
help: ["Inspect saved alint run outputs without rerunning rules or model calls.", "Use this when you already have JSON from `alint --format json` and want to render, validate, or transform that result for humans or tools."].join("\n\n"),
|
|
1196
|
+
name: "output"
|
|
1197
|
+
});
|
|
1198
|
+
//#endregion
|
|
1199
|
+
//#region src/cli/commands/setup/interactive.ts
|
|
1200
|
+
const nonTtyMessage = "interactive setup requires a TTY. Use -N/--no-interactive with --provider-id and --provider-endpoint.\n";
|
|
1201
|
+
const backValue = "__alint_back__";
|
|
1202
|
+
function formatProbeModelsFailure(endpoint, error) {
|
|
1203
|
+
const hint = endpoint.startsWith("https://localhost:11434") ? " Ollama usually uses http://localhost:11434/v1." : "";
|
|
1204
|
+
return `Could not probe models: ${errorMessageFrom(error)}.${hint}`;
|
|
1205
|
+
}
|
|
1206
|
+
function isBackInput(value) {
|
|
1207
|
+
return value.trim() === "..";
|
|
1208
|
+
}
|
|
1209
|
+
async function runInteractiveSetup(io) {
|
|
1210
|
+
if (io.stdin?.isTTY !== true || io.stdout.isTTY !== true) {
|
|
1211
|
+
io.stderr.write(nonTtyMessage);
|
|
1212
|
+
return 2;
|
|
1213
|
+
}
|
|
1214
|
+
const prompts = await import("@clack/prompts");
|
|
1215
|
+
const cancelPrompt = () => {
|
|
1216
|
+
prompts.cancel("Setup cancelled.");
|
|
1217
|
+
return 1;
|
|
753
1218
|
};
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1219
|
+
prompts.intro("alint setup");
|
|
1220
|
+
const draft = {};
|
|
1221
|
+
let step = "scope";
|
|
1222
|
+
while (true) {
|
|
1223
|
+
if (step === "scope") {
|
|
1224
|
+
const scope = await prompts.select({
|
|
1225
|
+
message: "Where should alint write setup config?",
|
|
1226
|
+
options: [{
|
|
1227
|
+
label: "Global",
|
|
1228
|
+
value: "global"
|
|
1229
|
+
}, {
|
|
1230
|
+
label: "Local project",
|
|
1231
|
+
value: "local"
|
|
1232
|
+
}]
|
|
1233
|
+
});
|
|
1234
|
+
if (prompts.isCancel(scope)) return cancelPrompt();
|
|
1235
|
+
draft.scope = scope;
|
|
1236
|
+
step = "source";
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1239
|
+
if (step === "source") {
|
|
1240
|
+
const source = await prompts.select({
|
|
1241
|
+
message: "Choose provider setup mode.",
|
|
1242
|
+
options: withBackOption([
|
|
1243
|
+
{
|
|
1244
|
+
label: "Custom OpenAI-compatible provider",
|
|
1245
|
+
value: "custom"
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
label: "Ollama",
|
|
1249
|
+
value: "ollama"
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
label: "Manual model entry",
|
|
1253
|
+
value: "manual"
|
|
1254
|
+
}
|
|
1255
|
+
])
|
|
1256
|
+
});
|
|
1257
|
+
if (prompts.isCancel(source)) return cancelPrompt();
|
|
1258
|
+
if (source === backValue) {
|
|
1259
|
+
step = "scope";
|
|
1260
|
+
continue;
|
|
759
1261
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1262
|
+
draft.source = source;
|
|
1263
|
+
step = "endpoint";
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (step === "endpoint") {
|
|
1267
|
+
const endpoint = await promptEndpoint(prompts, draft.source ?? "custom");
|
|
1268
|
+
if (prompts.isCancel(endpoint)) return cancelPrompt();
|
|
1269
|
+
if (typeof endpoint !== "string") return cancelPrompt();
|
|
1270
|
+
if (isBackInput(endpoint)) {
|
|
1271
|
+
step = "source";
|
|
1272
|
+
continue;
|
|
767
1273
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1274
|
+
draft.endpoint = endpoint;
|
|
1275
|
+
step = "providerId";
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
if (step === "providerId") {
|
|
1279
|
+
const existingConfig = await loadSetupConfig(getConfigPath(io, draft.scope ?? "global"));
|
|
1280
|
+
const providerId = await prompts.text({
|
|
1281
|
+
defaultValue: draft.providerId ?? createProviderId(draft.endpoint ?? "", new Set(existingConfig.providers.map((provider) => provider.id))),
|
|
1282
|
+
message: "Provider id",
|
|
1283
|
+
placeholder: "Type .. to go back",
|
|
1284
|
+
validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider id is required."
|
|
1285
|
+
});
|
|
1286
|
+
if (prompts.isCancel(providerId)) return cancelPrompt();
|
|
1287
|
+
if (typeof providerId !== "string") return cancelPrompt();
|
|
1288
|
+
if (isBackInput(providerId)) {
|
|
1289
|
+
step = "endpoint";
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
draft.providerId = providerId;
|
|
1293
|
+
step = "headers";
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
if (step === "headers") {
|
|
1297
|
+
const headerInput = await prompts.text({
|
|
1298
|
+
defaultValue: draft.headerInput ?? "",
|
|
1299
|
+
message: "Headers",
|
|
1300
|
+
placeholder: "Authorization=Bearer token, X-Test=true; type .. to go back",
|
|
1301
|
+
validate: (value) => {
|
|
1302
|
+
if (isBackInput(value ?? "")) return;
|
|
1303
|
+
try {
|
|
1304
|
+
parseHeaderList(splitHeaderInput(value ?? ""));
|
|
1305
|
+
return;
|
|
1306
|
+
} catch {
|
|
1307
|
+
return "Headers must be comma-separated Key=Value entries.";
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
if (prompts.isCancel(headerInput)) return cancelPrompt();
|
|
1312
|
+
if (typeof headerInput !== "string") return cancelPrompt();
|
|
1313
|
+
if (isBackInput(headerInput)) {
|
|
1314
|
+
step = "providerId";
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
draft.headerInput = headerInput;
|
|
1318
|
+
draft.headers = parseHeaderList(splitHeaderInput(headerInput));
|
|
1319
|
+
draft.discoveredModels = draft.source === "manual" ? [] : await probeModelsWithSpinner(prompts, draft.endpoint ?? "", draft.headers);
|
|
1320
|
+
step = "models";
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
if (step === "models") {
|
|
1324
|
+
const selectedModels = await promptModels(prompts, draft.discoveredModels ?? []);
|
|
1325
|
+
if (prompts.isCancel(selectedModels)) return cancelPrompt();
|
|
1326
|
+
if (selectedModels === backValue) {
|
|
1327
|
+
step = "headers";
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
if (!Array.isArray(selectedModels)) return cancelPrompt();
|
|
1331
|
+
draft.selectedModels = selectedModels;
|
|
1332
|
+
step = "defaultAlias";
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (step === "defaultAlias") {
|
|
1336
|
+
const addDefaultAlias = await prompts.select({
|
|
1337
|
+
message: `Add alias "default" to ${draft.selectedModels?.[0]}?`,
|
|
1338
|
+
options: withBackOption([{
|
|
1339
|
+
label: "Yes",
|
|
1340
|
+
value: "yes"
|
|
1341
|
+
}, {
|
|
1342
|
+
label: "No",
|
|
1343
|
+
value: "no"
|
|
1344
|
+
}])
|
|
1345
|
+
});
|
|
1346
|
+
if (prompts.isCancel(addDefaultAlias)) return cancelPrompt();
|
|
1347
|
+
if (addDefaultAlias === backValue) {
|
|
1348
|
+
step = "models";
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
draft.addDefaultAlias = addDefaultAlias === "yes";
|
|
1352
|
+
step = "confirm";
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
const nextProvider = createProviderConfig((draft.providerId ?? "").trim(), (draft.endpoint ?? "").trim(), draft.headers, draft.selectedModels ?? [], draft.addDefaultAlias ?? true);
|
|
1356
|
+
const confirmed = await prompts.select({
|
|
1357
|
+
message: [
|
|
1358
|
+
`Write ${draft.scope} setup config?`,
|
|
1359
|
+
`Provider: ${nextProvider.id}`,
|
|
1360
|
+
`Endpoint: ${nextProvider.endpoint}`,
|
|
1361
|
+
`Models: ${(draft.selectedModels ?? []).join(", ")}`
|
|
1362
|
+
].join("\n"),
|
|
1363
|
+
options: withBackOption([{
|
|
1364
|
+
label: "Yes",
|
|
1365
|
+
value: "yes"
|
|
1366
|
+
}, {
|
|
1367
|
+
label: "No",
|
|
1368
|
+
value: "no"
|
|
1369
|
+
}])
|
|
1370
|
+
});
|
|
1371
|
+
if (prompts.isCancel(confirmed)) return cancelPrompt();
|
|
1372
|
+
if (confirmed === backValue) {
|
|
1373
|
+
step = "defaultAlias";
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
if (confirmed === "no") return cancelPrompt();
|
|
1377
|
+
const configPath = getConfigPath(io, draft.scope ?? "global");
|
|
1378
|
+
await writeSetupConfig(configPath, mergeSetupConfigs(await loadSetupConfig(configPath), {
|
|
1379
|
+
providers: [nextProvider],
|
|
1380
|
+
version: 1
|
|
1381
|
+
}));
|
|
1382
|
+
prompts.outro(`Wrote ${configPath}`);
|
|
1383
|
+
return 0;
|
|
1384
|
+
}
|
|
772
1385
|
}
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
-
|
|
1386
|
+
function withBackOption(options) {
|
|
1387
|
+
return [...options, {
|
|
1388
|
+
label: "Back",
|
|
1389
|
+
value: backValue
|
|
1390
|
+
}];
|
|
776
1391
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1392
|
+
function createProviderConfig(providerId, endpoint, headers, modelIds, addDefaultAlias) {
|
|
1393
|
+
return {
|
|
1394
|
+
endpoint,
|
|
1395
|
+
headers,
|
|
1396
|
+
id: providerId,
|
|
1397
|
+
models: modelIds.map((modelId, index) => ({
|
|
1398
|
+
aliases: index === 0 && addDefaultAlias ? ["default"] : void 0,
|
|
1399
|
+
id: modelId,
|
|
1400
|
+
name: modelId
|
|
1401
|
+
})),
|
|
1402
|
+
type: "openai-compatible"
|
|
784
1403
|
};
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1404
|
+
}
|
|
1405
|
+
function getConfigPath(io, scope) {
|
|
1406
|
+
return scope === "local" ? getProjectSetupConfigPath(io.cwd) : getGlobalSetupConfigPath(io.env ?? process.env);
|
|
1407
|
+
}
|
|
1408
|
+
async function probeModelsWithSpinner(prompts, endpoint, headers) {
|
|
1409
|
+
const spinner = prompts.spinner();
|
|
1410
|
+
spinner.start("Probing models");
|
|
1411
|
+
try {
|
|
1412
|
+
const models = await probeModels(endpoint, headers ?? {});
|
|
1413
|
+
spinner.stop(models.length > 0 ? `Found ${models.length} models` : "No models discovered");
|
|
1414
|
+
return models;
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
spinner.stop(formatProbeModelsFailure(endpoint, error));
|
|
1417
|
+
return [];
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
async function promptEndpoint(prompts, source) {
|
|
1421
|
+
return prompts.text({
|
|
1422
|
+
defaultValue: source === "ollama" ? "http://localhost:11434/v1" : void 0,
|
|
1423
|
+
message: "Provider endpoint",
|
|
1424
|
+
placeholder: source === "ollama" ? "http://localhost:11434/v1; type .. to go back" : "https://example.test/v1; type .. to go back",
|
|
1425
|
+
validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider endpoint is required."
|
|
800
1426
|
});
|
|
801
|
-
const reporter = createRenderingProgressReporter(summary, renderer);
|
|
802
|
-
return {
|
|
803
|
-
dispose: renderer.finish,
|
|
804
|
-
reporter,
|
|
805
|
-
write: renderer.write
|
|
806
|
-
};
|
|
807
1427
|
}
|
|
808
|
-
function
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1428
|
+
async function promptModels(prompts, discoveredModels) {
|
|
1429
|
+
if (discoveredModels.length > 0) {
|
|
1430
|
+
const selectedModels = await prompts.multiselect({
|
|
1431
|
+
message: "Select models",
|
|
1432
|
+
options: withBackOption(discoveredModels.map((model) => ({
|
|
1433
|
+
label: model,
|
|
1434
|
+
value: model
|
|
1435
|
+
}))),
|
|
1436
|
+
required: true
|
|
1437
|
+
});
|
|
1438
|
+
return Array.isArray(selectedModels) && selectedModels.includes(backValue) ? backValue : selectedModels;
|
|
1439
|
+
}
|
|
1440
|
+
const modelInput = await prompts.text({
|
|
1441
|
+
message: "Models",
|
|
1442
|
+
placeholder: "qwen:8b, qwen:32b; type .. to go back",
|
|
1443
|
+
validate: (value) => isBackInput(value ?? "") || splitModelInput(value ?? "").length > 0 ? void 0 : "At least one model is required."
|
|
1444
|
+
});
|
|
1445
|
+
if (prompts.isCancel(modelInput)) return modelInput;
|
|
1446
|
+
return isBackInput(modelInput) ? backValue : splitModelInput(modelInput);
|
|
1447
|
+
}
|
|
1448
|
+
function splitHeaderInput(value) {
|
|
1449
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
1450
|
+
}
|
|
1451
|
+
function splitModelInput(value) {
|
|
1452
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
1453
|
+
}
|
|
1454
|
+
//#endregion
|
|
1455
|
+
//#region src/cli/commands/setup/index.ts
|
|
1456
|
+
const setup = defineCommand({
|
|
1457
|
+
action: (context, options) => runSetupCommand({
|
|
1458
|
+
...options,
|
|
1459
|
+
noInteractive: context.setupNoInteractive
|
|
1460
|
+
}, context.io),
|
|
1461
|
+
description: "Write alint provider configuration",
|
|
1462
|
+
examples: [
|
|
1463
|
+
["# Configure a provider interactively", "alint setup"].join("\n"),
|
|
1464
|
+
["# Write a project-local provider config without prompts", "alint setup --local -N --provider-id openrouter --provider-endpoint https://openrouter.ai/api/v1 --provider-model z-ai/glm-5.2"].join("\n"),
|
|
1465
|
+
["# Add an authorization header for an OpenAI-compatible provider", "alint setup --local -N --provider-id openrouter --provider-endpoint https://openrouter.ai/api/v1 --provider-model z-ai/glm-5.2 --provider-header \"Authorization=Bearer $OPENROUTER_API_KEY\""].join("\n")
|
|
1466
|
+
],
|
|
1467
|
+
help: ["Write alint provider and model configuration.", "Run without flags for interactive setup, or use `-N` with provider flags in scripts and CI. Use `--local` when the provider config should live with the current project instead of the user-level setup file."].join("\n\n"),
|
|
1468
|
+
name: "setup",
|
|
1469
|
+
options: [
|
|
1470
|
+
{
|
|
1471
|
+
description: "Write project-local config",
|
|
1472
|
+
flags: "--local"
|
|
829
1473
|
},
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1474
|
+
{
|
|
1475
|
+
description: "Disable interactive setup",
|
|
1476
|
+
flags: "-N, --no-interactive"
|
|
833
1477
|
},
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1478
|
+
{
|
|
1479
|
+
description: "Provider endpoint",
|
|
1480
|
+
flags: "--provider-endpoint <endpoint>"
|
|
837
1481
|
},
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1482
|
+
{
|
|
1483
|
+
description: "Provider id",
|
|
1484
|
+
flags: "--provider-id <id>"
|
|
841
1485
|
},
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1486
|
+
{
|
|
1487
|
+
description: "Provider model",
|
|
1488
|
+
flags: "--provider-model <model>"
|
|
845
1489
|
},
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1490
|
+
{
|
|
1491
|
+
description: "Provider header",
|
|
1492
|
+
flags: "--provider-header <Key=Value>"
|
|
849
1493
|
}
|
|
1494
|
+
]
|
|
1495
|
+
});
|
|
1496
|
+
function createSetupConfig(providerId, providerEndpoint, options) {
|
|
1497
|
+
const models = toArray(options.providerModel).map((model) => ({
|
|
1498
|
+
id: model,
|
|
1499
|
+
name: model
|
|
1500
|
+
}));
|
|
1501
|
+
return {
|
|
1502
|
+
providers: [{
|
|
1503
|
+
endpoint: providerEndpoint,
|
|
1504
|
+
headers: parseHeaderList(toArray(options.providerHeader)),
|
|
1505
|
+
id: providerId,
|
|
1506
|
+
models,
|
|
1507
|
+
type: "openai-compatible"
|
|
1508
|
+
}],
|
|
1509
|
+
version: 1
|
|
850
1510
|
};
|
|
851
1511
|
}
|
|
1512
|
+
async function runSetupCommand(options, io) {
|
|
1513
|
+
if (!options.providerEndpoint) {
|
|
1514
|
+
if (options.noInteractive !== true) return runInteractiveSetup({
|
|
1515
|
+
...io,
|
|
1516
|
+
stdin: io.stdin ?? process.stdin
|
|
1517
|
+
});
|
|
1518
|
+
io.stderr.write("setup requires --provider-endpoint in --no-interactive mode.\n");
|
|
1519
|
+
return 2;
|
|
1520
|
+
}
|
|
1521
|
+
if (!options.providerId) {
|
|
1522
|
+
io.stderr.write("setup requires --provider-id in --no-interactive mode.\n");
|
|
1523
|
+
return 2;
|
|
1524
|
+
}
|
|
1525
|
+
const setupConfigPath = options.local ? getProjectSetupConfigPath(io.cwd) : getGlobalSetupConfigPath(io.env ?? process.env);
|
|
1526
|
+
await writeSetupConfig(setupConfigPath, mergeSetupConfigs(await loadSetupConfig(setupConfigPath), createSetupConfig(options.providerId, options.providerEndpoint, options)));
|
|
1527
|
+
return 0;
|
|
1528
|
+
}
|
|
1529
|
+
function toArray(value) {
|
|
1530
|
+
if (value === void 0) return [];
|
|
1531
|
+
return (Array.isArray(value) ? value : [value]).filter((item) => typeof item === "string");
|
|
1532
|
+
}
|
|
1533
|
+
//#endregion
|
|
1534
|
+
//#region src/cli/commands/index.ts
|
|
1535
|
+
const commandTree = [
|
|
1536
|
+
setup,
|
|
1537
|
+
config,
|
|
1538
|
+
output,
|
|
1539
|
+
lint
|
|
1540
|
+
];
|
|
852
1541
|
//#endregion
|
|
853
1542
|
//#region src/cli/cli.ts
|
|
854
1543
|
async function executeCli(argv, io) {
|
|
855
1544
|
const cli = cac("alint");
|
|
856
1545
|
const setupNoInteractive = argv.includes("-N") || argv.includes("--no-interactive");
|
|
1546
|
+
const globalOptions = { outputLanguage: parseStringOption(argv, ["--lang", "-l"]) };
|
|
857
1547
|
let pendingResult;
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1548
|
+
const setPendingResult = (result) => {
|
|
1549
|
+
pendingResult = result;
|
|
1550
|
+
return result;
|
|
1551
|
+
};
|
|
1552
|
+
cli.option("--no-cache", "Disable cache for this run").option("--cache-location <path>", "Path to the alint cache file or directory").option("--config <path>", "Path to alint config file").option("--file-concurrency <count>", "Number of files to lint concurrently").option("--format <format>", "Reporter format", { default: "stylish" }).option("--model <model>", "Force a model override").option("-l, --lang <language>", "Ask model-backed rules to write diagnostics in this language").option("--progress", "Show run progress").option("--rule-concurrency <count>", "Number of rules to run concurrently within a file").option("--timeout-ms <ms>", "Rule execution timeout in milliseconds").help();
|
|
1553
|
+
registerCommandTree(cli, commandTree, {
|
|
1554
|
+
globalOptions,
|
|
1555
|
+
interceptConsoleOutput,
|
|
1556
|
+
io,
|
|
1557
|
+
setupNoInteractive
|
|
1558
|
+
}, setPendingResult, {
|
|
1559
|
+
examples: [
|
|
1560
|
+
["# Configure a provider interactively", "alint setup"].join("\n"),
|
|
1561
|
+
["# Run alint on source files with the default stylish reporter", "alint src"].join("\n"),
|
|
1562
|
+
[
|
|
1563
|
+
"# Run alint and save machine-readable JSON for later inspection",
|
|
1564
|
+
"alint --format json src > alint-output.json",
|
|
1565
|
+
"alint output inspect alint-output.json"
|
|
1566
|
+
].join("\n"),
|
|
1567
|
+
["# Inspect the effective config that applies to a file", "alint config inspect src/index.ts"].join("\n"),
|
|
1568
|
+
[
|
|
1569
|
+
"# List configured providers and models",
|
|
1570
|
+
"alint config providers list",
|
|
1571
|
+
"alint config models list"
|
|
1572
|
+
].join("\n")
|
|
1573
|
+
],
|
|
1574
|
+
help: ["AI-assisted linting for source files, saved run outputs, and provider/model setup.", "Start with `alint setup` to configure a model provider, run `alint <files>` to analyze files, and use `alint output inspect` to read saved JSON output without rerunning rules."].join("\n\n")
|
|
873
1575
|
});
|
|
874
1576
|
const restoreConsole = interceptConsoleOutput(shouldCaptureHelp(argv) ? io.stdout : io.stderr);
|
|
875
1577
|
try {
|
|
@@ -879,28 +1581,6 @@ async function executeCli(argv, io) {
|
|
|
879
1581
|
restoreConsole();
|
|
880
1582
|
}
|
|
881
1583
|
}
|
|
882
|
-
async function assertConfigExists(cwd, configPath) {
|
|
883
|
-
const resolvedConfigPath = resolve(cwd, configPath);
|
|
884
|
-
try {
|
|
885
|
-
if (!(await stat(resolvedConfigPath)).isFile()) throw new Error(`Config file "${configPath}" is not a file.`);
|
|
886
|
-
} catch (error) {
|
|
887
|
-
if (isNodeError(error) && error.code === "ENOENT") throw new Error(`Config file "${configPath}" does not exist.`);
|
|
888
|
-
throw error;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
function formatRunError(error, color) {
|
|
892
|
-
return `${color ? c.red("error") : "error"} ${formatRunErrorContext(error)}\n Rule running failed due to ${error.failure?.message ?? error.message}\n`;
|
|
893
|
-
}
|
|
894
|
-
function formatRunErrorContext(error) {
|
|
895
|
-
const failure = error.failure;
|
|
896
|
-
if (!failure) return "alint run failed";
|
|
897
|
-
const target = failure.target ? failure.target.name ? `${failure.target.kind} ${failure.target.name}` : failure.target.kind : void 0;
|
|
898
|
-
return [
|
|
899
|
-
failure.filePath,
|
|
900
|
-
target,
|
|
901
|
-
failure.ruleId
|
|
902
|
-
].filter(Boolean).join(" > ");
|
|
903
|
-
}
|
|
904
1584
|
function interceptConsoleOutput(stdout) {
|
|
905
1585
|
const cliConsole = globalThis.console;
|
|
906
1586
|
const originalConsoleDebug = cliConsole.debug;
|
|
@@ -924,176 +1604,18 @@ function interceptConsoleOutput(stdout) {
|
|
|
924
1604
|
cliConsole.log = originalConsoleLog;
|
|
925
1605
|
};
|
|
926
1606
|
}
|
|
927
|
-
function
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
return mergeSetupConfigs(globalSetupConfig, projectSetupConfig);
|
|
935
|
-
}
|
|
936
|
-
function mergeRunnerCacheConfig(setupCache, configCache) {
|
|
937
|
-
if (configCache === void 0) return setupCache;
|
|
938
|
-
if (typeof configCache === "boolean") return configCache;
|
|
939
|
-
if (typeof setupCache === "object") return {
|
|
940
|
-
...setupCache,
|
|
941
|
-
...configCache
|
|
942
|
-
};
|
|
943
|
-
return configCache;
|
|
944
|
-
}
|
|
945
|
-
function parsePositiveIntegerOption(value, label) {
|
|
946
|
-
if (value === void 0) return;
|
|
947
|
-
const parsed = Number(value);
|
|
948
|
-
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${label} must be a positive integer.`);
|
|
949
|
-
return parsed;
|
|
950
|
-
}
|
|
951
|
-
async function resolveLintFiles(files, config, cwd) {
|
|
952
|
-
if (config.ignore?.gitignore !== true || files.length === 0) return files;
|
|
953
|
-
const gitignore = new Gitignore();
|
|
954
|
-
const lintFiles = [];
|
|
955
|
-
for (const file of files) {
|
|
956
|
-
if (await gitignore.ignores(resolve(cwd, file))) continue;
|
|
957
|
-
lintFiles.push(file);
|
|
958
|
-
}
|
|
959
|
-
return lintFiles;
|
|
960
|
-
}
|
|
961
|
-
function resolveRunnerCacheConfig(setupCache, configCache, options) {
|
|
962
|
-
if (options.cache === false) return false;
|
|
963
|
-
const configuredCache = mergeRunnerCacheConfig(setupCache, configCache);
|
|
964
|
-
if (options.cacheLocation !== void 0) return typeof configuredCache === "object" ? {
|
|
965
|
-
...configuredCache,
|
|
966
|
-
location: options.cacheLocation
|
|
967
|
-
} : { location: options.cacheLocation };
|
|
968
|
-
return configuredCache;
|
|
969
|
-
}
|
|
970
|
-
function resolveRunnerConfig(setupConfig, config, options) {
|
|
971
|
-
const cache = resolveRunnerCacheConfig(setupConfig.runner?.cache, config.runner?.cache, options);
|
|
972
|
-
const fileConcurrency = parsePositiveIntegerOption(options.fileConcurrency, "--file-concurrency");
|
|
973
|
-
const ruleConcurrency = parsePositiveIntegerOption(options.ruleConcurrency, "--rule-concurrency");
|
|
974
|
-
const timeoutMs = parsePositiveIntegerOption(options.timeoutMs, "--timeout-ms");
|
|
975
|
-
const runner = {
|
|
976
|
-
...setupConfig.runner ?? {},
|
|
977
|
-
...config.runner ?? {},
|
|
978
|
-
cache,
|
|
979
|
-
fileConcurrency: fileConcurrency ?? config.runner?.fileConcurrency ?? setupConfig.runner?.fileConcurrency,
|
|
980
|
-
ruleConcurrency: ruleConcurrency ?? config.runner?.ruleConcurrency ?? setupConfig.runner?.ruleConcurrency,
|
|
981
|
-
timeoutMs: timeoutMs ?? config.runner?.timeoutMs ?? setupConfig.runner?.timeoutMs
|
|
982
|
-
};
|
|
983
|
-
return Object.values(runner).some((value) => value !== void 0) ? runner : void 0;
|
|
984
|
-
}
|
|
985
|
-
async function runConfigCommand(args, options, io) {
|
|
986
|
-
if (args[0] === "models" && args[1] === "probe" && args.length === 2) return runModelsProbeCommand(options, io);
|
|
987
|
-
if (args[0] === "models" && args[1] === "ls" && args.length === 2) return runModelsListCommand(io);
|
|
988
|
-
if (args[0] === "models" && args[1] === "show" && args.length === 3) return runModelsShowCommand(args[2], io);
|
|
989
|
-
if (args[0] === "providers" && args[1] === "ls" && args.length === 2) return runProvidersListCommand(io);
|
|
990
|
-
if (args[0] === "providers" && args[1] === "show" && args.length === 3) return runProvidersShowCommand(args[2], io);
|
|
991
|
-
if (args[0] === "providers" && args[1] === "probe" && args.length === 2) return runProvidersProbeCommand(options, io);
|
|
992
|
-
io.stderr.write(`unknown config command: ${args.join(" ")}\n`);
|
|
993
|
-
return 2;
|
|
994
|
-
}
|
|
995
|
-
async function runDefaultCommand(files, options, io) {
|
|
996
|
-
if (options.config) await assertConfigExists(io.cwd, options.config);
|
|
997
|
-
const [setupConfig, config] = await Promise.all([loadMergedSetupConfig(io), loadAlintConfig(io.cwd, options.config)]);
|
|
998
|
-
const lintFiles = await resolveLintFiles(files, config, io.cwd);
|
|
999
|
-
const runner = resolveRunnerConfig(setupConfig, config, options);
|
|
1000
|
-
const progress = shouldEnableProgress(options, io) ? createCliProgressReporter({
|
|
1001
|
-
color: io.stderr.isTTY === true,
|
|
1002
|
-
columns: io.stderr.columns ?? 80,
|
|
1003
|
-
cwd: io.cwd,
|
|
1004
|
-
isTty: io.stderr.isTTY === true,
|
|
1005
|
-
write: (chunk) => io.stderr.write(chunk)
|
|
1006
|
-
}) : void 0;
|
|
1007
|
-
const restoreProgressConsole = progress ? interceptConsoleOutput({ write: progress.write }) : void 0;
|
|
1008
|
-
let result;
|
|
1009
|
-
try {
|
|
1010
|
-
result = await runAlint({
|
|
1011
|
-
config,
|
|
1012
|
-
cwd: io.cwd,
|
|
1013
|
-
files: lintFiles,
|
|
1014
|
-
modelOverride: options.model,
|
|
1015
|
-
progress: progress?.reporter,
|
|
1016
|
-
runner,
|
|
1017
|
-
setupConfig
|
|
1018
|
-
});
|
|
1019
|
-
} catch (error) {
|
|
1020
|
-
restoreProgressConsole?.();
|
|
1021
|
-
progress?.dispose();
|
|
1022
|
-
if (error instanceof AlintRunError) {
|
|
1023
|
-
io.stderr.write(formatRunError(error, io.stderr.isTTY === true));
|
|
1024
|
-
return 2;
|
|
1607
|
+
function parseStringOption(argv, flags) {
|
|
1608
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1609
|
+
const value = argv[index];
|
|
1610
|
+
for (const flag of flags) {
|
|
1611
|
+
const equalsPrefix = `${flag}=`;
|
|
1612
|
+
if (value?.startsWith(equalsPrefix)) return value.slice(equalsPrefix.length);
|
|
1613
|
+
if (value === flag) return argv[index + 1];
|
|
1025
1614
|
}
|
|
1026
|
-
throw error;
|
|
1027
|
-
}
|
|
1028
|
-
restoreProgressConsole?.();
|
|
1029
|
-
progress?.dispose();
|
|
1030
|
-
io.stdout.write(formatDiagnostics(options.format, result, { color: io.stdout.isTTY === true }));
|
|
1031
|
-
return result.diagnostics.length > 0 ? 1 : 0;
|
|
1032
|
-
}
|
|
1033
|
-
async function runModelsListCommand(io) {
|
|
1034
|
-
io.stdout.write(formatModelList(await loadMergedSetupConfig(io)));
|
|
1035
|
-
return 0;
|
|
1036
|
-
}
|
|
1037
|
-
async function runModelsProbeCommand(options, io) {
|
|
1038
|
-
if (!options.endpoint) {
|
|
1039
|
-
io.stderr.write("config models probe requires --endpoint.\n");
|
|
1040
|
-
return 2;
|
|
1041
|
-
}
|
|
1042
|
-
try {
|
|
1043
|
-
const models = await probeModels(options.endpoint, parseHeaderList(toArray(options.providerHeader)) ?? {});
|
|
1044
|
-
io.stdout.write(`${models.join("\n")}${models.length > 0 ? "\n" : ""}`);
|
|
1045
|
-
return 0;
|
|
1046
|
-
} catch (error) {
|
|
1047
|
-
io.stderr.write(`failed to probe models: ${errorMessageFrom(error) ?? String(error)}\n`);
|
|
1048
|
-
return 2;
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
async function runModelsShowCommand(model, io) {
|
|
1052
|
-
const candidate = findModel(await loadMergedSetupConfig(io), model);
|
|
1053
|
-
if (candidate === void 0) {
|
|
1054
|
-
io.stderr.write(`unknown model "${model}".\n`);
|
|
1055
|
-
return 2;
|
|
1056
|
-
}
|
|
1057
|
-
io.stdout.write(formatModelShow(candidate));
|
|
1058
|
-
return 0;
|
|
1059
|
-
}
|
|
1060
|
-
async function runProvidersListCommand(io) {
|
|
1061
|
-
io.stdout.write(formatProviderList(await loadMergedSetupConfig(io)));
|
|
1062
|
-
return 0;
|
|
1063
|
-
}
|
|
1064
|
-
async function runProvidersProbeCommand(options, io) {
|
|
1065
|
-
if (!options.endpoint) {
|
|
1066
|
-
io.stderr.write("config providers probe requires --endpoint.\n");
|
|
1067
|
-
return 2;
|
|
1068
|
-
}
|
|
1069
|
-
try {
|
|
1070
|
-
const models = await probeModels(options.endpoint, parseHeaderList(toArray(options.providerHeader)) ?? {});
|
|
1071
|
-
io.stdout.write(`endpoint: ${options.endpoint}\nmodels: ${models.length}\n`);
|
|
1072
|
-
return 0;
|
|
1073
|
-
} catch (error) {
|
|
1074
|
-
io.stderr.write(`failed to probe provider: ${errorMessageFrom(error) ?? String(error)}\n`);
|
|
1075
|
-
return 2;
|
|
1076
1615
|
}
|
|
1077
1616
|
}
|
|
1078
|
-
async function runProvidersShowCommand(providerId, io) {
|
|
1079
|
-
const provider = (await loadMergedSetupConfig(io)).providers.find((item) => item.id === providerId);
|
|
1080
|
-
if (provider === void 0) {
|
|
1081
|
-
io.stderr.write(`unknown provider "${providerId}".\n`);
|
|
1082
|
-
return 2;
|
|
1083
|
-
}
|
|
1084
|
-
io.stdout.write(formatProviderShow(provider));
|
|
1085
|
-
return 0;
|
|
1086
|
-
}
|
|
1087
1617
|
function shouldCaptureHelp(argv) {
|
|
1088
1618
|
return argv.includes("--help") || argv.includes("-h");
|
|
1089
1619
|
}
|
|
1090
|
-
function shouldEnableProgress(options, io) {
|
|
1091
|
-
if (options.progress !== void 0) return options.progress;
|
|
1092
|
-
return options.format === "stylish" && io.stderr.isTTY === true;
|
|
1093
|
-
}
|
|
1094
|
-
function toArray(value) {
|
|
1095
|
-
if (value === void 0) return [];
|
|
1096
|
-
return (Array.isArray(value) ? value : [value]).filter((item) => typeof item === "string");
|
|
1097
|
-
}
|
|
1098
1620
|
//#endregion
|
|
1099
1621
|
export { formatJson as i, formatDiagnostics as n, formatStylish as r, executeCli as t };
|