@alint-js/cli 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -17
- package/dist/bin/index.mjs +39 -2
- package/dist/{cli-cQihuHqx.mjs → cli-F44RJ972.mjs} +38 -60
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +1 -1
- package/dist/lsp-commands.d.mts +16 -0
- package/dist/lsp-commands.mjs +16 -0
- package/dist/{server-xx0w7UQL.mjs → server-JWv5WkGk.mjs} +100 -14
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -219,18 +219,17 @@ alint output inspect alint-output.json
|
|
|
219
219
|
|
|
220
220
|
### Editor Integration
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
> not reload it, and the run commands below are advertised but not yet implemented. Restart the
|
|
225
|
-
> server to pick up either kind of change.
|
|
222
|
+
`alint lsp` runs alint as a language server on stdin and stdout. Any editor with LSP support can
|
|
223
|
+
use it.
|
|
226
224
|
|
|
227
|
-
|
|
225
|
+
The server is cache-first. It publishes diagnostics that earlier runs stored, and it never calls a
|
|
226
|
+
model on its own, so opening a workspace costs nothing. A cold cache shows no diagnostics. Run
|
|
227
|
+
`alint` once to fill it.
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
run `alint` once to populate it.
|
|
229
|
+
Diagnostics appear when the editor opens the workspace, and they refresh when you save a file. The
|
|
230
|
+
server reloads `alint.config.ts` after it changes.
|
|
232
231
|
|
|
233
|
-
|
|
232
|
+
Configure the editor to start the command. In Neovim:
|
|
234
233
|
|
|
235
234
|
```lua
|
|
236
235
|
vim.lsp.config.alint = {
|
|
@@ -239,20 +238,23 @@ vim.lsp.config.alint = {
|
|
|
239
238
|
}
|
|
240
239
|
```
|
|
241
240
|
|
|
242
|
-
The server declares
|
|
243
|
-
|
|
241
|
+
The server declares three commands. `alint.runFile` and `alint.runWorkspace` start runs that call
|
|
242
|
+
models and spend tokens; both report progress and accept cancellation. `alint.clearCache` deletes
|
|
243
|
+
the cached diagnostics.
|
|
244
244
|
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
#### VS Code
|
|
246
|
+
|
|
247
|
+
The extension in `apps/vscode` starts the server and displays the diagnostics. It is not published
|
|
248
|
+
to the Marketplace yet. To run it from a checkout:
|
|
247
249
|
|
|
248
250
|
```bash
|
|
249
251
|
pnpm -F @alint-js/vscode build
|
|
250
|
-
code --extensionDevelopmentPath=apps/vscode /path/to/your/project
|
|
252
|
+
code --extensionDevelopmentPath=apps/vscode/dist /path/to/your/project
|
|
251
253
|
```
|
|
252
254
|
|
|
253
|
-
|
|
254
|
-
the workspace folder, then `PATH`. The workspace
|
|
255
|
-
|
|
255
|
+
The extension finds the alint executable in this order: the `alint.path` setting, then
|
|
256
|
+
`node_modules/.bin/alint` in the workspace folder, then `PATH`. The workspace installation takes
|
|
257
|
+
precedence, so the server and a terminal run use the same cache.
|
|
256
258
|
|
|
257
259
|
### Codex stop-gate plugin (optional)
|
|
258
260
|
|
package/dist/bin/index.mjs
CHANGED
|
@@ -1,17 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as executeCli } from "../cli-
|
|
2
|
+
import { t as executeCli } from "../cli-F44RJ972.mjs";
|
|
3
3
|
import process from "node:process";
|
|
4
|
+
//#region src/cli/runtime/interrupt.ts
|
|
5
|
+
/** Binds one graceful process interrupt to an abort signal for the active CLI run. */
|
|
6
|
+
function bindInterruptSignal(source) {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
/**
|
|
9
|
+
* Aborts active CLI work when the process receives its first interrupt signal.
|
|
10
|
+
*
|
|
11
|
+
* Triggering workflow:
|
|
12
|
+
*
|
|
13
|
+
* {@link bindInterruptSignal}
|
|
14
|
+
* -> `InterruptSignalSource.once('SIGINT' | 'SIGTERM')`
|
|
15
|
+
* -> `handleInterrupt`
|
|
16
|
+
* -> `AbortController.abort()`
|
|
17
|
+
*
|
|
18
|
+
* Upstream:
|
|
19
|
+
* - {@link bindInterruptSignal} registers this handler for `SIGINT` and `SIGTERM`.
|
|
20
|
+
*
|
|
21
|
+
* Downstream:
|
|
22
|
+
* - Aborts {@link InterruptSignalBinding.signal}, which the lint command passes to `runAlint`.
|
|
23
|
+
*/
|
|
24
|
+
const handleInterrupt = () => {
|
|
25
|
+
controller.abort();
|
|
26
|
+
};
|
|
27
|
+
source.once("SIGINT", handleInterrupt);
|
|
28
|
+
source.once("SIGTERM", handleInterrupt);
|
|
29
|
+
return {
|
|
30
|
+
dispose: () => {
|
|
31
|
+
source.off("SIGINT", handleInterrupt);
|
|
32
|
+
source.off("SIGTERM", handleInterrupt);
|
|
33
|
+
},
|
|
34
|
+
signal: controller.signal
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
4
38
|
//#region src/bin/index.ts
|
|
39
|
+
const interrupt = bindInterruptSignal(process);
|
|
5
40
|
executeCli(process.argv, {
|
|
6
41
|
cwd: process.cwd(),
|
|
7
42
|
stderr: process.stderr,
|
|
8
43
|
stdin: process.stdin,
|
|
9
44
|
stdout: process.stdout
|
|
10
|
-
}).then((exitCode) => {
|
|
45
|
+
}, { signal: interrupt.signal }).then((exitCode) => {
|
|
11
46
|
process.exitCode = exitCode;
|
|
12
47
|
}).catch((error) => {
|
|
13
48
|
process.stderr.write(`${formatError(error)}\n`);
|
|
14
49
|
process.exitCode = 2;
|
|
50
|
+
}).finally(() => {
|
|
51
|
+
interrupt.dispose();
|
|
15
52
|
});
|
|
16
53
|
function formatError(error) {
|
|
17
54
|
if (error instanceof Error) return error.message;
|
|
@@ -3,7 +3,7 @@ import { inspect } from "node:util";
|
|
|
3
3
|
import { cac } from "cac";
|
|
4
4
|
import { appendFile, chmod, mkdir, opendir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
5
|
import { addProviderModels, getGlobalSetupConfigPath, getProjectSetupConfigPath, getStatsDir, installStaticPlugins, isAcpModel, loadAlintConfig, loadAlintConfigWithMetadata, loadSetupConfig, mergeSetupConfigs, normalizeSetupConfig, pruneProviderModels, removeProviderModels, replaceSetupProvider, setProviderEndpoint, setProviderHeader, setStopGateConfig, unsetProviderHeader, writeSetupConfig } from "@alint-js/config";
|
|
6
|
-
import { AlintCachePersistenceError, AlintRunCancelledError, AlintRunError, benchmarkModels, defaultStopGateConfig, hasDiscoveryFilePatterns, matchesDiscoveryFile, maximumStopGateTimeoutMs, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, resolveStopGateConfig, runAlint, stableHash } from "@alint-js/core";
|
|
6
|
+
import { AlintCachePersistenceError, AlintRunCancelledError, AlintRunError, benchmarkModels, defaultStopGateConfig, hasDiscoveryFilePatterns, matchesDiscoveryFile, maximumStopGateTimeoutMs, normalizeConfig, normalizeRunnerCacheConfig, resolveConfigForDirectory, resolveConfigForFile, resolveStopGateConfig, runAlint, stableHash } from "@alint-js/core";
|
|
7
7
|
import { isAbsolute, join, relative, resolve } from "pathe";
|
|
8
8
|
import { errorMessageFrom } from "@moeru/std";
|
|
9
9
|
import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std/error";
|
|
@@ -24,7 +24,7 @@ import { Minimatch, minimatch } from "minimatch";
|
|
|
24
24
|
import fastStringTruncatedWidth from "fast-string-truncated-width";
|
|
25
25
|
import { formatDuration, intervalToDuration } from "date-fns";
|
|
26
26
|
//#region package.json
|
|
27
|
-
var version = "0.
|
|
27
|
+
var version = "0.7.1";
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region src/cli/output.ts
|
|
30
30
|
function escapeLineValue(value) {
|
|
@@ -2877,7 +2877,7 @@ function mergeRunnerConfigs(setupRunner, configRunner) {
|
|
|
2877
2877
|
};
|
|
2878
2878
|
return Object.values(runner).some((value) => value !== void 0) ? runner : void 0;
|
|
2879
2879
|
}
|
|
2880
|
-
/** The last `runner` block in the flattened config
|
|
2880
|
+
/** The last `runner` block in the flattened config takes precedence, key by key. */
|
|
2881
2881
|
function resolveConfigRunner(config) {
|
|
2882
2882
|
return normalizeConfig(config).reduce((merged, item) => item.runner ? {
|
|
2883
2883
|
...merged,
|
|
@@ -2915,7 +2915,9 @@ async function createRunSession(io, options = {}) {
|
|
|
2915
2915
|
const runtime = await (options.startModelAdapters ?? startModelAdapters)(fileSetupConfig, io);
|
|
2916
2916
|
const { setupConfig } = runtime;
|
|
2917
2917
|
let shutdown;
|
|
2918
|
+
const runner = mergeRunnerConfigs(setupConfig.runner, resolveConfigRunner(config));
|
|
2918
2919
|
return {
|
|
2920
|
+
cache: normalizeRunnerCacheConfig(runner?.cache, io.cwd),
|
|
2919
2921
|
config,
|
|
2920
2922
|
cwd: io.cwd,
|
|
2921
2923
|
defaultModel,
|
|
@@ -2943,7 +2945,7 @@ async function createRunSession(io, options = {}) {
|
|
|
2943
2945
|
signal: runOptions.signal
|
|
2944
2946
|
});
|
|
2945
2947
|
},
|
|
2946
|
-
runner
|
|
2948
|
+
runner,
|
|
2947
2949
|
setupConfig,
|
|
2948
2950
|
shutdown: () => shutdown ??= runtime.shutdown()
|
|
2949
2951
|
};
|
|
@@ -3496,7 +3498,7 @@ async function executeLint(options) {
|
|
|
3496
3498
|
outputLanguage: options.outputLanguage,
|
|
3497
3499
|
progress: mergeProgressReporters(options.progress, statsCollector?.reporter),
|
|
3498
3500
|
runner,
|
|
3499
|
-
signal: options.createSignal?.()
|
|
3501
|
+
signal: options.signal ?? options.createSignal?.()
|
|
3500
3502
|
});
|
|
3501
3503
|
} catch (error) {
|
|
3502
3504
|
if (error instanceof AlintRunError || error instanceof AlintRunCancelledError || error instanceof AlintCachePersistenceError) await persistStats(error.result);
|
|
@@ -4251,55 +4253,29 @@ function createCliProgressReporter(options) {
|
|
|
4251
4253
|
};
|
|
4252
4254
|
}
|
|
4253
4255
|
function createRenderingProgressReporter(summary, renderer) {
|
|
4256
|
+
/**
|
|
4257
|
+
* Resets the summary and starts its bounded TTY render interval.
|
|
4258
|
+
*
|
|
4259
|
+
* Triggering workflow:
|
|
4260
|
+
*
|
|
4261
|
+
* `runAlint`
|
|
4262
|
+
* -> `ProgressReporter.onPrepareStart`
|
|
4263
|
+
* -> `handlePrepareStart`
|
|
4264
|
+
* -> `TtyProgressRenderer.start`
|
|
4265
|
+
*
|
|
4266
|
+
* Upstream:
|
|
4267
|
+
* - `runAlint` emits `onPrepareStart` before source discovery.
|
|
4268
|
+
*
|
|
4269
|
+
* Downstream:
|
|
4270
|
+
* - Delegates state reset to `summary.onPrepareStart` and starts the renderer interval.
|
|
4271
|
+
*/
|
|
4272
|
+
const handlePrepareStart = (payload) => {
|
|
4273
|
+
summary.onPrepareStart?.(payload);
|
|
4274
|
+
renderer.start();
|
|
4275
|
+
};
|
|
4254
4276
|
return {
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
renderer.render();
|
|
4258
|
-
},
|
|
4259
|
-
onExecuteEnd: (payload) => {
|
|
4260
|
-
summary.onExecuteEnd?.(payload);
|
|
4261
|
-
renderer.render();
|
|
4262
|
-
},
|
|
4263
|
-
onExecuteStart: (payload) => {
|
|
4264
|
-
summary.onExecuteStart?.(payload);
|
|
4265
|
-
renderer.render();
|
|
4266
|
-
},
|
|
4267
|
-
onFileReady: (payload) => {
|
|
4268
|
-
summary.onFileReady?.(payload);
|
|
4269
|
-
renderer.render();
|
|
4270
|
-
},
|
|
4271
|
-
onJobEnd: (payload) => {
|
|
4272
|
-
summary.onJobEnd?.(payload);
|
|
4273
|
-
renderer.render();
|
|
4274
|
-
},
|
|
4275
|
-
onJobQueued: (payload) => {
|
|
4276
|
-
summary.onJobQueued?.(payload);
|
|
4277
|
-
renderer.render();
|
|
4278
|
-
},
|
|
4279
|
-
onJobRetry: (payload) => {
|
|
4280
|
-
summary.onJobRetry?.(payload);
|
|
4281
|
-
renderer.render();
|
|
4282
|
-
},
|
|
4283
|
-
onJobStart: (payload) => {
|
|
4284
|
-
summary.onJobStart?.(payload);
|
|
4285
|
-
renderer.render();
|
|
4286
|
-
},
|
|
4287
|
-
onPrepareEnd: (payload) => {
|
|
4288
|
-
summary.onPrepareEnd?.(payload);
|
|
4289
|
-
renderer.render();
|
|
4290
|
-
},
|
|
4291
|
-
onPrepareStart: (payload) => {
|
|
4292
|
-
summary.onPrepareStart?.(payload);
|
|
4293
|
-
renderer.start();
|
|
4294
|
-
},
|
|
4295
|
-
onRunEnd: (payload) => {
|
|
4296
|
-
summary.onRunEnd?.(payload);
|
|
4297
|
-
renderer.render();
|
|
4298
|
-
},
|
|
4299
|
-
onUsage: (payload) => {
|
|
4300
|
-
summary.onUsage?.(payload);
|
|
4301
|
-
renderer.render();
|
|
4302
|
-
}
|
|
4277
|
+
...summary,
|
|
4278
|
+
onPrepareStart: handlePrepareStart
|
|
4303
4279
|
};
|
|
4304
4280
|
}
|
|
4305
4281
|
//#endregion
|
|
@@ -4374,7 +4350,7 @@ const lint = defineCommand({
|
|
|
4374
4350
|
action: (context, files = [], options) => runLintCommand(files, {
|
|
4375
4351
|
...options,
|
|
4376
4352
|
outputLanguage: options.lang ?? context.globalOptions.outputLanguage
|
|
4377
|
-
}, context.io, context.interceptConsoleOutput),
|
|
4353
|
+
}, context.io, context.interceptConsoleOutput, context.signal),
|
|
4378
4354
|
alias: ["!"],
|
|
4379
4355
|
arguments: "[...files]",
|
|
4380
4356
|
default: true,
|
|
@@ -4394,7 +4370,7 @@ async function assertConfigExists(cwd, configPath) {
|
|
|
4394
4370
|
throw error;
|
|
4395
4371
|
}
|
|
4396
4372
|
}
|
|
4397
|
-
async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
4373
|
+
async function runLintCommand(files, options, io, interceptConsoleOutput, signal) {
|
|
4398
4374
|
if (options.dirty && files.length > 0) {
|
|
4399
4375
|
io.stderr.write("The --dirty option does not accept file arguments.\n");
|
|
4400
4376
|
return 2;
|
|
@@ -4444,7 +4420,8 @@ async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
|
4444
4420
|
outputLanguage: options.outputLanguage,
|
|
4445
4421
|
progress: progress?.reporter,
|
|
4446
4422
|
runnerOptions: options,
|
|
4447
|
-
session
|
|
4423
|
+
session,
|
|
4424
|
+
signal
|
|
4448
4425
|
});
|
|
4449
4426
|
} catch (error) {
|
|
4450
4427
|
if (error instanceof NoFilesFoundError) {
|
|
@@ -4485,10 +4462,10 @@ function shouldEnableProgress(options, io) {
|
|
|
4485
4462
|
//#endregion
|
|
4486
4463
|
//#region src/cli/commands/lsp/index.ts
|
|
4487
4464
|
const lsp = defineCommand({
|
|
4488
|
-
action: async (context) => (await import("./server-
|
|
4465
|
+
action: async (context) => (await import("./server-JWv5WkGk.mjs")).startLspServer(context.io),
|
|
4489
4466
|
description: "Run alint as a language server over stdio",
|
|
4490
4467
|
examples: [["# Point an editor at the project's own alint", "alint lsp"].join("\n")],
|
|
4491
|
-
help: ["Serve alint diagnostics over the Language Server Protocol on stdin/stdout.", "The server is cache-first
|
|
4468
|
+
help: ["Serve alint diagnostics over the Language Server Protocol on stdin/stdout.", "The server is cache-first. It publishes diagnostics that are already cached, and it never calls a model on its own. Runs that spend tokens happen only through the `alint.runFile` and `alint.runWorkspace` commands."].join("\n\n"),
|
|
4492
4469
|
name: "lsp",
|
|
4493
4470
|
options: [{
|
|
4494
4471
|
description: "Use stdio transport (default; accepted for client compatibility)",
|
|
@@ -5161,7 +5138,7 @@ const commandTree = [
|
|
|
5161
5138
|
];
|
|
5162
5139
|
//#endregion
|
|
5163
5140
|
//#region src/cli/cli.ts
|
|
5164
|
-
async function executeCli(argv, io) {
|
|
5141
|
+
async function executeCli(argv, io, runtime = {}) {
|
|
5165
5142
|
if (argv.includes("--version") || argv.includes("-v")) {
|
|
5166
5143
|
io.stdout.write(`${version}\n`);
|
|
5167
5144
|
return 0;
|
|
@@ -5179,7 +5156,8 @@ async function executeCli(argv, io) {
|
|
|
5179
5156
|
globalOptions,
|
|
5180
5157
|
interceptConsoleOutput,
|
|
5181
5158
|
io,
|
|
5182
|
-
setupNoInteractive
|
|
5159
|
+
setupNoInteractive,
|
|
5160
|
+
signal: runtime.signal
|
|
5183
5161
|
}, setPendingResult, {
|
|
5184
5162
|
examples: [
|
|
5185
5163
|
["# Configure a provider interactively", "alint setup"].join("\n"),
|
package/dist/index.d.mts
CHANGED
|
@@ -27,7 +27,10 @@ interface CliWritable {
|
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region src/cli/cli.d.ts
|
|
30
|
-
|
|
30
|
+
interface CliRuntimeOptions {
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
declare function executeCli(argv: string[], io: CliIo, runtime?: CliRuntimeOptions): Promise<number>;
|
|
31
34
|
//#endregion
|
|
32
35
|
//#region src/cli/reporters/index.d.ts
|
|
33
36
|
interface FormatDiagnosticsOptions {
|
|
@@ -45,4 +48,4 @@ interface StylishReporterOptions {
|
|
|
45
48
|
}
|
|
46
49
|
declare function formatStylish(input: Diagnostic[] | RunResult, options?: StylishReporterOptions): string;
|
|
47
50
|
//#endregion
|
|
48
|
-
export { type AlintConfig, type AlintConfigItem, type CliIo, type ReporterName, type RunnerConfig, defineConfig, executeCli, formatDiagnostics, formatJson, formatStylish, ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated };
|
|
51
|
+
export { type AlintConfig, type AlintConfigItem, type CliIo, type CliRuntimeOptions, type ReporterName, type RunnerConfig, defineConfig, executeCli, formatDiagnostics, formatJson, formatStylish, ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated };
|
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-F44RJ972.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 };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/cli/commands/lsp/command-ids.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Command ids the language server declares in `executeCommandProvider`.
|
|
4
|
+
*
|
|
5
|
+
* They are part of the protocol surface, so any editor integration needs them, not only the VS
|
|
6
|
+
* Code extension in this repository. This module imports nothing, so a client can bundle it
|
|
7
|
+
* without pulling in the CLI.
|
|
8
|
+
*/
|
|
9
|
+
/** Deletes cached diagnostics. A client asks the user before it sends this. */
|
|
10
|
+
declare const CLEAR_CACHE_COMMAND = "alint.clearCache";
|
|
11
|
+
/** Runs one file. The argument is the document URI. */
|
|
12
|
+
declare const RUN_FILE_COMMAND = "alint.runFile";
|
|
13
|
+
/** Runs every file the config matches. It takes no arguments. */
|
|
14
|
+
declare const RUN_WORKSPACE_COMMAND = "alint.runWorkspace";
|
|
15
|
+
//#endregion
|
|
16
|
+
export { CLEAR_CACHE_COMMAND, RUN_FILE_COMMAND, RUN_WORKSPACE_COMMAND };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/cli/commands/lsp/command-ids.ts
|
|
2
|
+
/**
|
|
3
|
+
* Command ids the language server declares in `executeCommandProvider`.
|
|
4
|
+
*
|
|
5
|
+
* They are part of the protocol surface, so any editor integration needs them, not only the VS
|
|
6
|
+
* Code extension in this repository. This module imports nothing, so a client can bundle it
|
|
7
|
+
* without pulling in the CLI.
|
|
8
|
+
*/
|
|
9
|
+
/** Deletes cached diagnostics. A client asks the user before it sends this. */
|
|
10
|
+
const CLEAR_CACHE_COMMAND = "alint.clearCache";
|
|
11
|
+
/** Runs one file. The argument is the document URI. */
|
|
12
|
+
const RUN_FILE_COMMAND = "alint.runFile";
|
|
13
|
+
/** Runs every file the config matches. It takes no arguments. */
|
|
14
|
+
const RUN_WORKSPACE_COMMAND = "alint.runWorkspace";
|
|
15
|
+
//#endregion
|
|
16
|
+
export { CLEAR_CACHE_COMMAND, RUN_FILE_COMMAND, RUN_WORKSPACE_COMMAND };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { a as createRunSession } from "./cli-
|
|
2
|
-
import {
|
|
1
|
+
import { a as createRunSession } from "./cli-F44RJ972.mjs";
|
|
2
|
+
import { CLEAR_CACHE_COMMAND, RUN_FILE_COMMAND, RUN_WORKSPACE_COMMAND } from "./lsp-commands.mjs";
|
|
3
|
+
import { realpath, rm } from "node:fs/promises";
|
|
3
4
|
import { AlintRunCancelledError, AlintRunError } from "@alint-js/core";
|
|
4
5
|
import { errorMessageFrom } from "@moeru/std";
|
|
5
6
|
import { join, relative, sep } from "node:path";
|
|
@@ -90,12 +91,51 @@ async function createFolderSession(options) {
|
|
|
90
91
|
let session = await createRunSession(runIo);
|
|
91
92
|
let diagnostics = /* @__PURE__ */ new Map();
|
|
92
93
|
let scheduled;
|
|
94
|
+
let explicitRun = Promise.resolve();
|
|
95
|
+
let explicitRunning = false;
|
|
93
96
|
const replaceMap = (next) => {
|
|
94
97
|
const changed = /* @__PURE__ */ new Set([...next.keys(), ...diagnostics.keys()]);
|
|
95
98
|
diagnostics = next;
|
|
96
99
|
return [...changed];
|
|
97
100
|
};
|
|
101
|
+
const runOnce = async (runOptions) => {
|
|
102
|
+
const runPaths = runOptions.inputs?.map((uri) => toRunPath(uri, paths)).filter((path) => path !== void 0);
|
|
103
|
+
const changed = /* @__PURE__ */ new Set();
|
|
104
|
+
const merge = (result) => {
|
|
105
|
+
for (const [uri, list] of groupByUri(result, paths)) {
|
|
106
|
+
diagnostics.set(uri, list);
|
|
107
|
+
changed.add(uri);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
explicitRunning = true;
|
|
111
|
+
try {
|
|
112
|
+
merge(await session.run({
|
|
113
|
+
inputs: runPaths,
|
|
114
|
+
progress: {
|
|
115
|
+
onDiagnostic: ({ diagnostic }) => {
|
|
116
|
+
const uri = toDocumentUri(diagnostic.filePath, paths);
|
|
117
|
+
diagnostics.set(uri, [...diagnostics.get(uri) ?? [], toLspDiagnostic(diagnostic)]);
|
|
118
|
+
changed.add(uri);
|
|
119
|
+
},
|
|
120
|
+
onJobStart: ({ job, progress }) => runOptions.onProgress?.(progress, job.inputPath)
|
|
121
|
+
},
|
|
122
|
+
projectTargets: runPaths === void 0 ? void 0 : false,
|
|
123
|
+
runner: session.runner,
|
|
124
|
+
signal: runOptions.signal
|
|
125
|
+
}));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (!(error instanceof AlintRunError) && !(error instanceof AlintRunCancelledError)) throw error;
|
|
128
|
+
merge(error.result);
|
|
129
|
+
} finally {
|
|
130
|
+
explicitRunning = false;
|
|
131
|
+
if (changed.size > 0) options.onChanged?.([...changed]);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
98
134
|
return {
|
|
135
|
+
clearCache: async () => {
|
|
136
|
+
await rm(session.cache.location, { force: true });
|
|
137
|
+
return replaceMap(/* @__PURE__ */ new Map());
|
|
138
|
+
},
|
|
99
139
|
cwd,
|
|
100
140
|
get diagnostics() {
|
|
101
141
|
return diagnostics;
|
|
@@ -110,7 +150,7 @@ async function createFolderSession(options) {
|
|
|
110
150
|
folderUri: options.folderUri,
|
|
111
151
|
refreshFile: async (uri) => {
|
|
112
152
|
const filePath = toRunPath(uri, paths);
|
|
113
|
-
if (filePath === void 0) return [];
|
|
153
|
+
if (filePath === void 0 || explicitRunning) return [];
|
|
114
154
|
const found = groupByUri(await runFromCache(session, { inputs: [filePath] }), paths);
|
|
115
155
|
const changed = /* @__PURE__ */ new Set([...found.keys()]);
|
|
116
156
|
const documentUri = toDocumentUri(filePath, paths);
|
|
@@ -119,6 +159,7 @@ async function createFolderSession(options) {
|
|
|
119
159
|
return [...changed];
|
|
120
160
|
},
|
|
121
161
|
refreshFromCache: async () => {
|
|
162
|
+
if (explicitRunning) return;
|
|
122
163
|
replaceMap(groupByUri(await runFromCache(session), paths));
|
|
123
164
|
},
|
|
124
165
|
reloadConfig: async () => {
|
|
@@ -126,12 +167,17 @@ async function createFolderSession(options) {
|
|
|
126
167
|
session = await createRunSession(runIo);
|
|
127
168
|
await replaced.shutdown();
|
|
128
169
|
},
|
|
170
|
+
runExplicit: (runOptions) => {
|
|
171
|
+
explicitRun = explicitRun.catch(() => {}).then(() => runOnce(runOptions));
|
|
172
|
+
return explicitRun;
|
|
173
|
+
},
|
|
129
174
|
scheduleWorkspacePass: () => {
|
|
130
175
|
if (scheduled !== void 0) return;
|
|
131
176
|
scheduled = setTimeout(() => {
|
|
132
177
|
scheduled = void 0;
|
|
133
178
|
(async () => {
|
|
134
179
|
try {
|
|
180
|
+
if (explicitRunning) return;
|
|
135
181
|
options.onChanged?.(replaceMap(groupByUri(await runFromCache(session), paths)));
|
|
136
182
|
} catch (error) {
|
|
137
183
|
options.io.stderr.write(`alint lsp: workspace pass failed: ${errorMessageFrom(error) ?? "unknown error"}\n`);
|
|
@@ -197,9 +243,9 @@ function toRunPath(uri, paths) {
|
|
|
197
243
|
/**
|
|
198
244
|
* Collects document URIs and publishes each one once per window, open documents first.
|
|
199
245
|
*
|
|
200
|
-
* An LSP publish
|
|
201
|
-
* many times. A workspace pass can queue thousands of documents, and the open one must
|
|
202
|
-
* for the rest.
|
|
246
|
+
* An LSP publish contains a whole document, so one publish per diagnostic would send the same
|
|
247
|
+
* document many times. A workspace pass can queue thousands of documents, and the open one must
|
|
248
|
+
* not wait for the rest.
|
|
203
249
|
*/
|
|
204
250
|
function createPublisher(options) {
|
|
205
251
|
const open = /* @__PURE__ */ new Set();
|
|
@@ -238,9 +284,6 @@ function createPublisher(options) {
|
|
|
238
284
|
//#region src/cli/commands/lsp/server.ts
|
|
239
285
|
/** Long enough to collect a workspace pass into few messages, short enough to keep a save prompt. */
|
|
240
286
|
const PUBLISH_FLUSH_MS = 200;
|
|
241
|
-
/** Two commands, because a workspace run can cost orders of magnitude more than one file. */
|
|
242
|
-
const RUN_FILE_COMMAND = "alint.runFile";
|
|
243
|
-
const RUN_WORKSPACE_COMMAND = "alint.runWorkspace";
|
|
244
287
|
async function startLspServer(io) {
|
|
245
288
|
const { stdin } = io;
|
|
246
289
|
if (!stdin) {
|
|
@@ -282,7 +325,11 @@ async function startLspServer(io) {
|
|
|
282
325
|
watchesConfig = params.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration === true;
|
|
283
326
|
return { capabilities: {
|
|
284
327
|
executeCommandProvider: {
|
|
285
|
-
commands: [
|
|
328
|
+
commands: [
|
|
329
|
+
CLEAR_CACHE_COMMAND,
|
|
330
|
+
RUN_FILE_COMMAND,
|
|
331
|
+
RUN_WORKSPACE_COMMAND
|
|
332
|
+
],
|
|
286
333
|
workDoneProgress: true
|
|
287
334
|
},
|
|
288
335
|
textDocumentSync: {
|
|
@@ -302,7 +349,7 @@ async function startLspServer(io) {
|
|
|
302
349
|
try {
|
|
303
350
|
await openFolder(folderUri);
|
|
304
351
|
} catch (error) {
|
|
305
|
-
connection.console.error(
|
|
352
|
+
connection.console.error(`${folderUri}: ${errorMessageFrom(error) ?? "could not read cached diagnostics"}`);
|
|
306
353
|
}
|
|
307
354
|
}));
|
|
308
355
|
});
|
|
@@ -311,7 +358,7 @@ async function startLspServer(io) {
|
|
|
311
358
|
try {
|
|
312
359
|
for (const uri of await folder.refreshFile(textDocument.uri)) publisher.queue(uri);
|
|
313
360
|
} catch (error) {
|
|
314
|
-
connection.console.error(
|
|
361
|
+
connection.console.error(`${textDocument.uri}: ${errorMessageFrom(error) ?? "could not refresh the saved file"}`);
|
|
315
362
|
}
|
|
316
363
|
}));
|
|
317
364
|
});
|
|
@@ -322,16 +369,55 @@ async function startLspServer(io) {
|
|
|
322
369
|
connection.onDidCloseTextDocument(({ textDocument }) => {
|
|
323
370
|
publisher.setOpen(textDocument.uri, false);
|
|
324
371
|
});
|
|
372
|
+
connection.onExecuteCommand(async ({ arguments: args, command }) => {
|
|
373
|
+
if (command === "alint.clearCache") {
|
|
374
|
+
await Promise.all([...folders.values()].map(async (folder) => {
|
|
375
|
+
try {
|
|
376
|
+
for (const uri of await folder.clearCache()) publisher.queue(uri);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
connection.console.error(`${folder.folderUri}: ${errorMessageFrom(error) ?? "could not clear the cache"}`);
|
|
379
|
+
}
|
|
380
|
+
}));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const inputs = command === "alint.runFile" ? args?.filter((value) => typeof value === "string") : void 0;
|
|
384
|
+
if (command === "alint.runFile" && (inputs === void 0 || inputs.length === 0)) {
|
|
385
|
+
connection.console.error(`${RUN_FILE_COMMAND} needs a document URI.`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const reporter = await connection.window.createWorkDoneProgress();
|
|
389
|
+
const controller = new AbortController();
|
|
390
|
+
reporter.begin(inputs === void 0 ? "Running alint on the workspace" : "Running alint on the current file", 0, void 0, true);
|
|
391
|
+
reporter.token.onCancellationRequested(() => controller.abort());
|
|
392
|
+
try {
|
|
393
|
+
await Promise.all([...folders.values()].map(async (folder) => {
|
|
394
|
+
try {
|
|
395
|
+
await folder.runExplicit({
|
|
396
|
+
inputs,
|
|
397
|
+
onProgress: (progress, inputPath) => {
|
|
398
|
+
const percentage = progress.jobsTotal === 0 ? 0 : Math.round(progress.jobsCompleted / progress.jobsTotal * 100);
|
|
399
|
+
reporter.report(percentage, inputPath);
|
|
400
|
+
},
|
|
401
|
+
signal: controller.signal
|
|
402
|
+
});
|
|
403
|
+
} catch (error) {
|
|
404
|
+
connection.console.error(`${folder.folderUri}: ${errorMessageFrom(error) ?? "the run failed"}`);
|
|
405
|
+
}
|
|
406
|
+
}));
|
|
407
|
+
} finally {
|
|
408
|
+
reporter.done();
|
|
409
|
+
}
|
|
410
|
+
});
|
|
325
411
|
connection.onDidChangeWatchedFiles(() => {
|
|
326
412
|
Promise.all([...folders.values()].map(async (folder) => {
|
|
327
413
|
try {
|
|
328
414
|
await folder.reloadConfig();
|
|
329
415
|
folder.scheduleWorkspacePass();
|
|
330
416
|
} catch (error) {
|
|
331
|
-
connection.console.error(
|
|
417
|
+
connection.console.error(`${folder.folderUri}: ${errorMessageFrom(error) ?? "could not reload the config"}`);
|
|
332
418
|
}
|
|
333
419
|
}));
|
|
334
|
-
connection.console.info("
|
|
420
|
+
connection.console.info("Configuration changed. Cached diagnostics were cleared. Run alint again to recreate them.");
|
|
335
421
|
});
|
|
336
422
|
connection.onShutdown(async () => {
|
|
337
423
|
publisher.dispose();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alint-js/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.1",
|
|
5
5
|
"description": "Agentic code analysis CLI for model-backed lint rules",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"types": "./dist/index.d.mts",
|
|
23
23
|
"default": "./dist/index.mjs"
|
|
24
24
|
},
|
|
25
|
+
"./lsp-commands": {
|
|
26
|
+
"types": "./dist/lsp-commands.d.mts",
|
|
27
|
+
"default": "./dist/lsp-commands.mjs"
|
|
28
|
+
},
|
|
25
29
|
"./package.json": "./package.json"
|
|
26
30
|
},
|
|
27
31
|
"bin": {
|
|
@@ -46,10 +50,10 @@
|
|
|
46
50
|
"tinyexec": "^1.2.4",
|
|
47
51
|
"tinyrainbow": "^3.1.1",
|
|
48
52
|
"vscode-languageserver": "^10.1.0",
|
|
49
|
-
"@alint-js/config": "0.
|
|
50
|
-
"@alint-js/core": "0.
|
|
51
|
-
"@alint-js/
|
|
52
|
-
"@alint-js/
|
|
53
|
+
"@alint-js/config": "0.7.1",
|
|
54
|
+
"@alint-js/core": "0.7.1",
|
|
55
|
+
"@alint-js/model-adapter-acp": "0.7.1",
|
|
56
|
+
"@alint-js/utils": "0.7.1"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"@pnpm/find-workspace-dir": "^1000.1.5",
|