@alint-js/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1755 @@
1
+ import process from "node:process";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "pathe";
4
+ import { isMacOS } from "std-env";
5
+ import { xdgConfig } from "xdg-basedir";
6
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
7
+ import { isPlainObject } from "es-toolkit/compat";
8
+ import { parse, stringify } from "smol-toml";
9
+ import { inspect } from "node:util";
10
+ import c, { createColors } from "tinyrainbow";
11
+ import { cac } from "cac";
12
+ import { loadConfig } from "c12";
13
+ import { AsyncLocalStorage } from "node:async_hooks";
14
+ import { parseSync } from "oxc-parser";
15
+ import { extname, relative } from "node:path";
16
+ import cliSpinners from "cli-spinners";
17
+ //#region src/config/paths.ts
18
+ function getGlobalSetupConfigPath(env = process.env, options = {}) {
19
+ return join(env.XDG_CONFIG_HOME ?? resolveDefaultConfigHome(options), "alint", "config.toml");
20
+ }
21
+ function getProjectSetupConfigPath(cwd) {
22
+ return join(cwd, ".alint", "config.toml");
23
+ }
24
+ function resolveDefaultConfigHome(options) {
25
+ if (options.isMacOS ?? isMacOS) return join(homedir(), ".config");
26
+ return options.xdgConfig ?? xdgConfig ?? join(homedir(), ".config");
27
+ }
28
+ //#endregion
29
+ //#region src/config/setup-toml.ts
30
+ const modelSizes = /* @__PURE__ */ new Set([
31
+ "large",
32
+ "medium",
33
+ "small"
34
+ ]);
35
+ function parseSetupConfigToml(toml) {
36
+ const rawConfig = parse(toml);
37
+ if (rawConfig.version !== 1) throw new Error("Invalid setup config: version must be 1.");
38
+ if (!Array.isArray(rawConfig.providers)) throw new TypeError("Invalid setup config: providers must be an array.");
39
+ const parsedConfig = {
40
+ providers: rawConfig.providers.map((provider) => parseProvider(provider)),
41
+ version: 1
42
+ };
43
+ if (rawConfig.runner !== void 0) parsedConfig.runner = parseRunner(rawConfig.runner);
44
+ return parsedConfig;
45
+ }
46
+ function stringifySetupConfigToml(config) {
47
+ const stringifiableConfig = {
48
+ providers: config.providers.map(toTomlProvider),
49
+ version: config.version
50
+ };
51
+ if (config.runner !== void 0) stringifiableConfig.runner = toTomlRunner(config.runner);
52
+ return stringify(stringifiableConfig);
53
+ }
54
+ function isModelSize(value) {
55
+ return typeof value === "string" && modelSizes.has(value);
56
+ }
57
+ function parseModel(providerId, model) {
58
+ const id = readNonEmptyString(model.id, `provider "${providerId}" model id`);
59
+ const parsedModel = { id };
60
+ if (model.name !== void 0) parsedModel.name = readString(model.name, `model "${id}" name`);
61
+ if (model.aliases !== void 0) parsedModel.aliases = readStringArray(model.aliases, `model "${id}" aliases`);
62
+ if (model.capabilities !== void 0) parsedModel.capabilities = readStringArray(model.capabilities, `model "${id}" capabilities`);
63
+ if (model.size !== void 0) {
64
+ if (!isModelSize(model.size)) throw new Error(`Invalid model "${id}": size must be "small", "medium", or "large".`);
65
+ parsedModel.size = model.size;
66
+ }
67
+ if (model.context_window !== void 0) parsedModel.contextWindow = readFiniteNumber(model.context_window, `model "${id}" context_window`);
68
+ if (model.default_params !== void 0) parsedModel.defaultParams = readRecord(model.default_params, `model "${id}" default_params`);
69
+ return parsedModel;
70
+ }
71
+ function parsePositiveInteger(value, label) {
72
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new TypeError(`Invalid ${label}: must be a positive integer.`);
73
+ return value;
74
+ }
75
+ function parseProvider(provider) {
76
+ const id = readNonEmptyString(provider.id, "provider id");
77
+ if (provider.type !== "openai-compatible") throw new Error(`Invalid provider "${id}": type must be "openai-compatible".`);
78
+ const endpoint = readNonEmptyString(provider.endpoint, `provider "${id}" endpoint`);
79
+ if (!Array.isArray(provider.models)) throw new TypeError(`Invalid provider "${id}": models must be an array.`);
80
+ const parsedProvider = {
81
+ endpoint,
82
+ id,
83
+ models: provider.models.map((model) => parseModel(id, model)),
84
+ type: provider.type
85
+ };
86
+ if (provider.headers !== void 0) parsedProvider.headers = readStringMap(provider.headers, `provider "${id}" headers`);
87
+ return parsedProvider;
88
+ }
89
+ function parseRunner(runner) {
90
+ const parsedRunner = {};
91
+ if (runner.file_concurrency !== void 0) parsedRunner.fileConcurrency = parsePositiveInteger(runner.file_concurrency, "runner file_concurrency");
92
+ if (runner.rule_concurrency !== void 0) parsedRunner.ruleConcurrency = parsePositiveInteger(runner.rule_concurrency, "runner rule_concurrency");
93
+ if (runner.timeout_ms !== void 0) parsedRunner.timeoutMs = parsePositiveInteger(runner.timeout_ms, "runner timeout_ms");
94
+ return parsedRunner;
95
+ }
96
+ function readFiniteNumber(value, label) {
97
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`Invalid ${label}: must be a finite number.`);
98
+ return value;
99
+ }
100
+ function readNonEmptyString(value, label) {
101
+ const stringValue = readString(value, label);
102
+ if (stringValue.length === 0) throw new Error(`Invalid ${label}: must be a non-empty string.`);
103
+ return stringValue;
104
+ }
105
+ function readRecord(value, label) {
106
+ if (!isPlainObject(value)) throw new Error(`Invalid ${label}: must be a table.`);
107
+ return value;
108
+ }
109
+ function readString(value, label) {
110
+ if (typeof value !== "string") throw new TypeError(`Invalid ${label}: must be a string.`);
111
+ return value;
112
+ }
113
+ function readStringArray(value, label) {
114
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new Error(`Invalid ${label}: must be an array of strings.`);
115
+ return value;
116
+ }
117
+ function readStringMap(value, label) {
118
+ const record = readRecord(value, label);
119
+ if (!Object.values(record).every((item) => typeof item === "string")) throw new Error(`Invalid ${label}: must be a string map.`);
120
+ return record;
121
+ }
122
+ function toTomlModel(model) {
123
+ const tomlModel = { id: model.id };
124
+ if (model.name !== void 0) tomlModel.name = model.name;
125
+ if (model.aliases !== void 0) tomlModel.aliases = model.aliases;
126
+ if (model.capabilities !== void 0) tomlModel.capabilities = model.capabilities;
127
+ if (model.size !== void 0) tomlModel.size = model.size;
128
+ if (model.contextWindow !== void 0) tomlModel.context_window = model.contextWindow;
129
+ if (model.defaultParams !== void 0) tomlModel.default_params = model.defaultParams;
130
+ return tomlModel;
131
+ }
132
+ function toTomlProvider(provider) {
133
+ const tomlProvider = {
134
+ endpoint: provider.endpoint,
135
+ id: provider.id,
136
+ models: provider.models.map(toTomlModel),
137
+ type: provider.type
138
+ };
139
+ if (provider.headers !== void 0) tomlProvider.headers = provider.headers;
140
+ return tomlProvider;
141
+ }
142
+ function toTomlRunner(runner) {
143
+ return {
144
+ file_concurrency: runner.fileConcurrency,
145
+ rule_concurrency: runner.ruleConcurrency,
146
+ timeout_ms: runner.timeoutMs
147
+ };
148
+ }
149
+ //#endregion
150
+ //#region src/config/setup-load.ts
151
+ const emptySetupConfig = {
152
+ providers: [],
153
+ version: 1
154
+ };
155
+ async function loadSetupConfig(filePath) {
156
+ try {
157
+ return parseSetupConfigToml(await readFile(filePath, "utf8"));
158
+ } catch (error) {
159
+ if (isNodeError$1(error) && error.code === "ENOENT") return createEmptySetupConfig();
160
+ throw error;
161
+ }
162
+ }
163
+ function mergeSetupConfigs(...configs) {
164
+ let providers = [];
165
+ let runner;
166
+ const providersById = /* @__PURE__ */ new Map();
167
+ for (const config of configs) {
168
+ const configProviders = [];
169
+ if (config.runner !== void 0) runner = {
170
+ ...runner ?? {},
171
+ ...config.runner
172
+ };
173
+ for (const provider of config.providers) {
174
+ const existingProvider = providersById.get(provider.id);
175
+ if (existingProvider === void 0) {
176
+ const mergedProvider = cloneProvider(provider);
177
+ providers.push(mergedProvider);
178
+ providersById.set(provider.id, mergedProvider);
179
+ configProviders.push(mergedProvider);
180
+ continue;
181
+ }
182
+ existingProvider.endpoint = provider.endpoint;
183
+ existingProvider.type = provider.type;
184
+ configProviders.push(existingProvider);
185
+ if (provider.headers !== void 0) existingProvider.headers = {
186
+ ...existingProvider.headers,
187
+ ...provider.headers
188
+ };
189
+ const nextModels = [];
190
+ for (const incomingModel of provider.models) {
191
+ const existingModelIndex = existingProvider.models.findIndex((model) => model.id === incomingModel.id);
192
+ if (existingModelIndex === -1) {
193
+ nextModels.push(cloneModel(incomingModel));
194
+ continue;
195
+ }
196
+ const existingModel = existingProvider.models[existingModelIndex];
197
+ existingProvider.models.splice(existingModelIndex, 1);
198
+ nextModels.push(mergeModel(existingModel, incomingModel));
199
+ }
200
+ existingProvider.models = [...nextModels, ...existingProvider.models];
201
+ }
202
+ providers = prioritizeProviders(providers, configProviders);
203
+ }
204
+ const mergedConfig = {
205
+ providers,
206
+ version: 1
207
+ };
208
+ if (runner !== void 0) mergedConfig.runner = runner;
209
+ return mergedConfig;
210
+ }
211
+ function cloneModel(model) {
212
+ const clonedModel = { ...model };
213
+ if (model.aliases !== void 0) clonedModel.aliases = [...model.aliases];
214
+ if (model.capabilities !== void 0) clonedModel.capabilities = [...model.capabilities];
215
+ if (model.defaultParams !== void 0) clonedModel.defaultParams = { ...model.defaultParams };
216
+ return clonedModel;
217
+ }
218
+ function cloneProvider(provider) {
219
+ const clonedProvider = {
220
+ ...provider,
221
+ models: provider.models.map(cloneModel)
222
+ };
223
+ if (provider.headers !== void 0) clonedProvider.headers = { ...provider.headers };
224
+ return clonedProvider;
225
+ }
226
+ function createEmptySetupConfig() {
227
+ return {
228
+ providers: [],
229
+ version: 1
230
+ };
231
+ }
232
+ function isNodeError$1(error) {
233
+ return error instanceof Error && "code" in error;
234
+ }
235
+ function mergeModel(existingModel, incomingModel) {
236
+ const existing = cloneModel(existingModel);
237
+ const incoming = cloneModel(incomingModel);
238
+ return {
239
+ ...existing,
240
+ ...incoming,
241
+ aliases: incoming.aliases ?? existing.aliases,
242
+ capabilities: incoming.capabilities ?? existing.capabilities,
243
+ defaultParams: incoming.defaultParams ?? existing.defaultParams
244
+ };
245
+ }
246
+ function prioritizeProviders(providers, prioritizedProviders) {
247
+ const prioritizedProviderIds = new Set(prioritizedProviders.map((provider) => provider.id));
248
+ return [...prioritizedProviders, ...providers.filter((provider) => !prioritizedProviderIds.has(provider.id))];
249
+ }
250
+ //#endregion
251
+ //#region src/config/setup-write.ts
252
+ async function writeSetupConfig(filePath, config) {
253
+ await mkdir(dirname(filePath), { recursive: true });
254
+ await writeFile(filePath, stringifySetupConfigToml(config), "utf8");
255
+ }
256
+ //#endregion
257
+ //#region src/config/load-config.ts
258
+ async function loadAlintConfig(cwd, configFile) {
259
+ return (await loadConfig({
260
+ configFile,
261
+ cwd,
262
+ defaults: {
263
+ plugins: [],
264
+ rules: {}
265
+ },
266
+ dotenv: true,
267
+ name: "alint"
268
+ })).config;
269
+ }
270
+ //#endregion
271
+ //#region src/dsl/registry.ts
272
+ function buildRuleRegistry(config) {
273
+ const rules = /* @__PURE__ */ new Map();
274
+ const enabledRules = [];
275
+ for (const plugin of config.plugins ?? []) for (const [localId, rule] of Object.entries(plugin.rules)) {
276
+ const id = `${plugin.scope}/${localId}`;
277
+ if (rules.has(id)) throw new Error(`Duplicate rule id "${id}".`);
278
+ rules.set(id, rule);
279
+ const severity = normalizeSeverity(config.rules?.[id]);
280
+ if (severity !== "off") enabledRules.push({
281
+ id,
282
+ localId,
283
+ rule,
284
+ scope: plugin.scope,
285
+ severity
286
+ });
287
+ }
288
+ for (const id of Object.keys(config.rules ?? {})) if (!rules.has(id)) throw new Error(`Unknown rule "${id}".`);
289
+ return {
290
+ enabledRules,
291
+ rules
292
+ };
293
+ }
294
+ function normalizeSeverity(entry) {
295
+ if (Array.isArray(entry)) return entry[0] ?? "warn";
296
+ return entry ?? "warn";
297
+ }
298
+ //#endregion
299
+ //#region src/models/resolve.ts
300
+ function resolveModel(registry, options = {}) {
301
+ const candidates = flattenModels(registry);
302
+ const ruleId = options.ruleId ?? "<unknown>";
303
+ if (options.request !== void 0) {
304
+ const request = options.request;
305
+ const candidate = candidates.find(({ model }) => matchesRequest(model, request));
306
+ if (candidate === void 0) throw new Error(`Unknown model "${request}".`);
307
+ if (!satisfiesHardRequirements(candidate.model, options.requirement)) throw new Error(`Model "${request}" does not satisfy requirement for rule "${ruleId}".`);
308
+ return toResolvedModel(candidate, options.requirement);
309
+ }
310
+ const candidate = preferSize(candidates.filter(({ model }) => satisfiesHardRequirements(model, options.requirement)), options.requirement);
311
+ if (candidate === void 0) throw new Error(`No model satisfies requirement for rule "${ruleId}".`);
312
+ return toResolvedModel(candidate, options.requirement);
313
+ }
314
+ function flattenModels(registry) {
315
+ return registry.providers.flatMap((provider) => provider.models.map((model) => ({
316
+ model,
317
+ provider
318
+ })));
319
+ }
320
+ function matchesRequest(model, request) {
321
+ return model.id === request || model.name === request || (model.aliases ?? []).includes(request);
322
+ }
323
+ function preferSize(candidates, requirement) {
324
+ if (requirement?.size === void 0) return candidates[0];
325
+ return candidates.find(({ model }) => model.size === requirement.size) ?? candidates[0];
326
+ }
327
+ function satisfiesHardRequirements(model, requirement) {
328
+ if (requirement === void 0) return true;
329
+ if (requirement.capabilities !== void 0 && !requirement.capabilities.every((capability) => (model.capabilities ?? []).includes(capability))) return false;
330
+ if (requirement.minContextWindow !== void 0 && (model.contextWindow === void 0 || model.contextWindow < requirement.minContextWindow)) return false;
331
+ return true;
332
+ }
333
+ function toResolvedModel(candidate, requirement) {
334
+ const { model, provider } = candidate;
335
+ return {
336
+ aliases: [...model.aliases ?? []],
337
+ capabilities: [...model.capabilities ?? []],
338
+ contextWindow: model.contextWindow,
339
+ id: model.id,
340
+ name: model.name ?? model.id,
341
+ params: {
342
+ ...model.defaultParams ?? {},
343
+ ...requirement?.params ?? {}
344
+ },
345
+ provider: {
346
+ endpoint: provider.endpoint,
347
+ headers: { ...provider.headers ?? {} },
348
+ id: provider.id,
349
+ type: provider.type
350
+ },
351
+ size: model.size
352
+ };
353
+ }
354
+ //#endregion
355
+ //#region src/core/source/runtime.ts
356
+ function createSourceFile(path, text) {
357
+ return {
358
+ language: inferLanguage(path),
359
+ lines: text.split(/\r?\n/),
360
+ path,
361
+ text
362
+ };
363
+ }
364
+ function createSourceRuntime() {
365
+ return {
366
+ getText: (target) => target.text,
367
+ readFile: async (filePath) => createSourceFile(filePath, await readFile(filePath, "utf8")),
368
+ sliceLines,
369
+ sliceRange
370
+ };
371
+ }
372
+ function sliceLines(file, range) {
373
+ const startLine = clampLine(range.startLine, file.lines.length);
374
+ const endLine = clampLine(range.endLine, file.lines.length);
375
+ const orderedStartLine = Math.min(startLine, endLine);
376
+ const orderedEndLine = Math.max(startLine, endLine);
377
+ const text = file.lines.slice(orderedStartLine - 1, orderedEndLine).join("\n");
378
+ return {
379
+ filePath: file.path,
380
+ loc: {
381
+ end: {
382
+ column: file.lines[orderedEndLine - 1]?.length ?? 0,
383
+ line: orderedEndLine
384
+ },
385
+ start: {
386
+ column: 0,
387
+ line: orderedStartLine
388
+ }
389
+ },
390
+ text
391
+ };
392
+ }
393
+ function sliceRange(file, range) {
394
+ const start = clampOffset(range.start, file.text.length);
395
+ const end = clampOffset(range.end, file.text.length);
396
+ const orderedStart = Math.min(start, end);
397
+ const orderedEnd = Math.max(start, end);
398
+ return {
399
+ filePath: file.path,
400
+ loc: {
401
+ end: getPosition(file.text, orderedEnd),
402
+ start: getPosition(file.text, orderedStart)
403
+ },
404
+ text: file.text.slice(orderedStart, orderedEnd)
405
+ };
406
+ }
407
+ function clampLine(line, lineCount) {
408
+ if (lineCount === 0) return 1;
409
+ return Math.min(Math.max(Math.trunc(line), 1), lineCount);
410
+ }
411
+ function clampOffset(offset, textLength) {
412
+ return Math.min(Math.max(Math.trunc(offset), 0), textLength);
413
+ }
414
+ function getPosition(text, offset) {
415
+ let line = 1;
416
+ let column = 0;
417
+ let index = 0;
418
+ while (index < offset) {
419
+ const character = text[index];
420
+ if (character === "\r") {
421
+ if (text[index + 1] === "\n" && index + 1 < offset) index += 1;
422
+ line += 1;
423
+ column = 0;
424
+ } else if (character === "\n") {
425
+ line += 1;
426
+ column = 0;
427
+ } else column += 1;
428
+ index += 1;
429
+ }
430
+ return {
431
+ column,
432
+ line
433
+ };
434
+ }
435
+ function inferLanguage(path) {
436
+ switch (extname(path)) {
437
+ case ".cjs":
438
+ case ".js":
439
+ case ".jsx":
440
+ case ".mjs": return "javascript";
441
+ case ".cts":
442
+ case ".mts":
443
+ case ".ts":
444
+ case ".tsx": return "typescript";
445
+ default: return "unknown";
446
+ }
447
+ }
448
+ //#endregion
449
+ //#region src/core/source/js.ts
450
+ function extractJsSourceUnits(file) {
451
+ const result = parseSync(file.path, file.text, { sourceType: "module" });
452
+ const state = {
453
+ bindingNodes: /* @__PURE__ */ new Map(),
454
+ classes: [],
455
+ exportedNodes: /* @__PURE__ */ new Set(),
456
+ file,
457
+ functions: [],
458
+ inferredNames: /* @__PURE__ */ new Map(),
459
+ seenClasses: /* @__PURE__ */ new Set(),
460
+ seenFunctions: /* @__PURE__ */ new Set(),
461
+ visited: /* @__PURE__ */ new Set()
462
+ };
463
+ collectModuleBindings(result.program, state);
464
+ collectExportedBindings(result.program, state);
465
+ visit(result.program, state);
466
+ return {
467
+ classes: state.classes,
468
+ functions: state.functions
469
+ };
470
+ }
471
+ function addClassUnit(node, state) {
472
+ if (state.seenClasses.has(node)) return;
473
+ const unit = createClassUnit(node, state);
474
+ if (!unit) return;
475
+ state.seenClasses.add(node);
476
+ state.classes.push(unit);
477
+ }
478
+ function addFunctionUnit(node, state) {
479
+ if (state.seenFunctions.has(node)) return;
480
+ const unit = createFunctionUnit(node, state);
481
+ if (!unit) return;
482
+ state.seenFunctions.add(node);
483
+ state.functions.push(unit);
484
+ }
485
+ function asAstNode(value) {
486
+ return isAstNode(value) ? value : void 0;
487
+ }
488
+ function collectDeclarationBindings(node, state) {
489
+ const name = getNodeName(node);
490
+ if (name && (isClassNode(node) || isFunctionNode(node))) {
491
+ state.bindingNodes.set(name, node);
492
+ return;
493
+ }
494
+ if (node.type !== "VariableDeclaration" || !Array.isArray(node.declarations)) return;
495
+ for (const declarator of node.declarations) {
496
+ if (!isAstNode(declarator)) continue;
497
+ const id = asAstNode(declarator.id);
498
+ const initializer = asAstNode(declarator.init);
499
+ if (typeof id?.name === "string" && initializer && (isClassNode(initializer) || isFunctionNode(initializer))) state.bindingNodes.set(id.name, initializer);
500
+ }
501
+ }
502
+ function collectExportedBindings(node, state) {
503
+ if (!node) return;
504
+ if (Array.isArray(node)) {
505
+ for (const child of node) collectExportedBindings(child, state);
506
+ return;
507
+ }
508
+ if (node.type === "ExportNamedDeclaration" && !node.source && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
509
+ if (!isAstNode(specifier) || specifier.type !== "ExportSpecifier") continue;
510
+ const local = asAstNode(specifier.local);
511
+ if (typeof local?.name === "string") markExportedBinding(local.name, state);
512
+ }
513
+ else if (node.type === "ExportDefaultDeclaration") {
514
+ const declaration = asAstNode(node.declaration);
515
+ if (typeof declaration?.name === "string") markExportedBinding(declaration.name, state);
516
+ }
517
+ for (const value of Object.values(node)) if (Array.isArray(value)) collectExportedBindings(value.filter(isAstNode), state);
518
+ else if (isAstNode(value)) collectExportedBindings(value, state);
519
+ }
520
+ function collectModuleBindings(program, state) {
521
+ if (!Array.isArray(program.body)) return;
522
+ for (const statement of program.body) {
523
+ if (!isAstNode(statement)) continue;
524
+ const declaration = isExportDeclaration(statement) ? asAstNode(statement.declaration) : statement;
525
+ if (declaration) collectDeclarationBindings(declaration, state);
526
+ }
527
+ }
528
+ function createClassUnit(node, state) {
529
+ const range = getRange(node);
530
+ if (!range) return;
531
+ const source = sliceRange(state.file, range);
532
+ return {
533
+ exported: isExportedUnit(node, state),
534
+ file: state.file,
535
+ kind: "class",
536
+ loc: source.loc,
537
+ name: getNodeName(node) ?? state.inferredNames.get(node),
538
+ range,
539
+ text: source.text
540
+ };
541
+ }
542
+ function createFunctionUnit(node, state) {
543
+ const range = getRange(node);
544
+ if (!range) return;
545
+ const source = sliceRange(state.file, range);
546
+ const functionNode = node.type === "MethodDefinition" ? asAstNode(node.value) : node;
547
+ return {
548
+ async: functionNode?.async === true,
549
+ exported: isExportedUnit(node, state),
550
+ file: state.file,
551
+ kind: "function",
552
+ loc: source.loc,
553
+ name: getNodeName(node) ?? getNodeName(functionNode) ?? state.inferredNames.get(node),
554
+ range,
555
+ text: source.text
556
+ };
557
+ }
558
+ function getNodeName(node) {
559
+ if (!node) return;
560
+ if (typeof node.name === "string") return node.name;
561
+ const id = asAstNode(node.id);
562
+ if (typeof id?.name === "string") return id.name;
563
+ const key = asAstNode(node.key);
564
+ if (typeof key?.name === "string") return key.name;
565
+ if (typeof key?.value === "string" || typeof key?.value === "number") return String(key.value);
566
+ }
567
+ function getRange(node) {
568
+ if (typeof node.start === "number" && typeof node.end === "number") return {
569
+ end: node.end,
570
+ start: node.start
571
+ };
572
+ if (Array.isArray(node.range) && node.range.length === 2) return {
573
+ end: node.range[1],
574
+ start: node.range[0]
575
+ };
576
+ }
577
+ function inferChildName(parent, key, child, state) {
578
+ if (state.inferredNames.has(child)) return;
579
+ if (parent.type === "VariableDeclarator" && key === "init") {
580
+ const id = asAstNode(parent.id);
581
+ if (typeof id?.name === "string") state.inferredNames.set(child, id.name);
582
+ }
583
+ if (parent.type === "Property" && key === "value") {
584
+ const name = getNodeName(parent);
585
+ if (name) state.inferredNames.set(child, name);
586
+ }
587
+ }
588
+ function isAstNode(value) {
589
+ return typeof value === "object" && value !== null && typeof value.type === "string";
590
+ }
591
+ function isClassNode(node) {
592
+ return node.type === "ClassDeclaration" || node.type === "ClassExpression";
593
+ }
594
+ function isExportDeclaration(node) {
595
+ return node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration";
596
+ }
597
+ function isExportedUnit(node, state) {
598
+ return state.exportedNodes.has(node);
599
+ }
600
+ function isFunctionNode(node) {
601
+ return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
602
+ }
603
+ function markExportedBinding(name, state) {
604
+ const bindingNode = state.bindingNodes.get(name);
605
+ if (bindingNode) state.exportedNodes.add(bindingNode);
606
+ }
607
+ function markExportedDeclaration(node, state) {
608
+ state.exportedNodes.add(node);
609
+ if (node.type !== "VariableDeclaration" || !Array.isArray(node.declarations)) return;
610
+ for (const declarator of node.declarations) {
611
+ if (!isAstNode(declarator)) continue;
612
+ const initializer = asAstNode(declarator.init);
613
+ if (initializer && (isClassNode(initializer) || isFunctionNode(initializer))) state.exportedNodes.add(initializer);
614
+ }
615
+ }
616
+ function visit(node, state) {
617
+ if (!node) return;
618
+ if (Array.isArray(node)) {
619
+ for (const child of node) visit(child, state);
620
+ return;
621
+ }
622
+ if (state.visited.has(node)) return;
623
+ state.visited.add(node);
624
+ if (node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration") {
625
+ if (node.declaration) {
626
+ markExportedDeclaration(node.declaration, state);
627
+ visit(node.declaration, state);
628
+ }
629
+ visitChildren(node, state, /* @__PURE__ */ new Set(["declaration"]));
630
+ return;
631
+ }
632
+ if (isClassNode(node)) addClassUnit(node, state);
633
+ else if (isFunctionNode(node) || node.type === "MethodDefinition") {
634
+ addFunctionUnit(node, state);
635
+ const methodValue = node.type === "MethodDefinition" ? asAstNode(node.value) : void 0;
636
+ if (methodValue) state.seenFunctions.add(methodValue);
637
+ }
638
+ visitChildren(node, state);
639
+ }
640
+ function visitChildren(node, state, skippedKeys = /* @__PURE__ */ new Set()) {
641
+ for (const [key, value] of Object.entries(node)) {
642
+ if (skippedKeys.has(key) || key === "type" || key === "start" || key === "end" || key === "range") continue;
643
+ if (Array.isArray(value)) {
644
+ for (const child of value) if (isAstNode(child)) {
645
+ inferChildName(node, key, child, state);
646
+ visit(child, state);
647
+ }
648
+ } else if (isAstNode(value)) {
649
+ inferChildName(node, key, value, state);
650
+ visit(value, state);
651
+ }
652
+ }
653
+ }
654
+ //#endregion
655
+ //#region src/core/run.ts
656
+ var AlintRuleExecutionError = class extends Error {
657
+ cause;
658
+ failure;
659
+ constructor(error, path) {
660
+ const message = toErrorMessage(error);
661
+ super(message);
662
+ this.name = "AlintRuleExecutionError";
663
+ this.cause = error;
664
+ this.failure = {
665
+ filePath: path.file.path,
666
+ message,
667
+ ruleId: path.rule.id,
668
+ target: {
669
+ kind: path.target.kind,
670
+ name: path.target.name
671
+ }
672
+ };
673
+ }
674
+ };
675
+ var AlintRunError = class extends Error {
676
+ cause;
677
+ failure;
678
+ result;
679
+ constructor(message, result, options = {}) {
680
+ super(message);
681
+ this.name = "AlintRunError";
682
+ this.cause = options.cause;
683
+ this.failure = options.failure;
684
+ this.result = result;
685
+ }
686
+ };
687
+ async function runAlint(options = {}) {
688
+ const cwd = options.cwd ?? process.cwd();
689
+ const config = options.config ?? await loadAlintConfig(cwd);
690
+ const setupConfig = options.setupConfig ?? emptySetupConfig;
691
+ const clock = options.runner?.clock ?? Date.now;
692
+ const diagnostics = [];
693
+ const usage = createUsageAccumulator();
694
+ const registry = buildRuleRegistry(config);
695
+ const src = createSourceRuntime();
696
+ const files = await Promise.all((options.files ?? []).map(async (filePath) => {
697
+ const file = await src.readFile(resolve(cwd, filePath));
698
+ return {
699
+ file,
700
+ units: file.language === "javascript" || file.language === "typescript" ? extractJsSourceUnits(file) : void 0
701
+ };
702
+ }));
703
+ let planned = 0;
704
+ const counters = createRuleEndCounters();
705
+ const primaryError = createPrimaryErrorState();
706
+ let runStartedAt;
707
+ let runError;
708
+ try {
709
+ const filePlans = createPreparedFileExecutionPlans(registry.enabledRules.map((enabledRule) => {
710
+ const executionState = new AsyncLocalStorage();
711
+ const context = {
712
+ cwd,
713
+ id: enabledRule.id,
714
+ localId: enabledRule.localId,
715
+ logger: { debug: () => {} },
716
+ metering: { recordUsage: (record) => {
717
+ const usageRecord = usage.record({
718
+ ...record,
719
+ ruleId: record.ruleId ?? enabledRule.id
720
+ });
721
+ options.progress?.onUsage?.({
722
+ path: executionState.getStore()?.progressPath,
723
+ record: usageRecord,
724
+ total: usage.toJSON()
725
+ });
726
+ } },
727
+ model: async (selector) => {
728
+ const request = options.modelOverride ?? (typeof selector === "string" ? selector : void 0);
729
+ const resolvedModel = resolveModel(setupConfig, {
730
+ request,
731
+ requirement: mergeModelRequirement(enabledRule.rule.model, typeof selector === "string" ? void 0 : selector),
732
+ ruleId: enabledRule.id
733
+ });
734
+ const state = executionState.getStore();
735
+ if (state) state.currentModel = toDiagnosticModel(resolvedModel, request);
736
+ return resolvedModel;
737
+ },
738
+ report: (descriptor) => {
739
+ const state = executionState.getStore();
740
+ const filePath = descriptor.filePath ?? state?.activeFilePath;
741
+ if (!filePath) throw new Error(`Diagnostic for rule "${enabledRule.id}" is missing filePath.`);
742
+ const diagnosticModel = state?.currentModel ? { ...state.currentModel } : void 0;
743
+ if (state) state.currentModel = void 0;
744
+ const diagnostic = {
745
+ evidence: descriptor.evidence,
746
+ filePath,
747
+ loc: descriptor.loc,
748
+ message: descriptor.message,
749
+ model: diagnosticModel,
750
+ ruleId: enabledRule.id,
751
+ severity: enabledRule.severity
752
+ };
753
+ diagnostics.push(diagnostic);
754
+ options.progress?.onDiagnostic?.({
755
+ diagnostic,
756
+ diagnostics: [...diagnostics],
757
+ path: state?.progressPath
758
+ });
759
+ },
760
+ scope: enabledRule.scope,
761
+ src
762
+ };
763
+ return {
764
+ enabledRule,
765
+ executionState,
766
+ handlers: enabledRule.rule.create(context)
767
+ };
768
+ }), files);
769
+ planned = calculatePlannedExecutions(filePlans);
770
+ runStartedAt = clock();
771
+ options.progress?.onRunStart?.({
772
+ files: filePlans.map((filePlan) => toProgressFilePath(filePlan, files.length)),
773
+ filesTotal: files.length,
774
+ planned,
775
+ rulesTotal: registry.enabledRules.length,
776
+ startedAt: runStartedAt
777
+ });
778
+ await runConcurrently(filePlans.filter((filePlan) => filePlan.targets.length > 0), resolveFileConcurrency(options.runner?.fileConcurrency), (filePlan) => executeFilePlan(filePlan, files.length, clock, counters, options));
779
+ } catch (error) {
780
+ primaryError.set();
781
+ runError = error;
782
+ } finally {
783
+ emitCleanupProgress(() => options.progress?.onRunEnd?.({
784
+ ...counters.snapshot(planned),
785
+ diagnostics,
786
+ endedAt: clock(),
787
+ startedAt: runStartedAt ?? clock(),
788
+ usage: usage.toJSON()
789
+ }), primaryError);
790
+ }
791
+ const result = {
792
+ diagnostics,
793
+ usage: usage.toJSON()
794
+ };
795
+ if (runError) throw createAlintRunError(runError, result);
796
+ return result;
797
+ }
798
+ function addTokenCount(base, value) {
799
+ return typeof value === "number" && Number.isFinite(value) ? base + value : base;
800
+ }
801
+ function calculateFilePlanExecutions(filePlan) {
802
+ return filePlan.targets.reduce((total, target) => total + target.executions.length, 0);
803
+ }
804
+ function calculatePlannedExecutions(filePlans) {
805
+ return filePlans.reduce((total, filePlan) => total + filePlan.planned, 0);
806
+ }
807
+ function collectExecutionTargets(ruleRuntimes, preparedFile) {
808
+ const targets = [];
809
+ const fileExecutions = ruleRuntimes.map((runtime) => {
810
+ if (!runtime.handlers.onFile) return void 0;
811
+ return {
812
+ run: () => runtime.handlers.onFile?.(preparedFile.file),
813
+ runtime
814
+ };
815
+ }).filter((execution) => execution !== void 0);
816
+ if (fileExecutions.length > 0) targets.push({
817
+ executions: fileExecutions,
818
+ kind: "file"
819
+ });
820
+ if (preparedFile.units) {
821
+ for (const classNode of preparedFile.units.classes) {
822
+ const executions = ruleRuntimes.map((runtime) => {
823
+ if (!runtime.handlers.onClass) return void 0;
824
+ return {
825
+ run: () => runtime.handlers.onClass?.(classNode),
826
+ runtime
827
+ };
828
+ }).filter((execution) => execution !== void 0);
829
+ if (executions.length > 0) targets.push({
830
+ executions,
831
+ kind: "class",
832
+ name: classNode.name
833
+ });
834
+ }
835
+ for (const functionNode of preparedFile.units.functions) {
836
+ const executions = ruleRuntimes.map((runtime) => {
837
+ if (!runtime.handlers.onFunction) return void 0;
838
+ return {
839
+ run: () => runtime.handlers.onFunction?.(functionNode),
840
+ runtime
841
+ };
842
+ }).filter((execution) => execution !== void 0);
843
+ if (executions.length > 0) targets.push({
844
+ executions,
845
+ kind: "function",
846
+ name: functionNode.name
847
+ });
848
+ }
849
+ }
850
+ return targets;
851
+ }
852
+ function createAlintRunError(error, result) {
853
+ if (error instanceof AlintRunError) return error;
854
+ if (error instanceof AlintRuleExecutionError) return new AlintRunError(error.message, result, {
855
+ cause: error.cause,
856
+ failure: error.failure
857
+ });
858
+ return new AlintRunError(toErrorMessage(error), result, { cause: error });
859
+ }
860
+ function createPreparedFileExecutionPlans(ruleRuntimes, files) {
861
+ return files.map((preparedFile, fileOffset) => {
862
+ const filePlan = {
863
+ fileIndex: fileOffset + 1,
864
+ planned: 0,
865
+ preparedFile,
866
+ targets: collectExecutionTargets(ruleRuntimes, preparedFile)
867
+ };
868
+ filePlan.planned = calculateFilePlanExecutions(filePlan);
869
+ return filePlan;
870
+ });
871
+ }
872
+ function createPrimaryErrorState() {
873
+ let hasError = false;
874
+ return {
875
+ get hasError() {
876
+ return hasError;
877
+ },
878
+ set() {
879
+ hasError = true;
880
+ }
881
+ };
882
+ }
883
+ function createProgressPath(filePath, ruleId, entry) {
884
+ return {
885
+ file: {
886
+ index: entry.fileIndex,
887
+ path: filePath,
888
+ planned: entry.filePlanned,
889
+ total: entry.fileTotal
890
+ },
891
+ rule: {
892
+ id: ruleId,
893
+ index: entry.ruleIndex,
894
+ total: entry.ruleTotal
895
+ },
896
+ target: {
897
+ index: entry.targetIndex,
898
+ kind: entry.targetKind,
899
+ name: entry.targetName,
900
+ total: entry.targetTotal
901
+ }
902
+ };
903
+ }
904
+ function createRuleEndCounters() {
905
+ let completed = 0;
906
+ let errored = 0;
907
+ return {
908
+ complete() {
909
+ completed += 1;
910
+ },
911
+ error() {
912
+ errored += 1;
913
+ },
914
+ snapshot(planned) {
915
+ return {
916
+ cached: 0,
917
+ completed,
918
+ errored,
919
+ planned
920
+ };
921
+ }
922
+ };
923
+ }
924
+ function createUsageAccumulator() {
925
+ const records = [];
926
+ let inputTokens = 0;
927
+ let outputTokens = 0;
928
+ let totalTokens = 0;
929
+ return {
930
+ record(record) {
931
+ records.push(record);
932
+ inputTokens = addTokenCount(inputTokens, record.inputTokens);
933
+ outputTokens = addTokenCount(outputTokens, record.outputTokens);
934
+ totalTokens = addTokenCount(totalTokens, record.totalTokens);
935
+ return record;
936
+ },
937
+ toJSON() {
938
+ return {
939
+ inputTokens,
940
+ outputTokens,
941
+ records,
942
+ totalTokens
943
+ };
944
+ }
945
+ };
946
+ }
947
+ function emitCleanupProgress(callback, primaryError) {
948
+ try {
949
+ callback();
950
+ } catch (error) {
951
+ if (!primaryError.hasError) throw error;
952
+ }
953
+ }
954
+ async function executeFilePlan(filePlan, filesTotal, clock, counters, options) {
955
+ const preparedFile = filePlan.preparedFile;
956
+ const fileStartedAt = clock();
957
+ const fileProgress = {
958
+ index: filePlan.fileIndex,
959
+ path: preparedFile.file.path,
960
+ planned: filePlan.planned,
961
+ total: filesTotal
962
+ };
963
+ options.progress?.onFileStart?.({
964
+ file: fileProgress,
965
+ startedAt: fileStartedAt
966
+ });
967
+ const fileError = createPrimaryErrorState();
968
+ try {
969
+ for (const [targetOffset, target] of filePlan.targets.entries()) {
970
+ const targetPath = createProgressPath(preparedFile.file.path, target.executions[0]?.runtime.enabledRule.id ?? "", {
971
+ fileIndex: filePlan.fileIndex,
972
+ filePlanned: filePlan.planned,
973
+ fileTotal: filesTotal,
974
+ ruleIndex: 1,
975
+ ruleTotal: target.executions.length,
976
+ targetIndex: targetOffset + 1,
977
+ targetKind: target.kind,
978
+ targetName: target.name,
979
+ targetTotal: filePlan.targets.length
980
+ });
981
+ const targetError = createPrimaryErrorState();
982
+ const targetStartedAt = clock();
983
+ options.progress?.onTargetStart?.({
984
+ path: targetPath,
985
+ startedAt: targetStartedAt
986
+ });
987
+ try {
988
+ for (const [executionOffset, execution] of target.executions.entries()) await executeProgressTarget(execution, createProgressPath(preparedFile.file.path, execution.runtime.enabledRule.id, {
989
+ fileIndex: filePlan.fileIndex,
990
+ filePlanned: filePlan.planned,
991
+ fileTotal: filesTotal,
992
+ ruleIndex: executionOffset + 1,
993
+ ruleTotal: target.executions.length,
994
+ targetIndex: targetOffset + 1,
995
+ targetKind: target.kind,
996
+ targetName: target.name,
997
+ targetTotal: filePlan.targets.length
998
+ }), clock, counters, options);
999
+ } catch (error) {
1000
+ targetError.set();
1001
+ throw error;
1002
+ } finally {
1003
+ emitCleanupProgress(() => options.progress?.onTargetEnd?.({
1004
+ endedAt: clock(),
1005
+ path: targetPath,
1006
+ startedAt: targetStartedAt
1007
+ }), targetError);
1008
+ }
1009
+ }
1010
+ } catch (error) {
1011
+ fileError.set();
1012
+ throw error;
1013
+ } finally {
1014
+ emitCleanupProgress(() => options.progress?.onFileEnd?.({
1015
+ endedAt: clock(),
1016
+ file: fileProgress,
1017
+ startedAt: fileStartedAt
1018
+ }), fileError);
1019
+ }
1020
+ }
1021
+ async function executeProgressTarget(execution, path, clock, counters, options) {
1022
+ const startedAt = clock();
1023
+ options.progress?.onRuleStart?.({
1024
+ path,
1025
+ startedAt
1026
+ });
1027
+ let handlerError;
1028
+ let handlerSucceeded = false;
1029
+ try {
1030
+ await execution.runtime.executionState.run({
1031
+ activeFilePath: path.file.path,
1032
+ progressPath: path
1033
+ }, execution.run);
1034
+ handlerSucceeded = true;
1035
+ } catch (error) {
1036
+ handlerError = error;
1037
+ }
1038
+ if (handlerSucceeded) {
1039
+ counters.complete();
1040
+ options.progress?.onRuleEnd?.({
1041
+ cache: "miss",
1042
+ endedAt: clock(),
1043
+ path,
1044
+ startedAt,
1045
+ state: "completed"
1046
+ });
1047
+ return;
1048
+ }
1049
+ counters.error();
1050
+ try {
1051
+ options.progress?.onRuleEnd?.({
1052
+ cache: "miss",
1053
+ endedAt: clock(),
1054
+ path,
1055
+ startedAt,
1056
+ state: "errored"
1057
+ });
1058
+ } catch {}
1059
+ throw new AlintRuleExecutionError(handlerError, path);
1060
+ }
1061
+ function mergeCapabilities(base, extra) {
1062
+ if (!base && !extra) return;
1063
+ return [.../* @__PURE__ */ new Set([...base ?? [], ...extra ?? []])];
1064
+ }
1065
+ function mergeMinContextWindow(base, extra) {
1066
+ if (base === void 0) return extra;
1067
+ if (extra === void 0) return base;
1068
+ return Math.max(base, extra);
1069
+ }
1070
+ function mergeModelRequirement(base, extra) {
1071
+ if (!base && !extra) return;
1072
+ const capabilities = mergeCapabilities(base?.capabilities, extra?.capabilities);
1073
+ const params = {
1074
+ ...base?.params ?? {},
1075
+ ...extra?.params ?? {}
1076
+ };
1077
+ return {
1078
+ capabilities,
1079
+ minContextWindow: mergeMinContextWindow(base?.minContextWindow, extra?.minContextWindow),
1080
+ params: Object.keys(params).length > 0 ? params : void 0,
1081
+ size: extra?.size ?? base?.size
1082
+ };
1083
+ }
1084
+ function resolveFileConcurrency(fileConcurrency) {
1085
+ return fileConcurrency ?? 1;
1086
+ }
1087
+ async function runConcurrently(items, concurrency, runItem) {
1088
+ let firstError;
1089
+ let nextIndex = 0;
1090
+ const workerCount = Math.min(Math.max(concurrency, 1), items.length);
1091
+ async function runWorker() {
1092
+ while (firstError === void 0) {
1093
+ const currentIndex = nextIndex;
1094
+ nextIndex += 1;
1095
+ if (currentIndex >= items.length) return;
1096
+ try {
1097
+ await runItem(items[currentIndex]);
1098
+ } catch (error) {
1099
+ firstError ??= error;
1100
+ return;
1101
+ }
1102
+ }
1103
+ }
1104
+ await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
1105
+ if (firstError !== void 0) throw firstError;
1106
+ }
1107
+ function toDiagnosticModel(model, request) {
1108
+ return {
1109
+ providerId: model.provider.id,
1110
+ requested: request,
1111
+ resolvedId: model.id
1112
+ };
1113
+ }
1114
+ function toErrorMessage(error) {
1115
+ if (error instanceof Error) return error.message;
1116
+ return String(error);
1117
+ }
1118
+ function toProgressFilePath(filePlan, filesTotal) {
1119
+ return {
1120
+ index: filePlan.fileIndex,
1121
+ path: filePlan.preparedFile.file.path,
1122
+ planned: filePlan.planned,
1123
+ total: filesTotal
1124
+ };
1125
+ }
1126
+ //#endregion
1127
+ //#region src/cli/reporters/json.ts
1128
+ function formatJson(result) {
1129
+ return `${JSON.stringify(result, null, 2)}\n`;
1130
+ }
1131
+ //#endregion
1132
+ //#region src/cli/reporters/stylish.ts
1133
+ const colors$1 = createColors({ force: true });
1134
+ function formatStylish(input, options = {}) {
1135
+ const diagnostics = Array.isArray(input) ? input : input.diagnostics;
1136
+ const totalTokens = Array.isArray(input) ? void 0 : input.usage.totalTokens;
1137
+ if (diagnostics.length === 0) return "";
1138
+ const diagnosticsByFile = /* @__PURE__ */ new Map();
1139
+ for (const diagnostic of diagnostics) {
1140
+ const fileDiagnostics = diagnosticsByFile.get(diagnostic.filePath);
1141
+ if (fileDiagnostics) {
1142
+ fileDiagnostics.push(diagnostic);
1143
+ continue;
1144
+ }
1145
+ diagnosticsByFile.set(diagnostic.filePath, [diagnostic]);
1146
+ }
1147
+ const lines = [];
1148
+ const style = createStyle(options.color === true);
1149
+ for (const [filePath, fileDiagnostics] of diagnosticsByFile) {
1150
+ lines.push(style.file(filePath));
1151
+ for (const diagnostic of fileDiagnostics) {
1152
+ const line = diagnostic.loc?.start.line ?? 0;
1153
+ const column = diagnostic.loc?.start.column ?? 0;
1154
+ const severity = diagnostic.severity === "warn" ? style.warning("warning") : style.error("error");
1155
+ lines.push(` ${style.location(`${line}:${column}`)} ${severity} ${diagnostic.message} ${style.ruleId(diagnostic.ruleId)}`);
1156
+ }
1157
+ lines.push("");
1158
+ }
1159
+ lines.push("", formatSummary(diagnostics, totalTokens, style));
1160
+ return `${lines.join("\n")}\n`;
1161
+ }
1162
+ function countDiagnostics$2(diagnostics, severity) {
1163
+ return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
1164
+ }
1165
+ function createStyle(color) {
1166
+ if (!color) return {
1167
+ error: identity,
1168
+ file: identity,
1169
+ location: identity,
1170
+ ruleId: identity,
1171
+ summaryToken: identity,
1172
+ warning: identity
1173
+ };
1174
+ return {
1175
+ error: colors$1.red,
1176
+ file: colors$1.underline,
1177
+ location: colors$1.dim,
1178
+ ruleId: colors$1.dim,
1179
+ summaryToken: colors$1.cyan,
1180
+ warning: colors$1.yellow
1181
+ };
1182
+ }
1183
+ function formatSummary(diagnostics, totalTokens, style) {
1184
+ const warnCount = countDiagnostics$2(diagnostics, "warn");
1185
+ const errorCount = countDiagnostics$2(diagnostics, "error");
1186
+ const tokens = totalTokens === void 0 ? void 0 : `${totalTokens.toLocaleString("en-US")} tokens`;
1187
+ const problemSummary = [style.warning(`${warnCount} warn`), style.error(`${errorCount} error`)].join(" / ");
1188
+ if (tokens === void 0) return problemSummary;
1189
+ return `${problemSummary} | ${style.summaryToken(tokens)}`;
1190
+ }
1191
+ function identity(value) {
1192
+ return value;
1193
+ }
1194
+ //#endregion
1195
+ //#region src/cli/reporters/index.ts
1196
+ function formatDiagnostics(format, result, options = {}) {
1197
+ if (format === "json") return formatJson(result);
1198
+ if (format === "stylish") return formatStylish(result, { color: options.color });
1199
+ throw new Error(`Unknown reporter "${format}".`);
1200
+ }
1201
+ //#endregion
1202
+ //#region src/cli/reporters/progress/plain.ts
1203
+ function createPlainProgressReporter(options) {
1204
+ const writeLine = (line) => options.write(`${line}\n`);
1205
+ return {
1206
+ onRuleStart: (payload) => {
1207
+ const target = payload.path.target.name ? `${payload.path.target.kind} ${payload.path.target.name}` : payload.path.target.kind;
1208
+ writeLine(`scan ${payload.path.file.path} > ${target} > ${payload.path.rule.id}`);
1209
+ },
1210
+ onRunEnd: (payload) => {
1211
+ const warnCount = countDiagnostics$1(payload.diagnostics, "warn");
1212
+ const errorCount = countDiagnostics$1(payload.diagnostics, "error");
1213
+ const state = payload.errored > 0 ? "failed" : "finished";
1214
+ const errored = payload.errored > 0 ? `, ${payload.errored} errored` : "";
1215
+ writeLine(`alint ${state}: ${warnCount} warn, ${errorCount} error, ${payload.usage.totalTokens} tokens${errored}`);
1216
+ },
1217
+ onRunStart: (payload) => {
1218
+ writeLine(`alint started: ${payload.filesTotal} files, ${payload.rulesTotal} rules, ${payload.planned} planned executions`);
1219
+ }
1220
+ };
1221
+ }
1222
+ function countDiagnostics$1(diagnostics, severity) {
1223
+ return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
1224
+ }
1225
+ //#endregion
1226
+ //#region src/cli/reporters/progress/summary.ts
1227
+ const colors = createColors({ force: true });
1228
+ function createSummaryProgressReporter(options) {
1229
+ const now = () => options.clock?.() ?? Date.now();
1230
+ const state = {
1231
+ cached: 0,
1232
+ completed: 0,
1233
+ diagnostics: [],
1234
+ errored: 0,
1235
+ files: /* @__PURE__ */ new Map(),
1236
+ planned: 0,
1237
+ spinnerIndex: 0,
1238
+ totalTokens: 0
1239
+ };
1240
+ return {
1241
+ getRows: () => createRows(state, options, now()),
1242
+ onDiagnostic: (payload) => {
1243
+ state.diagnostics = payload.diagnostics;
1244
+ },
1245
+ onFileEnd: (payload) => {
1246
+ const file = getFileState(state, payload.file);
1247
+ file.endedAt = payload.endedAt ?? now();
1248
+ file.rule = void 0;
1249
+ file.target = void 0;
1250
+ },
1251
+ onFileStart: (payload) => {
1252
+ const file = getFileState(state, payload.file);
1253
+ file.startedAt = payload.startedAt ?? now();
1254
+ file.endedAt = void 0;
1255
+ },
1256
+ onRuleEnd: (payload) => {
1257
+ const file = getFileState(state, payload.path.file);
1258
+ if (payload.cache === "hit") {
1259
+ state.cached += 1;
1260
+ file.cached += 1;
1261
+ }
1262
+ if (payload.state === "completed") {
1263
+ state.completed += 1;
1264
+ file.completed += 1;
1265
+ }
1266
+ if (payload.state === "errored") {
1267
+ state.errored += 1;
1268
+ file.errored += 1;
1269
+ }
1270
+ if (file.rule?.id === payload.path.rule.id) file.rule = void 0;
1271
+ },
1272
+ onRuleStart: (payload) => {
1273
+ const file = getFileState(state, payload.path.file);
1274
+ file.startedAt ??= payload.startedAt ?? now();
1275
+ file.rule = {
1276
+ id: payload.path.rule.id,
1277
+ startedAt: payload.startedAt ?? now(),
1278
+ target: formatTarget(payload)
1279
+ };
1280
+ file.target = file.rule.target;
1281
+ },
1282
+ onRunEnd: (payload) => {
1283
+ state.cached = payload.cached;
1284
+ state.completed = payload.completed;
1285
+ state.diagnostics = payload.diagnostics;
1286
+ state.endedAt = payload.endedAt ?? now();
1287
+ state.errored = payload.errored;
1288
+ state.planned = payload.planned;
1289
+ state.runStartedAt = payload.startedAt ?? state.runStartedAt;
1290
+ state.totalTokens = payload.usage.totalTokens;
1291
+ },
1292
+ onRunStart: (payload) => {
1293
+ state.cached = 0;
1294
+ state.completed = 0;
1295
+ state.diagnostics = [];
1296
+ state.endedAt = void 0;
1297
+ state.errored = 0;
1298
+ state.files = /* @__PURE__ */ new Map();
1299
+ state.planned = payload.planned;
1300
+ state.runStartedAt = payload.startedAt ?? now();
1301
+ state.spinnerIndex = 0;
1302
+ state.totalTokens = 0;
1303
+ for (const file of payload.files ?? []) state.files.set(file.path, createFileState(file));
1304
+ },
1305
+ onTargetEnd: (payload) => {
1306
+ const file = getFileState(state, payload.path.file);
1307
+ const target = formatTarget(payload);
1308
+ if (file.target === target) file.target = void 0;
1309
+ },
1310
+ onTargetStart: (payload) => {
1311
+ const file = getFileState(state, payload.path.file);
1312
+ file.startedAt ??= payload.startedAt ?? now();
1313
+ file.target = formatTarget(payload);
1314
+ },
1315
+ onUsage: (payload) => {
1316
+ state.totalTokens = payload.total.totalTokens;
1317
+ },
1318
+ tick: () => {
1319
+ state.spinnerIndex = (state.spinnerIndex + 1) % Math.max(options.spinnerFrames.length, 1);
1320
+ }
1321
+ };
1322
+ }
1323
+ function countDiagnostics(diagnostics, severity) {
1324
+ return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
1325
+ }
1326
+ function countQueuedFiles(state) {
1327
+ return [...state.files.values()].filter((file) => file.startedAt === void 0 && file.endedAt === void 0 && (file.file.planned ?? 0) > 0).length;
1328
+ }
1329
+ function createFileState(file) {
1330
+ return {
1331
+ cached: 0,
1332
+ completed: 0,
1333
+ errored: 0,
1334
+ file
1335
+ };
1336
+ }
1337
+ function createRows(state, options, now) {
1338
+ const rows = getActiveFiles(state).flatMap((file) => formatFileRows(file, state, options, now));
1339
+ const queued = countQueuedFiles(state);
1340
+ const warnCount = countDiagnostics(state.diagnostics, "warn");
1341
+ const errorCount = countDiagnostics(state.diagnostics, "error");
1342
+ const footer = formatFooter(state, warnCount, errorCount, queued, options, now);
1343
+ if (queued > 0) rows.push(formatQueuedRow(queued, options));
1344
+ if (rows.length === 0) rows.push(formatIdleRow(state, options));
1345
+ rows.push("", footer);
1346
+ return options.color ? rows.map((row) => styleRow(row, state, warnCount, errorCount, options)) : rows;
1347
+ }
1348
+ function escapeRegExp(value) {
1349
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1350
+ }
1351
+ function estimateTotal(elapsedMs, completed, planned) {
1352
+ if (completed <= 0 || planned <= 0) return;
1353
+ return elapsedMs * planned / completed;
1354
+ }
1355
+ function fitRow(row, columns) {
1356
+ if (columns <= 0) return "";
1357
+ if (row.length <= columns) return row;
1358
+ if (columns === 1) return "…";
1359
+ return `${row.slice(0, columns - 1)}…`;
1360
+ }
1361
+ function formatDuration(ms) {
1362
+ if (ms === void 0 || !Number.isFinite(ms)) return "?";
1363
+ return `${(Math.max(ms, 0) / 1e3).toFixed(1)}s`;
1364
+ }
1365
+ function formatEstimatedDuration(ms) {
1366
+ return `~${formatDuration(ms)}`;
1367
+ }
1368
+ function formatFilePath(filePath, cwd) {
1369
+ if (!cwd) return filePath;
1370
+ return relative(cwd, filePath) || filePath;
1371
+ }
1372
+ function formatFileRows(file, state, options, now) {
1373
+ const firstRow = formatFileSummaryRow(file, state, options);
1374
+ if (!file.rule) return [firstRow];
1375
+ const elapsed = now - file.rule.startedAt;
1376
+ const done = file.completed + file.cached + file.errored;
1377
+ const estimated = estimateTotal(file.startedAt === void 0 ? elapsed : now - file.startedAt, done, file.file.planned ?? 0);
1378
+ return [firstRow, fitRow(` ${file.rule.target} > ${file.rule.id} (${formatDuration(elapsed)}, ${formatEstimatedDuration(estimated)})`, options.columns)];
1379
+ }
1380
+ function formatFileSummaryRow(file, state, options) {
1381
+ const prefix = `${options.spinnerFrames[state.spinnerIndex] ?? ""} ${formatFilePath(file.file.path, options.cwd)}`;
1382
+ const counter = `${file.completed}/${file.cached}/${file.errored}/${file.file.planned ?? 0}`;
1383
+ return fitRow(`${prefix}${" ".repeat(Math.max(1, options.columns - prefix.length - counter.length))}${counter}`, options.columns);
1384
+ }
1385
+ function formatFooter(state, warnCount, errorCount, queued, options, now) {
1386
+ const endedAt = state.endedAt ?? now;
1387
+ const elapsed = state.runStartedAt === void 0 ? void 0 : endedAt - state.runStartedAt;
1388
+ const completed = state.completed + state.cached + state.errored;
1389
+ const estimated = elapsed === void 0 ? void 0 : estimateTotal(elapsed, completed, state.planned);
1390
+ const estimatedTokens = completed > 0 && state.planned > 0 ? Math.ceil(state.totalTokens * state.planned / completed).toLocaleString("en-US") : "?";
1391
+ return fitRow([
1392
+ `${formatDuration(elapsed)} -> ${formatEstimatedDuration(estimated)}`,
1393
+ `${state.totalTokens.toLocaleString("en-US")} tokens -> ~${estimatedTokens} tokens`,
1394
+ `${queued} queued / ${state.cached} cached / ${warnCount} warn / ${errorCount} error`
1395
+ ].join(" | "), options.columns);
1396
+ }
1397
+ function formatIdleRow(state, options) {
1398
+ const prefix = `${options.spinnerFrames[state.spinnerIndex] ?? ""} alint`;
1399
+ const counter = `${state.completed}/${state.cached}/${state.errored}/${state.planned}`;
1400
+ return fitRow(`${prefix}${" ".repeat(Math.max(1, options.columns - prefix.length - counter.length))}${counter}`, options.columns);
1401
+ }
1402
+ function formatQueuedRow(queued, options) {
1403
+ return fitRow(` ${queued} ${queued === 1 ? "file" : "files"} queued`, options.columns);
1404
+ }
1405
+ function formatTarget(payload) {
1406
+ return payload.path.target.name ? `${payload.path.target.kind} ${payload.path.target.name}` : payload.path.target.kind;
1407
+ }
1408
+ function getActiveFiles(state) {
1409
+ return [...state.files.values()].filter((file) => file.startedAt !== void 0 && file.endedAt === void 0).sort((left, right) => left.file.index - right.file.index);
1410
+ }
1411
+ function getFileState(state, file) {
1412
+ const existingFile = state.files.get(file.path);
1413
+ if (existingFile) {
1414
+ existingFile.file = {
1415
+ ...existingFile.file,
1416
+ ...file,
1417
+ planned: file.planned ?? existingFile.file.planned
1418
+ };
1419
+ return existingFile;
1420
+ }
1421
+ const nextFile = createFileState({
1422
+ ...file,
1423
+ planned: file.planned ?? (state.files.size === 0 ? state.planned : void 0)
1424
+ });
1425
+ state.files.set(file.path, nextFile);
1426
+ return nextFile;
1427
+ }
1428
+ function replaceFirst(row, search, replacement) {
1429
+ if (search.length === 0) return row;
1430
+ return row.replace(new RegExp(escapeRegExp(search)), replacement);
1431
+ }
1432
+ function styleRow(row, state, warnCount, errorCount, options) {
1433
+ let styledRow = row;
1434
+ const spinner = options.spinnerFrames[state.spinnerIndex] ?? "";
1435
+ if (spinner) styledRow = replaceFirst(styledRow, spinner, colors.cyan(spinner));
1436
+ 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));
1437
+ return styledRow;
1438
+ }
1439
+ //#endregion
1440
+ //#region src/cli/reporters/progress/tty.ts
1441
+ const clearCurrentLine = "\r\x1B[K";
1442
+ const clearPreviousLine = "\r\x1B[1A\x1B[K";
1443
+ function createTtyProgressRenderer(options) {
1444
+ let interval;
1445
+ let previousRows = 0;
1446
+ const clearPreviousFrame = () => {
1447
+ if (previousRows === 0) return;
1448
+ let sequence = clearCurrentLine;
1449
+ for (let row = 1; row < previousRows; row += 1) sequence += clearPreviousLine;
1450
+ options.write(sequence);
1451
+ previousRows = 0;
1452
+ };
1453
+ const render = () => {
1454
+ clearPreviousFrame();
1455
+ const rows = options.getRows();
1456
+ if (rows.length === 0) return;
1457
+ options.write(rows.join("\n"));
1458
+ previousRows = rows.length;
1459
+ };
1460
+ const write = (chunk) => {
1461
+ const wasRendering = interval !== void 0;
1462
+ clearPreviousFrame();
1463
+ options.write(chunk);
1464
+ if (wasRendering) render();
1465
+ };
1466
+ return {
1467
+ finish: () => {
1468
+ if (interval) {
1469
+ options.clearInterval(interval);
1470
+ interval = void 0;
1471
+ }
1472
+ clearPreviousFrame();
1473
+ },
1474
+ render,
1475
+ start: () => {
1476
+ if (!interval) {
1477
+ interval = options.createInterval(render, options.intervalMs);
1478
+ if (isUnrefableInterval(interval)) interval.unref();
1479
+ }
1480
+ render();
1481
+ },
1482
+ write
1483
+ };
1484
+ }
1485
+ function isUnrefableInterval(interval) {
1486
+ if (typeof interval !== "object" || interval === null || !("unref" in interval)) return false;
1487
+ return typeof interval.unref === "function";
1488
+ }
1489
+ //#endregion
1490
+ //#region src/cli/reporters/progress/index.ts
1491
+ function createCliProgressReporter(options) {
1492
+ if (!options.isTty) return {
1493
+ dispose: () => {},
1494
+ reporter: createPlainProgressReporter({ write: options.write }),
1495
+ write: options.write
1496
+ };
1497
+ const summary = createSummaryProgressReporter({
1498
+ color: options.color,
1499
+ columns: options.columns,
1500
+ cwd: options.cwd,
1501
+ spinnerFrames: cliSpinners.dots.frames
1502
+ });
1503
+ const renderer = createTtyProgressRenderer({
1504
+ clearInterval: (handle) => globalThis.clearInterval(handle),
1505
+ createInterval: (callback, intervalMs) => globalThis.setInterval(() => {
1506
+ summary.tick();
1507
+ callback();
1508
+ }, intervalMs),
1509
+ getRows: summary.getRows,
1510
+ intervalMs: 120,
1511
+ write: options.write
1512
+ });
1513
+ const reporter = createRenderingProgressReporter(summary, renderer);
1514
+ return {
1515
+ dispose: renderer.finish,
1516
+ reporter,
1517
+ write: renderer.write
1518
+ };
1519
+ }
1520
+ function createRenderingProgressReporter(summary, renderer) {
1521
+ return {
1522
+ onDiagnostic: (payload) => {
1523
+ summary.onDiagnostic?.(payload);
1524
+ renderer.render();
1525
+ },
1526
+ onFileEnd: (payload) => {
1527
+ summary.onFileEnd?.(payload);
1528
+ renderer.render();
1529
+ },
1530
+ onFileStart: (payload) => {
1531
+ summary.onFileStart?.(payload);
1532
+ renderer.render();
1533
+ },
1534
+ onRuleEnd: (payload) => {
1535
+ summary.onRuleEnd?.(payload);
1536
+ renderer.render();
1537
+ },
1538
+ onRuleStart: (payload) => {
1539
+ summary.onRuleStart?.(payload);
1540
+ renderer.render();
1541
+ },
1542
+ onRunEnd: (payload) => {
1543
+ summary.onRunEnd?.(payload);
1544
+ renderer.render();
1545
+ },
1546
+ onRunStart: (payload) => {
1547
+ summary.onRunStart?.(payload);
1548
+ renderer.start();
1549
+ },
1550
+ onTargetEnd: (payload) => {
1551
+ summary.onTargetEnd?.(payload);
1552
+ renderer.render();
1553
+ },
1554
+ onTargetStart: (payload) => {
1555
+ summary.onTargetStart?.(payload);
1556
+ renderer.render();
1557
+ },
1558
+ onUsage: (payload) => {
1559
+ summary.onUsage?.(payload);
1560
+ renderer.render();
1561
+ }
1562
+ };
1563
+ }
1564
+ //#endregion
1565
+ //#region src/cli/cli.ts
1566
+ async function executeCli(argv, io) {
1567
+ const cli = cac("alint");
1568
+ const setupNoInteractive = argv.includes("-N") || argv.includes("--no-interactive");
1569
+ let pendingResult;
1570
+ cli.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("--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();
1571
+ cli.command("setup", "Write alint provider configuration").option("--local", "Write project-local config").option("-N, --no-interactive", "Disable interactive setup").option("--provider-endpoint <endpoint>", "Provider endpoint").option("--provider-model <model>", "Provider model").option("--provider-header <Key=Value>", "Provider header").action((options) => {
1572
+ pendingResult = runSetupCommand({
1573
+ ...options,
1574
+ noInteractive: setupNoInteractive
1575
+ }, io);
1576
+ return pendingResult;
1577
+ });
1578
+ cli.command("[...files]", "Run alint").action((files = [], options) => {
1579
+ pendingResult = runDefaultCommand(files, options, io);
1580
+ return pendingResult;
1581
+ });
1582
+ const restoreConsole = interceptConsoleOutput(shouldCaptureHelp(argv) ? io.stdout : io.stderr);
1583
+ try {
1584
+ cli.parse(argv);
1585
+ return await (pendingResult ?? Promise.resolve(0));
1586
+ } finally {
1587
+ restoreConsole();
1588
+ }
1589
+ }
1590
+ async function assertConfigExists(cwd, configPath) {
1591
+ const resolvedConfigPath = resolve(cwd, configPath);
1592
+ try {
1593
+ if (!(await stat(resolvedConfigPath)).isFile()) throw new Error(`Config file "${configPath}" is not a file.`);
1594
+ } catch (error) {
1595
+ if (isNodeError(error) && error.code === "ENOENT") throw new Error(`Config file "${configPath}" does not exist.`);
1596
+ throw error;
1597
+ }
1598
+ }
1599
+ function createSetupConfig(providerEndpoint, options) {
1600
+ const models = toArray(options.providerModel).map((model) => ({
1601
+ id: model,
1602
+ name: model
1603
+ }));
1604
+ return {
1605
+ providers: [{
1606
+ endpoint: providerEndpoint,
1607
+ headers: parseHeaders(toArray(options.providerHeader)),
1608
+ id: providerEndpoint,
1609
+ models,
1610
+ type: "openai-compatible"
1611
+ }],
1612
+ version: 1
1613
+ };
1614
+ }
1615
+ function formatRunError(error, color) {
1616
+ return `${color ? c.red("error") : "error"} ${formatRunErrorContext(error)}\n Rule running failed due to ${error.failure?.message ?? error.message}\n`;
1617
+ }
1618
+ function formatRunErrorContext(error) {
1619
+ const failure = error.failure;
1620
+ if (!failure) return "alint run failed";
1621
+ const target = failure.target ? failure.target.name ? `${failure.target.kind} ${failure.target.name}` : failure.target.kind : void 0;
1622
+ return [
1623
+ failure.filePath,
1624
+ target,
1625
+ failure.ruleId
1626
+ ].filter(Boolean).join(" > ");
1627
+ }
1628
+ function interceptConsoleOutput(stdout) {
1629
+ const cliConsole = globalThis.console;
1630
+ const originalConsoleDebug = cliConsole.debug;
1631
+ const originalConsoleDir = cliConsole.dir;
1632
+ const originalConsoleInfo = console.info;
1633
+ const originalConsoleLog = cliConsole.log;
1634
+ const writeConsoleLine = (...args) => {
1635
+ stdout.write(`${args.map(String).join(" ")}\n`);
1636
+ };
1637
+ const writeConsoleDir = (item, options) => {
1638
+ stdout.write(`${inspect(item, options)}\n`);
1639
+ };
1640
+ cliConsole.debug = writeConsoleLine;
1641
+ cliConsole.dir = writeConsoleDir;
1642
+ console.info = writeConsoleLine;
1643
+ cliConsole.log = writeConsoleLine;
1644
+ return () => {
1645
+ cliConsole.debug = originalConsoleDebug;
1646
+ cliConsole.dir = originalConsoleDir;
1647
+ console.info = originalConsoleInfo;
1648
+ cliConsole.log = originalConsoleLog;
1649
+ };
1650
+ }
1651
+ function isNodeError(error) {
1652
+ return error instanceof Error && "code" in error;
1653
+ }
1654
+ function isNoInteractive(options) {
1655
+ return options.noInteractive === true;
1656
+ }
1657
+ function parseHeaders(headers) {
1658
+ if (headers.length === 0) return;
1659
+ const parsedHeaders = {};
1660
+ for (const header of headers) {
1661
+ const separatorIndex = header.indexOf("=");
1662
+ if (separatorIndex <= 0) throw new Error(`Invalid provider header "${header}". Expected Key=Value.`);
1663
+ parsedHeaders[header.slice(0, separatorIndex)] = header.slice(separatorIndex + 1);
1664
+ }
1665
+ return parsedHeaders;
1666
+ }
1667
+ function parsePositiveIntegerOption(value, label) {
1668
+ if (value === void 0) return;
1669
+ const parsed = Number(value);
1670
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${label} must be a positive integer.`);
1671
+ return parsed;
1672
+ }
1673
+ function resolveRunnerConfig(setupConfig, config, options) {
1674
+ const fileConcurrency = parsePositiveIntegerOption(options.fileConcurrency, "--file-concurrency");
1675
+ const ruleConcurrency = parsePositiveIntegerOption(options.ruleConcurrency, "--rule-concurrency");
1676
+ const timeoutMs = parsePositiveIntegerOption(options.timeoutMs, "--timeout-ms");
1677
+ const runner = {
1678
+ ...setupConfig.runner ?? {},
1679
+ ...config.runner ?? {},
1680
+ fileConcurrency: fileConcurrency ?? config.runner?.fileConcurrency ?? setupConfig.runner?.fileConcurrency,
1681
+ ruleConcurrency: ruleConcurrency ?? config.runner?.ruleConcurrency ?? setupConfig.runner?.ruleConcurrency,
1682
+ timeoutMs: timeoutMs ?? config.runner?.timeoutMs ?? setupConfig.runner?.timeoutMs
1683
+ };
1684
+ return Object.values(runner).some((value) => value !== void 0) ? runner : void 0;
1685
+ }
1686
+ async function runDefaultCommand(files, options, io) {
1687
+ if (options.config) await assertConfigExists(io.cwd, options.config);
1688
+ const globalSetupConfigPath = getGlobalSetupConfigPath(io.env ?? process.env);
1689
+ const projectSetupConfigPath = getProjectSetupConfigPath(io.cwd);
1690
+ const [globalSetupConfig, projectSetupConfig, config] = await Promise.all([
1691
+ loadSetupConfig(globalSetupConfigPath),
1692
+ loadSetupConfig(projectSetupConfigPath),
1693
+ loadAlintConfig(io.cwd, options.config)
1694
+ ]);
1695
+ const setupConfig = mergeSetupConfigs(globalSetupConfig, projectSetupConfig);
1696
+ const runner = resolveRunnerConfig(setupConfig, config, options);
1697
+ const progress = shouldEnableProgress(options, io) ? createCliProgressReporter({
1698
+ color: io.stderr.isTTY === true,
1699
+ columns: io.stderr.columns ?? 80,
1700
+ cwd: io.cwd,
1701
+ isTty: io.stderr.isTTY === true,
1702
+ write: (chunk) => io.stderr.write(chunk)
1703
+ }) : void 0;
1704
+ const restoreProgressConsole = progress ? interceptConsoleOutput({ write: progress.write }) : void 0;
1705
+ let result;
1706
+ try {
1707
+ result = await runAlint({
1708
+ config,
1709
+ cwd: io.cwd,
1710
+ files,
1711
+ modelOverride: options.model,
1712
+ progress: progress?.reporter,
1713
+ runner,
1714
+ setupConfig
1715
+ });
1716
+ } catch (error) {
1717
+ restoreProgressConsole?.();
1718
+ progress?.dispose();
1719
+ if (error instanceof AlintRunError) {
1720
+ io.stderr.write(formatRunError(error, io.stderr.isTTY === true));
1721
+ return 2;
1722
+ }
1723
+ throw error;
1724
+ }
1725
+ restoreProgressConsole?.();
1726
+ progress?.dispose();
1727
+ io.stdout.write(formatDiagnostics(options.format, result, { color: io.stdout.isTTY === true }));
1728
+ return result.diagnostics.length > 0 ? 1 : 0;
1729
+ }
1730
+ async function runSetupCommand(options, io) {
1731
+ if (!options.providerEndpoint) {
1732
+ if (!isNoInteractive(options)) {
1733
+ io.stderr.write("interactive setup is not implemented yet. Use -N/--no-interactive with --provider-endpoint.\n");
1734
+ return 2;
1735
+ }
1736
+ io.stderr.write("setup requires --provider-endpoint in --no-interactive mode.\n");
1737
+ return 2;
1738
+ }
1739
+ const setupConfigPath = options.local ? getProjectSetupConfigPath(io.cwd) : getGlobalSetupConfigPath(io.env ?? process.env);
1740
+ await writeSetupConfig(setupConfigPath, mergeSetupConfigs(await loadSetupConfig(setupConfigPath), createSetupConfig(options.providerEndpoint, options)));
1741
+ return 0;
1742
+ }
1743
+ function shouldCaptureHelp(argv) {
1744
+ return argv.includes("--help") || argv.includes("-h");
1745
+ }
1746
+ function shouldEnableProgress(options, io) {
1747
+ if (options.progress !== void 0) return options.progress;
1748
+ return options.format === "stylish" && io.stderr.isTTY === true;
1749
+ }
1750
+ function toArray(value) {
1751
+ if (value === void 0) return [];
1752
+ return (Array.isArray(value) ? value : [value]).filter((item) => typeof item === "string");
1753
+ }
1754
+ //#endregion
1755
+ export { mergeSetupConfigs as _, runAlint as a, getGlobalSetupConfigPath as b, createSourceRuntime as c, resolveModel as d, buildRuleRegistry as f, loadSetupConfig as g, emptySetupConfig as h, formatJson as i, sliceLines as l, writeSetupConfig as m, formatDiagnostics as n, extractJsSourceUnits as o, loadAlintConfig as p, formatStylish as r, createSourceFile as s, executeCli as t, sliceRange as u, parseSetupConfigToml as v, getProjectSetupConfigPath as x, stringifySetupConfigToml as y };