@alint-js/cli 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -6
- package/dist/bin/index.mjs +1 -1
- package/dist/{cli-Dey9TSc8.mjs → cli-CwuIPM7m.mjs} +190 -61
- package/dist/index.mjs +1 -1
- package/package.json +4 -8
package/README.md
CHANGED
|
@@ -274,6 +274,41 @@ Use `alint plugin install` after adding or changing plugin sources.
|
|
|
274
274
|
- **Package:** `@scope/alint-plugin-example1@1.2.3` downloads that exact version into `.alint/plugins/store`, verifies its integrity, and loads its root export.
|
|
275
275
|
- **Local:** `./plugins/my-plugin` or an absolute directory loads the current package root export in place. `alint` checks that the package and entry exist and that the entry stays inside the package directory. It does not build the plugin or install the plugin's dependencies.
|
|
276
276
|
|
|
277
|
+
#### Declarative local rule plugins
|
|
278
|
+
|
|
279
|
+
For simple prompt-backed rules, a local plugin directory can omit `package.json` and contain one or more `rule.alint.*` files:
|
|
280
|
+
|
|
281
|
+
```toml
|
|
282
|
+
# rules/architecture/semantic/rule.alint.toml
|
|
283
|
+
name = "semantic-boundary"
|
|
284
|
+
builtInAgent = "basic-structured"
|
|
285
|
+
instruction = """
|
|
286
|
+
Find semantic boundary problems in the reviewed source.
|
|
287
|
+
Report concrete findings with line numbers and short suggestions.
|
|
288
|
+
"""
|
|
289
|
+
includeFiles = [ "src/**/*.py" ]
|
|
290
|
+
excludeFiles = [
|
|
291
|
+
"**/*_test.py",
|
|
292
|
+
"**/vendor/**",
|
|
293
|
+
]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Reference the directory through the same static `plugins` table:
|
|
297
|
+
|
|
298
|
+
```toml
|
|
299
|
+
[[config.group]]
|
|
300
|
+
files = [ "src/**/*.py" ]
|
|
301
|
+
language = "text/plain"
|
|
302
|
+
|
|
303
|
+
[config.group.plugins]
|
|
304
|
+
arch = "./rules/architecture"
|
|
305
|
+
|
|
306
|
+
[config.group.rules]
|
|
307
|
+
"arch/semantic-boundary" = "warn"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`name` becomes the local rule id. `builtInAgent` can be `basic-structured` for a prompt-only structured-output rule or `basic-coding-agent` for a small built-in agent with filesystem tools. `includeFiles` and `excludeFiles` define where diagnostics may be reported; they do not limit what `basic-coding-agent` can inspect.
|
|
311
|
+
|
|
277
312
|
Run the install command again after changing a source string, moving a local directory, or changing its symlink target. Changes inside the same local directory are loaded by the next CLI process without reinstalling.
|
|
278
313
|
|
|
279
314
|
Local plugins execute as trusted Node.js code. Directory containment checks validate the installed source; they are not a sandbox.
|
|
@@ -386,11 +421,7 @@ import { defineRule } from '@alint-js/core'
|
|
|
386
421
|
|
|
387
422
|
export const checkFunctionRule = defineRule({
|
|
388
423
|
create: ctx => ({
|
|
389
|
-
async
|
|
390
|
-
if (target.kind !== 'function') {
|
|
391
|
-
return
|
|
392
|
-
}
|
|
393
|
-
|
|
424
|
+
async onTargetFunction(target) {
|
|
394
425
|
const model = await ctx.model({ capabilities: ['tool-call'], size: 'small' })
|
|
395
426
|
|
|
396
427
|
ctx.report({
|
|
@@ -432,7 +463,7 @@ Rule authors can opt out of caching when a rule depends on external state:
|
|
|
432
463
|
defineRule({
|
|
433
464
|
cache: false,
|
|
434
465
|
create: ctx => ({
|
|
435
|
-
|
|
466
|
+
onTargetFile(target) {
|
|
436
467
|
// Always reruns.
|
|
437
468
|
},
|
|
438
469
|
}),
|
package/dist/bin/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { inspect } from "node:util";
|
|
3
3
|
import { cac } from "cac";
|
|
4
|
+
import { appendFile, mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
4
5
|
import { getGlobalSetupConfigPath, getProjectSetupConfigPath, getStatsDir, installStaticPlugins, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, writeSetupConfig } from "@alint-js/config";
|
|
5
|
-
import { AlintRunError, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForFile, runAlint } from "@alint-js/core";
|
|
6
|
+
import { AlintRunError, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, runAlint } from "@alint-js/core";
|
|
6
7
|
import { isAbsolute, join, relative, resolve } from "pathe";
|
|
7
8
|
import { getBorderCharacters, table } from "table";
|
|
8
9
|
import { errorMessageFrom } from "@moeru/std/error";
|
|
9
|
-
import { appendFile, mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
10
10
|
import c, { createColors } from "tinyrainbow";
|
|
11
11
|
import cliSpinners from "cli-spinners";
|
|
12
12
|
import { relative as relative$1 } from "node:path";
|
|
@@ -14,7 +14,7 @@ import Gitignore from "gitignore-fs";
|
|
|
14
14
|
import { Minimatch, minimatch } from "minimatch";
|
|
15
15
|
import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
|
|
16
16
|
//#region package.json
|
|
17
|
-
var version = "0.0.
|
|
17
|
+
var version = "0.0.23";
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/cli/commands/command.ts
|
|
20
20
|
function defineCommand(node) {
|
|
@@ -200,6 +200,7 @@ function shouldSkipOptionValue(arg) {
|
|
|
200
200
|
"--rule-concurrency",
|
|
201
201
|
"--since",
|
|
202
202
|
"--timeout-ms",
|
|
203
|
+
"-c",
|
|
203
204
|
"-f",
|
|
204
205
|
"-l"
|
|
205
206
|
].includes(arg);
|
|
@@ -207,15 +208,17 @@ function shouldSkipOptionValue(arg) {
|
|
|
207
208
|
//#endregion
|
|
208
209
|
//#region src/cli/commands/config/inspect.ts
|
|
209
210
|
const inspect$2 = defineCommand({
|
|
210
|
-
action: (context,
|
|
211
|
-
arguments: "<
|
|
212
|
-
description: "Inspect resolved config for a file",
|
|
211
|
+
action: (context, path, options) => runConfigInspectCommand(path, options.config, context.io),
|
|
212
|
+
arguments: "<path>",
|
|
213
|
+
description: "Inspect resolved config for a file or directory",
|
|
213
214
|
name: "inspect"
|
|
214
215
|
});
|
|
215
|
-
async function runConfigInspectCommand(
|
|
216
|
+
async function runConfigInspectCommand(path, configPath, io) {
|
|
216
217
|
const config = await loadAlintConfig(io.cwd, configPath);
|
|
217
|
-
const
|
|
218
|
-
|
|
218
|
+
const targetPath = resolve(io.cwd, path);
|
|
219
|
+
const isDirectory = await stat(targetPath).then((stats) => stats.isDirectory()).catch(() => false);
|
|
220
|
+
const result = isDirectory ? resolveConfigForDirectory(targetPath, config, { cwd: io.cwd }) : resolveConfigForFile(targetPath, config, { cwd: io.cwd });
|
|
221
|
+
io.stdout.write(`${isDirectory ? "directory" : "file"}: ${path}\n`);
|
|
219
222
|
io.stdout.write(`ignored: ${result.ignored ? "yes" : "no"}\n`);
|
|
220
223
|
io.stdout.write("matched:\n");
|
|
221
224
|
for (const item of result.matched) io.stdout.write(` - ${item.name ?? "<anonymous>"}\n`);
|
|
@@ -258,7 +261,8 @@ function createProviderId(endpoint, existingIds) {
|
|
|
258
261
|
let base = "provider";
|
|
259
262
|
try {
|
|
260
263
|
const url = new URL(endpoint);
|
|
261
|
-
|
|
264
|
+
const source = findProviderSetupSourceByEndpoint(url);
|
|
265
|
+
base = normalizeProviderIdBase(source?.defaultProviderId ?? source?.label ?? url.hostname);
|
|
262
266
|
} catch {
|
|
263
267
|
base = "provider";
|
|
264
268
|
}
|
|
@@ -362,6 +366,9 @@ function formatTable(rows) {
|
|
|
362
366
|
drawHorizontalLine: () => false
|
|
363
367
|
});
|
|
364
368
|
}
|
|
369
|
+
function normalizeProviderIdBase(value) {
|
|
370
|
+
return value.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase() || "provider";
|
|
371
|
+
}
|
|
365
372
|
//#endregion
|
|
366
373
|
//#region src/cli/commands/config/setup-config.ts
|
|
367
374
|
async function loadMergedSetupConfig(io) {
|
|
@@ -541,8 +548,9 @@ function formatJson(result) {
|
|
|
541
548
|
const colors$2 = createColors({ force: true });
|
|
542
549
|
function formatStylish(input, options = {}) {
|
|
543
550
|
const diagnostics = Array.isArray(input) ? input : input.diagnostics;
|
|
544
|
-
const
|
|
545
|
-
|
|
551
|
+
const result = Array.isArray(input) ? void 0 : input;
|
|
552
|
+
const style = createStyle(options.color === true);
|
|
553
|
+
if (diagnostics.length === 0) return result?.execution?.cached ? `${formatSummary(diagnostics, result, style)}\n` : "";
|
|
546
554
|
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
547
555
|
for (const diagnostic of diagnostics) {
|
|
548
556
|
const fileDiagnostics = diagnosticsByFile.get(diagnostic.filePath);
|
|
@@ -553,7 +561,6 @@ function formatStylish(input, options = {}) {
|
|
|
553
561
|
diagnosticsByFile.set(diagnostic.filePath, [diagnostic]);
|
|
554
562
|
}
|
|
555
563
|
const lines = [];
|
|
556
|
-
const style = createStyle(options.color === true);
|
|
557
564
|
for (const [filePath, fileDiagnostics] of diagnosticsByFile) {
|
|
558
565
|
lines.push(style.file(filePath));
|
|
559
566
|
for (const diagnostic of fileDiagnostics) {
|
|
@@ -564,9 +571,12 @@ function formatStylish(input, options = {}) {
|
|
|
564
571
|
}
|
|
565
572
|
lines.push("");
|
|
566
573
|
}
|
|
567
|
-
lines.push("", formatSummary(diagnostics,
|
|
574
|
+
lines.push("", formatSummary(diagnostics, result, style));
|
|
568
575
|
return `${lines.join("\n")}\n`;
|
|
569
576
|
}
|
|
577
|
+
function countCachedDiagnostics(diagnostics, severity) {
|
|
578
|
+
return diagnostics.filter((diagnostic) => diagnostic.severity === severity && diagnostic.cached === true).length;
|
|
579
|
+
}
|
|
570
580
|
function countDiagnostics$2(diagnostics, severity) {
|
|
571
581
|
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
572
582
|
}
|
|
@@ -588,13 +598,32 @@ function createStyle(color) {
|
|
|
588
598
|
warning: colors$2.yellow
|
|
589
599
|
};
|
|
590
600
|
}
|
|
591
|
-
function
|
|
601
|
+
function formatDiagnosticCount(total, cached, label) {
|
|
602
|
+
return `${total} ${label}${cached > 0 ? ` (${cached} cached)` : ""}`;
|
|
603
|
+
}
|
|
604
|
+
function formatExecutionSummary(execution) {
|
|
605
|
+
if (!execution || execution.cached === 0) return;
|
|
606
|
+
return `${execution.cached}/${execution.planned} cached`;
|
|
607
|
+
}
|
|
608
|
+
function formatSummary(diagnostics, result, style) {
|
|
592
609
|
const warnCount = countDiagnostics$2(diagnostics, "warn");
|
|
593
610
|
const errorCount = countDiagnostics$2(diagnostics, "error");
|
|
594
|
-
const
|
|
595
|
-
const
|
|
611
|
+
const cachedWarnCount = countCachedDiagnostics(diagnostics, "warn");
|
|
612
|
+
const cachedErrorCount = countCachedDiagnostics(diagnostics, "error");
|
|
613
|
+
const tokens = result === void 0 ? void 0 : formatTokenSummary(result);
|
|
614
|
+
const problemSummary = [style.warning(formatDiagnosticCount(warnCount, cachedWarnCount, "warn")), style.error(formatDiagnosticCount(errorCount, cachedErrorCount, "error"))].join(" / ");
|
|
596
615
|
if (tokens === void 0) return problemSummary;
|
|
597
|
-
|
|
616
|
+
const execution = formatExecutionSummary(result?.execution);
|
|
617
|
+
return [
|
|
618
|
+
problemSummary,
|
|
619
|
+
style.summaryToken(tokens),
|
|
620
|
+
execution
|
|
621
|
+
].filter((segment) => segment !== void 0).join(" | ");
|
|
622
|
+
}
|
|
623
|
+
function formatTokenSummary(result) {
|
|
624
|
+
const liveTokens = result.usage.totalTokens.toLocaleString("en-US");
|
|
625
|
+
const cachedTokens = result.usage.cached?.totalTokens ?? 0;
|
|
626
|
+
return `${liveTokens} tokens${cachedTokens > 0 ? ` (${cachedTokens.toLocaleString("en-US")} cached)` : ""}`;
|
|
598
627
|
}
|
|
599
628
|
function identity(value) {
|
|
600
629
|
return value;
|
|
@@ -635,7 +664,7 @@ function countDiagnostics$1(diagnostics, severity) {
|
|
|
635
664
|
//#region src/cli/reporters/progress/summary.ts
|
|
636
665
|
const colors$1 = createColors({ force: true });
|
|
637
666
|
function createSummaryProgressReporter(options) {
|
|
638
|
-
const now =
|
|
667
|
+
const now = Date.now;
|
|
639
668
|
const state = {
|
|
640
669
|
cached: 0,
|
|
641
670
|
completed: 0,
|
|
@@ -983,10 +1012,10 @@ var NoFilesFoundError = class extends Error {
|
|
|
983
1012
|
this.pattern = pattern;
|
|
984
1013
|
}
|
|
985
1014
|
};
|
|
986
|
-
async function
|
|
1015
|
+
async function findLintTargets(options) {
|
|
987
1016
|
const { config, cwd, errorOnUnmatchedPattern = true, globInputPaths = true, inputs } = options;
|
|
988
1017
|
const gitignore = shouldFilterGitignoredFiles(config) ? new Gitignore() : void 0;
|
|
989
|
-
const
|
|
1018
|
+
const targets = await resolveInputFiles({
|
|
990
1019
|
config,
|
|
991
1020
|
cwd,
|
|
992
1021
|
errorOnUnmatchedPattern,
|
|
@@ -994,13 +1023,16 @@ async function findFiles(options) {
|
|
|
994
1023
|
globInputPaths,
|
|
995
1024
|
inputs: inputs.length > 0 ? inputs : ["."]
|
|
996
1025
|
});
|
|
997
|
-
if (!gitignore ||
|
|
1026
|
+
if (!gitignore || targets.files.length === 0) return targets;
|
|
998
1027
|
const lintFiles = [];
|
|
999
|
-
for (const file of
|
|
1028
|
+
for (const file of targets.files) {
|
|
1000
1029
|
if (await gitignore.ignores(resolve(cwd, file))) continue;
|
|
1001
1030
|
lintFiles.push(file);
|
|
1002
1031
|
}
|
|
1003
|
-
return
|
|
1032
|
+
return {
|
|
1033
|
+
directories: targets.directories,
|
|
1034
|
+
files: lintFiles
|
|
1035
|
+
};
|
|
1004
1036
|
}
|
|
1005
1037
|
function collectGlobalIgnorePatterns(config) {
|
|
1006
1038
|
return normalizeConfig(config).flatMap((item) => isGlobalIgnoreItem(item) ? [...item.ignores] : []);
|
|
@@ -1041,6 +1073,7 @@ async function resolveInputFiles(options) {
|
|
|
1041
1073
|
const hasFilePatterns = hasDiscoveryFilePatterns(options.config);
|
|
1042
1074
|
const ignoredPatterns = collectGlobalIgnorePatterns(options.config);
|
|
1043
1075
|
const candidates = [];
|
|
1076
|
+
const directories = [];
|
|
1044
1077
|
for (const input of options.inputs) {
|
|
1045
1078
|
const path = resolve(options.cwd, input);
|
|
1046
1079
|
const stats = await statPath(path);
|
|
@@ -1049,12 +1082,13 @@ async function resolveInputFiles(options) {
|
|
|
1049
1082
|
continue;
|
|
1050
1083
|
}
|
|
1051
1084
|
if (stats?.isDirectory()) {
|
|
1052
|
-
if (!hasFilePatterns) continue;
|
|
1053
1085
|
if (await shouldPruneDirectory(path, {
|
|
1054
1086
|
cwd: options.cwd,
|
|
1055
1087
|
gitignore: options.gitignore,
|
|
1056
1088
|
ignoredPatterns
|
|
1057
1089
|
})) continue;
|
|
1090
|
+
directories.push(input);
|
|
1091
|
+
if (!hasFilePatterns) continue;
|
|
1058
1092
|
const directoryFiles = (await walkFiles(path, {
|
|
1059
1093
|
cwd: options.cwd,
|
|
1060
1094
|
gitignore: options.gitignore,
|
|
@@ -1081,7 +1115,10 @@ async function resolveInputFiles(options) {
|
|
|
1081
1115
|
}
|
|
1082
1116
|
if (options.errorOnUnmatchedPattern) throw new NoFilesFoundError(input, { globInputPaths: options.globInputPaths });
|
|
1083
1117
|
}
|
|
1084
|
-
return
|
|
1118
|
+
return {
|
|
1119
|
+
directories: [...new Set(directories)],
|
|
1120
|
+
files: [...new Set(candidates)]
|
|
1121
|
+
};
|
|
1085
1122
|
}
|
|
1086
1123
|
async function searchGlob(options) {
|
|
1087
1124
|
const root = resolve(options.cwd, getGlobParent(options.pattern));
|
|
@@ -1251,11 +1288,10 @@ function parseSince(time, now) {
|
|
|
1251
1288
|
const DEFAULT_RETENTION_MONTHS = 12;
|
|
1252
1289
|
const STATS_FILE_PATTERN = /^stats-(\d{4})-(\d{2})\.jsonl$/u;
|
|
1253
1290
|
function createJsonlStatsStore(options) {
|
|
1254
|
-
const now = options.clock ?? Date.now;
|
|
1255
1291
|
const retentionMonths = options.retentionMonths ?? DEFAULT_RETENTION_MONTHS;
|
|
1256
1292
|
return {
|
|
1257
|
-
query: (filter) => query(options.dir, filter ?? {}, now),
|
|
1258
|
-
record: (input) => record(options.dir, input, now(), retentionMonths)
|
|
1293
|
+
query: (filter) => query(options.dir, filter ?? {}, Date.now),
|
|
1294
|
+
record: (input) => record(options.dir, input, Date.now(), retentionMonths)
|
|
1259
1295
|
};
|
|
1260
1296
|
}
|
|
1261
1297
|
function accumulate(rows, key, inTok, outTok, totalTok, countRun) {
|
|
@@ -1534,9 +1570,9 @@ function isNodeError(error) {
|
|
|
1534
1570
|
async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
1535
1571
|
if (options.config) await assertConfigExists(io.cwd, options.config);
|
|
1536
1572
|
const [setupConfig, config] = await Promise.all([loadMergedSetupConfig(io), loadAlintConfig(io.cwd, options.config)]);
|
|
1537
|
-
let
|
|
1573
|
+
let lintTargets;
|
|
1538
1574
|
try {
|
|
1539
|
-
|
|
1575
|
+
lintTargets = await findLintTargets({
|
|
1540
1576
|
config,
|
|
1541
1577
|
cwd: io.cwd,
|
|
1542
1578
|
errorOnUnmatchedPattern: true,
|
|
@@ -1566,7 +1602,8 @@ async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
|
1566
1602
|
result = await runAlint({
|
|
1567
1603
|
config,
|
|
1568
1604
|
cwd: io.cwd,
|
|
1569
|
-
|
|
1605
|
+
directories: lintTargets.directories,
|
|
1606
|
+
files: lintTargets.files,
|
|
1570
1607
|
modelOverride: options.model,
|
|
1571
1608
|
outputLanguage: options.outputLanguage,
|
|
1572
1609
|
progress: mergeProgressReporters(progress?.reporter, statsCollector?.reporter),
|
|
@@ -1586,7 +1623,7 @@ async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
|
1586
1623
|
progress?.dispose();
|
|
1587
1624
|
if (statsTarget && statsCollector) await writeRunStats(statsTarget, statsCollector, result, io.cwd);
|
|
1588
1625
|
io.stdout.write(formatDiagnostics(options.format, result, { color: io.stdout.isTTY === true }));
|
|
1589
|
-
return result.diagnostics.
|
|
1626
|
+
return result.diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
|
|
1590
1627
|
}
|
|
1591
1628
|
function shouldEnableProgress(options, io) {
|
|
1592
1629
|
if (options.progress !== void 0) return options.progress;
|
|
@@ -1641,7 +1678,7 @@ async function inspectOutputFile(io, file, options) {
|
|
|
1641
1678
|
io.stderr.write(`${errorMessageFrom$1(error)}\n`);
|
|
1642
1679
|
return 2;
|
|
1643
1680
|
}
|
|
1644
|
-
return parsed.diagnostics.
|
|
1681
|
+
return parsed.diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
|
|
1645
1682
|
}
|
|
1646
1683
|
function isDiagnostic(value) {
|
|
1647
1684
|
if (!isRecord(value)) return false;
|
|
@@ -1715,6 +1752,53 @@ const plugin = defineCommand({
|
|
|
1715
1752
|
//#region src/cli/commands/setup/interactive.ts
|
|
1716
1753
|
const nonTtyMessage = "interactive setup requires a TTY. Use -N/--no-interactive with --provider-id and --provider-endpoint.\n";
|
|
1717
1754
|
const backValue = "__alint_back__";
|
|
1755
|
+
function applyDefaultAlias(config, target) {
|
|
1756
|
+
return {
|
|
1757
|
+
...config,
|
|
1758
|
+
providers: config.providers.map((provider) => ({
|
|
1759
|
+
...provider,
|
|
1760
|
+
headers: provider.headers === void 0 ? void 0 : { ...provider.headers },
|
|
1761
|
+
models: provider.models.map((model) => {
|
|
1762
|
+
const aliasesWithoutDefault = (model.aliases ?? []).filter((alias) => alias !== "default");
|
|
1763
|
+
const aliases = provider.id === target.providerId && model.id === target.modelId ? [...aliasesWithoutDefault, "default"] : aliasesWithoutDefault;
|
|
1764
|
+
return {
|
|
1765
|
+
...model,
|
|
1766
|
+
aliases: aliases.length > 0 ? aliases : void 0,
|
|
1767
|
+
capabilities: model.capabilities === void 0 ? void 0 : [...model.capabilities],
|
|
1768
|
+
defaultParams: model.defaultParams === void 0 ? void 0 : { ...model.defaultParams }
|
|
1769
|
+
};
|
|
1770
|
+
})
|
|
1771
|
+
}))
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
function createDefaultModelCandidates(config, newProviderId, newModelIds) {
|
|
1775
|
+
const candidates = [];
|
|
1776
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1777
|
+
const newModelIdSet = new Set(newModelIds);
|
|
1778
|
+
const allModels = config.providers.flatMap((provider) => provider.models.map((model) => ({
|
|
1779
|
+
isCurrentDefault: (model.aliases ?? []).includes("default"),
|
|
1780
|
+
isNew: provider.id === newProviderId && newModelIdSet.has(model.id),
|
|
1781
|
+
model,
|
|
1782
|
+
provider,
|
|
1783
|
+
value: createDefaultModelCandidateValue(provider.id, model.id)
|
|
1784
|
+
})));
|
|
1785
|
+
const addCandidate = (candidate) => {
|
|
1786
|
+
if (candidate === void 0 || seen.has(candidate.value)) return;
|
|
1787
|
+
seen.add(candidate.value);
|
|
1788
|
+
candidates.push({
|
|
1789
|
+
isCurrentDefault: candidate.isCurrentDefault,
|
|
1790
|
+
isNew: candidate.isNew,
|
|
1791
|
+
label: `${candidate.provider.id} / ${candidate.model.id}`,
|
|
1792
|
+
modelId: candidate.model.id,
|
|
1793
|
+
providerId: candidate.provider.id,
|
|
1794
|
+
value: candidate.value
|
|
1795
|
+
});
|
|
1796
|
+
};
|
|
1797
|
+
addCandidate(allModels.find((candidate) => candidate.isCurrentDefault));
|
|
1798
|
+
for (const modelId of newModelIds) addCandidate(allModels.find((candidate) => candidate.provider.id === newProviderId && candidate.model.id === modelId));
|
|
1799
|
+
for (const candidate of allModels) addCandidate(candidate);
|
|
1800
|
+
return candidates;
|
|
1801
|
+
}
|
|
1718
1802
|
function formatProbeModelsFailure(endpoint, error) {
|
|
1719
1803
|
const hint = endpoint.startsWith("https://localhost:11434") ? " Ollama usually uses http://localhost:11434/v1." : "";
|
|
1720
1804
|
return `Could not probe models: ${errorMessageFrom(error)}.${hint}`;
|
|
@@ -1786,9 +1870,8 @@ async function runInteractiveSetup(io) {
|
|
|
1786
1870
|
if (step === "providerId") {
|
|
1787
1871
|
const existingConfig = await loadSetupConfig(getConfigPath(io, draft.scope ?? "global"));
|
|
1788
1872
|
const providerId = await prompts.text({
|
|
1789
|
-
|
|
1790
|
-
message: "Provider id",
|
|
1791
|
-
placeholder: "Type .. to go back",
|
|
1873
|
+
initialValue: draft.providerId ?? createProviderId(draft.endpoint ?? "", new Set(existingConfig.providers.map((provider) => provider.id))),
|
|
1874
|
+
message: "Provider id (type .. to go back)",
|
|
1792
1875
|
validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider id is required."
|
|
1793
1876
|
});
|
|
1794
1877
|
if (prompts.isCancel(providerId)) return cancelPrompt();
|
|
@@ -1803,9 +1886,9 @@ async function runInteractiveSetup(io) {
|
|
|
1803
1886
|
}
|
|
1804
1887
|
if (step === "headers") {
|
|
1805
1888
|
const headerInput = await prompts.text({
|
|
1806
|
-
|
|
1807
|
-
message: "Headers",
|
|
1808
|
-
placeholder: "Authorization=Bearer token, X-Test=true
|
|
1889
|
+
initialValue: draft.headerInput,
|
|
1890
|
+
message: "Headers (leave empty to skip; type .. to go back)",
|
|
1891
|
+
placeholder: "Authorization=Bearer token, X-Test=true",
|
|
1809
1892
|
validate: (value) => {
|
|
1810
1893
|
if (isBackInput(value ?? "")) return;
|
|
1811
1894
|
try {
|
|
@@ -1841,26 +1924,69 @@ async function runInteractiveSetup(io) {
|
|
|
1841
1924
|
continue;
|
|
1842
1925
|
}
|
|
1843
1926
|
if (step === "defaultAlias") {
|
|
1844
|
-
const
|
|
1927
|
+
const defaultAliasAction = await prompts.select({
|
|
1845
1928
|
message: `Add alias "default" to ${draft.selectedModels?.[0]}?`,
|
|
1846
|
-
options: withBackOption([
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1929
|
+
options: withBackOption([
|
|
1930
|
+
{
|
|
1931
|
+
label: "Yes",
|
|
1932
|
+
value: "yes"
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
label: "No",
|
|
1936
|
+
value: "no"
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
label: "Select another",
|
|
1940
|
+
value: "selectAnother"
|
|
1941
|
+
}
|
|
1942
|
+
])
|
|
1853
1943
|
});
|
|
1854
|
-
if (prompts.isCancel(
|
|
1855
|
-
if (
|
|
1944
|
+
if (prompts.isCancel(defaultAliasAction)) return cancelPrompt();
|
|
1945
|
+
if (defaultAliasAction === backValue) {
|
|
1856
1946
|
step = "models";
|
|
1857
1947
|
continue;
|
|
1858
1948
|
}
|
|
1859
|
-
|
|
1949
|
+
if (defaultAliasAction === "selectAnother") {
|
|
1950
|
+
step = "selectDefaultModel";
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
draft.defaultAliasTarget = defaultAliasAction === "yes" ? {
|
|
1954
|
+
modelId: draft.selectedModels?.[0] ?? "",
|
|
1955
|
+
providerId: (draft.providerId ?? "").trim()
|
|
1956
|
+
} : void 0;
|
|
1957
|
+
step = "confirm";
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
if (step === "selectDefaultModel") {
|
|
1961
|
+
const existingConfig = await loadSetupConfig(getConfigPath(io, draft.scope ?? "global"));
|
|
1962
|
+
const draftProvider = createProviderConfig((draft.providerId ?? "").trim(), (draft.endpoint ?? "").trim(), draft.headers, draft.selectedModels ?? []);
|
|
1963
|
+
const candidates = createDefaultModelCandidates(mergeSetupConfigs(existingConfig, {
|
|
1964
|
+
providers: [draftProvider],
|
|
1965
|
+
version: 1
|
|
1966
|
+
}), draftProvider.id, draft.selectedModels ?? []);
|
|
1967
|
+
const defaultModel = await prompts.select({
|
|
1968
|
+
message: "Select default model",
|
|
1969
|
+
options: withBackOption(candidates.map((candidate) => ({
|
|
1970
|
+
hint: candidate.isCurrentDefault ? "current default" : candidate.isNew ? "new" : void 0,
|
|
1971
|
+
label: candidate.label,
|
|
1972
|
+
value: candidate.value
|
|
1973
|
+
})))
|
|
1974
|
+
});
|
|
1975
|
+
if (prompts.isCancel(defaultModel)) return cancelPrompt();
|
|
1976
|
+
if (defaultModel === backValue) {
|
|
1977
|
+
step = "defaultAlias";
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
const candidate = candidates.find((item) => item.value === defaultModel);
|
|
1981
|
+
if (candidate === void 0) return cancelPrompt();
|
|
1982
|
+
draft.defaultAliasTarget = {
|
|
1983
|
+
modelId: candidate.modelId,
|
|
1984
|
+
providerId: candidate.providerId
|
|
1985
|
+
};
|
|
1860
1986
|
step = "confirm";
|
|
1861
1987
|
continue;
|
|
1862
1988
|
}
|
|
1863
|
-
const nextProvider = createProviderConfig((draft.providerId ?? "").trim(), (draft.endpoint ?? "").trim(), draft.headers, draft.selectedModels ?? []
|
|
1989
|
+
const nextProvider = createProviderConfig((draft.providerId ?? "").trim(), (draft.endpoint ?? "").trim(), draft.headers, draft.selectedModels ?? []);
|
|
1864
1990
|
const confirmed = await prompts.select({
|
|
1865
1991
|
message: [
|
|
1866
1992
|
`Write ${draft.scope} setup config?`,
|
|
@@ -1883,10 +2009,11 @@ async function runInteractiveSetup(io) {
|
|
|
1883
2009
|
}
|
|
1884
2010
|
if (confirmed === "no") return cancelPrompt();
|
|
1885
2011
|
const configPath = getConfigPath(io, draft.scope ?? "global");
|
|
1886
|
-
|
|
2012
|
+
const mergedConfig = mergeSetupConfigs(await loadSetupConfig(configPath), {
|
|
1887
2013
|
providers: [nextProvider],
|
|
1888
2014
|
version: 1
|
|
1889
|
-
})
|
|
2015
|
+
});
|
|
2016
|
+
await writeSetupConfig(configPath, draft.defaultAliasTarget === void 0 ? mergedConfig : applyDefaultAlias(mergedConfig, draft.defaultAliasTarget));
|
|
1890
2017
|
prompts.outro(`Wrote ${configPath}`);
|
|
1891
2018
|
return 0;
|
|
1892
2019
|
}
|
|
@@ -1897,13 +2024,15 @@ function withBackOption(options) {
|
|
|
1897
2024
|
value: backValue
|
|
1898
2025
|
}];
|
|
1899
2026
|
}
|
|
1900
|
-
function
|
|
2027
|
+
function createDefaultModelCandidateValue(providerId, modelId) {
|
|
2028
|
+
return `${providerId}\u0000${modelId}`;
|
|
2029
|
+
}
|
|
2030
|
+
function createProviderConfig(providerId, endpoint, headers, modelIds) {
|
|
1901
2031
|
return {
|
|
1902
2032
|
endpoint,
|
|
1903
2033
|
headers,
|
|
1904
2034
|
id: providerId,
|
|
1905
|
-
models: modelIds.map((modelId
|
|
1906
|
-
aliases: index === 0 && addDefaultAlias ? ["default"] : void 0,
|
|
2035
|
+
models: modelIds.map((modelId) => ({
|
|
1907
2036
|
id: modelId,
|
|
1908
2037
|
name: modelId
|
|
1909
2038
|
})),
|
|
@@ -1927,9 +2056,9 @@ async function probeModelsWithSpinner(prompts, endpoint, headers) {
|
|
|
1927
2056
|
}
|
|
1928
2057
|
async function promptEndpoint(prompts, source) {
|
|
1929
2058
|
return prompts.text({
|
|
1930
|
-
|
|
1931
|
-
message: "Provider endpoint",
|
|
1932
|
-
placeholder:
|
|
2059
|
+
initialValue: source.defaultEndpoint,
|
|
2060
|
+
message: "Provider endpoint (type .. to go back)",
|
|
2061
|
+
placeholder: source.defaultEndpoint ?? "https://example.test/v1",
|
|
1933
2062
|
validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider endpoint is required."
|
|
1934
2063
|
});
|
|
1935
2064
|
}
|
|
@@ -2163,7 +2292,7 @@ async function executeCli(argv, io) {
|
|
|
2163
2292
|
pendingResult = result;
|
|
2164
2293
|
return result;
|
|
2165
2294
|
};
|
|
2166
|
-
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("--no-stats", "Do not record run stats for this run").option("--timeout-ms <ms>", "Rule execution timeout in milliseconds").version(version).help();
|
|
2295
|
+
cli.option("--no-cache", "Disable cache for this run").option("--cache-location <path>", "Path to the alint cache file or directory").option("-c, --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("--no-stats", "Do not record run stats for this run").option("--timeout-ms <ms>", "Rule execution timeout in milliseconds").version(version).help();
|
|
2167
2296
|
registerCommandTree(cli, commandTree, {
|
|
2168
2297
|
globalOptions,
|
|
2169
2298
|
interceptConsoleOutput,
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-
|
|
1
|
+
import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-CwuIPM7m.mjs";
|
|
2
2
|
import { ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated } from "@alint-js/config";
|
|
3
3
|
import { defineConfig } from "@alint-js/core";
|
|
4
4
|
export { defineConfig, executeCli, formatDiagnostics, formatJson, formatStylish, ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alint-js/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.23",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.mts",
|
|
@@ -25,18 +25,14 @@
|
|
|
25
25
|
"pathe": "^2.0.3",
|
|
26
26
|
"table": "^6.9.0",
|
|
27
27
|
"tinyrainbow": "^3.1.0",
|
|
28
|
-
"@alint-js/config": "0.0.
|
|
29
|
-
"@alint-js/core": "0.0.
|
|
28
|
+
"@alint-js/config": "0.0.23",
|
|
29
|
+
"@alint-js/core": "0.0.23"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@pnpm/find-workspace-dir": "^1000.1.5",
|
|
33
|
-
"@types/node": "^26.0.1",
|
|
34
33
|
"local-pkg": "^1.2.1",
|
|
35
34
|
"mlly": "^1.8.2",
|
|
36
|
-
"tinyexec": "^1.2.4"
|
|
37
|
-
"tsdown": "^0.22.3",
|
|
38
|
-
"typescript": "^6.0.3",
|
|
39
|
-
"vitest": "^4.1.9"
|
|
35
|
+
"tinyexec": "^1.2.4"
|
|
40
36
|
},
|
|
41
37
|
"scripts": {
|
|
42
38
|
"alint": "node ./dist/bin/index.mjs",
|