@konvert7/klint 0.24.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/cli/help.ts +41 -0
- package/cli/install-skill.ts +102 -0
- package/cli/output.ts +159 -0
- package/cli/paths.ts +3 -0
- package/cli/rust-config.ts +144 -0
- package/cli/rust-engine.ts +301 -0
- package/cli.ts +15 -733
- package/core/arch.ts +44 -1
- package/core/config.schema.ts +39 -2
- package/core/native-binary.ts +5 -1
- package/core/types.ts +8 -0
- package/package.json +6 -5
- package/skill/klint-rules/SKILL.md +29 -3
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { type SpawnSyncReturns, spawnSync } from "node:child_process";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { runKlint } from "../core/runner";
|
|
6
|
+
import type { KlintRule, RuleConfigValue } from "../core/types";
|
|
7
|
+
import {
|
|
8
|
+
emptyJsonPayload,
|
|
9
|
+
isJsonPayload,
|
|
10
|
+
type JsonPayload,
|
|
11
|
+
jsonPayloadEquals,
|
|
12
|
+
mergeJsonOutputs,
|
|
13
|
+
parseJsonPayload,
|
|
14
|
+
toJsonCliResult,
|
|
15
|
+
toJsonPayload,
|
|
16
|
+
writeTextOutput,
|
|
17
|
+
} from "./output";
|
|
18
|
+
import { PACKAGE_ROOT } from "./paths";
|
|
19
|
+
import {
|
|
20
|
+
resolveEffectiveRules,
|
|
21
|
+
resolveRustEngineCommand,
|
|
22
|
+
ruleConfigSeverity,
|
|
23
|
+
rustEngineUnsupportedReason,
|
|
24
|
+
splitRulesForAuto,
|
|
25
|
+
} from "./rust-config";
|
|
26
|
+
|
|
27
|
+
export function runRustEngine({
|
|
28
|
+
fix,
|
|
29
|
+
json,
|
|
30
|
+
raw,
|
|
31
|
+
root,
|
|
32
|
+
rulesFile,
|
|
33
|
+
startedAt,
|
|
34
|
+
}: {
|
|
35
|
+
fix: boolean;
|
|
36
|
+
json: boolean;
|
|
37
|
+
raw: {
|
|
38
|
+
plugins?: string[];
|
|
39
|
+
rules?: Record<string, RuleConfigValue>;
|
|
40
|
+
arch?: unknown;
|
|
41
|
+
};
|
|
42
|
+
root: string;
|
|
43
|
+
rulesFile?: string;
|
|
44
|
+
startedAt: number;
|
|
45
|
+
}): void {
|
|
46
|
+
const unsupportedReason = rustEngineUnsupportedReason({
|
|
47
|
+
fix,
|
|
48
|
+
raw,
|
|
49
|
+
rulesFile,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (unsupportedReason) {
|
|
53
|
+
process.stderr.write(`klint: ${unsupportedReason}\n`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { configDir: rustConfigDir, cleanup } = writeRustEngineConfig({ raw, root });
|
|
58
|
+
const command = resolveRustEngineCommand(rustConfigDir);
|
|
59
|
+
let result: SpawnSyncReturns<string>;
|
|
60
|
+
try {
|
|
61
|
+
if (process.argv.includes("--debug") || process.argv.includes("-debug")) {
|
|
62
|
+
process.stderr.write(
|
|
63
|
+
`[klint:debug] rust engine: ${command.bin} ${command.args.join(" ")}\n`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
result = spawnSync(command.bin, command.args, {
|
|
67
|
+
cwd: PACKAGE_ROOT,
|
|
68
|
+
encoding: "utf-8",
|
|
69
|
+
});
|
|
70
|
+
} finally {
|
|
71
|
+
cleanup();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
75
|
+
if (json) {
|
|
76
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
77
|
+
process.exit(result.status ?? 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const payload = parseJsonPayload(result.stdout);
|
|
81
|
+
if (!isJsonPayload(payload)) {
|
|
82
|
+
process.stderr.write("klint: --engine rust failed to parse Rust JSON output\n");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
writeTextOutput(payload.violations, startedAt);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function runCompareEngine({
|
|
89
|
+
fix,
|
|
90
|
+
json,
|
|
91
|
+
raw,
|
|
92
|
+
root,
|
|
93
|
+
rulesFile,
|
|
94
|
+
tsViolations,
|
|
95
|
+
}: {
|
|
96
|
+
fix: boolean;
|
|
97
|
+
json: boolean;
|
|
98
|
+
raw: {
|
|
99
|
+
plugins?: string[];
|
|
100
|
+
rules?: Record<string, RuleConfigValue>;
|
|
101
|
+
arch?: unknown;
|
|
102
|
+
};
|
|
103
|
+
root: string;
|
|
104
|
+
rulesFile?: string;
|
|
105
|
+
tsViolations: ReturnType<typeof runKlint>;
|
|
106
|
+
}): void {
|
|
107
|
+
if (!json) {
|
|
108
|
+
process.stderr.write("klint: --engine compare currently requires --json\n");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const unsupportedReason = rustEngineUnsupportedReason({
|
|
113
|
+
fix,
|
|
114
|
+
raw,
|
|
115
|
+
rulesFile,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (unsupportedReason) {
|
|
119
|
+
process.stderr.write(`klint: ${unsupportedReason}\n`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const tsResult = toJsonCliResult(tsViolations);
|
|
124
|
+
const { configDir: rustConfigDir, cleanup } = writeRustEngineConfig({ raw, root });
|
|
125
|
+
const rustCommand = resolveRustEngineCommand(rustConfigDir);
|
|
126
|
+
let rustResult: SpawnSyncReturns<string>;
|
|
127
|
+
try {
|
|
128
|
+
rustResult = spawnSync(rustCommand.bin, rustCommand.args, {
|
|
129
|
+
cwd: PACKAGE_ROOT,
|
|
130
|
+
encoding: "utf-8",
|
|
131
|
+
});
|
|
132
|
+
} finally {
|
|
133
|
+
cleanup();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const rustStatus = rustResult.status ?? 1;
|
|
137
|
+
if (rustResult.stderr) process.stderr.write(rustResult.stderr);
|
|
138
|
+
|
|
139
|
+
const tsPayload = parseJsonPayload(tsResult.stdout);
|
|
140
|
+
const rustPayload = parseJsonPayload(rustResult.stdout);
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
rustStatus !== tsResult.status ||
|
|
144
|
+
tsPayload === undefined ||
|
|
145
|
+
rustPayload === undefined ||
|
|
146
|
+
!jsonPayloadEquals(tsPayload, rustPayload)
|
|
147
|
+
) {
|
|
148
|
+
const mismatchLines = [
|
|
149
|
+
"klint: compare engine mismatch",
|
|
150
|
+
`TypeScript exit: ${tsResult.status}`,
|
|
151
|
+
`Rust exit: ${rustStatus}`,
|
|
152
|
+
];
|
|
153
|
+
process.stderr.write(`${mismatchLines.join("\n")}\n`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
process.stdout.write(tsResult.stdout);
|
|
158
|
+
process.exit(tsResult.status);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function writeRustEngineConfig({
|
|
162
|
+
raw,
|
|
163
|
+
root,
|
|
164
|
+
}: {
|
|
165
|
+
raw: {
|
|
166
|
+
include?: string[];
|
|
167
|
+
plugins?: string[];
|
|
168
|
+
rules?: Record<string, RuleConfigValue>;
|
|
169
|
+
arch?: unknown;
|
|
170
|
+
};
|
|
171
|
+
root: string;
|
|
172
|
+
}): { configDir: string; cleanup: () => void } {
|
|
173
|
+
const tempDir = mkdtempSync(join(tmpdir(), "klint-rust-engine-"));
|
|
174
|
+
writeFileSync(
|
|
175
|
+
join(tempDir, "klint.config.json"),
|
|
176
|
+
JSON.stringify({
|
|
177
|
+
root,
|
|
178
|
+
include: raw.include ?? ["."],
|
|
179
|
+
plugins: [],
|
|
180
|
+
rules: resolveEffectiveRules(raw.plugins, raw.rules ?? {}),
|
|
181
|
+
arch: raw.arch,
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
return {
|
|
185
|
+
configDir: tempDir,
|
|
186
|
+
cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function runAutoEngine({
|
|
191
|
+
fix,
|
|
192
|
+
json,
|
|
193
|
+
raw,
|
|
194
|
+
root,
|
|
195
|
+
rulesFile,
|
|
196
|
+
startedAt,
|
|
197
|
+
customRules,
|
|
198
|
+
customRulesMap,
|
|
199
|
+
}: {
|
|
200
|
+
fix: boolean;
|
|
201
|
+
json: boolean;
|
|
202
|
+
raw: {
|
|
203
|
+
root?: string;
|
|
204
|
+
include?: string[];
|
|
205
|
+
plugins?: string[];
|
|
206
|
+
rules?: Record<string, RuleConfigValue>;
|
|
207
|
+
arch?: unknown;
|
|
208
|
+
};
|
|
209
|
+
root: string;
|
|
210
|
+
rulesFile?: string;
|
|
211
|
+
startedAt: number;
|
|
212
|
+
customRules: Record<string, KlintRule>;
|
|
213
|
+
customRulesMap: Record<string, RuleConfigValue>;
|
|
214
|
+
}): void {
|
|
215
|
+
if (fix) {
|
|
216
|
+
process.stderr.write("klint: --engine auto does not support --fix\n");
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
if (rulesFile) {
|
|
220
|
+
process.stderr.write("klint: --engine auto does not support --rules\n");
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const effectiveRules = resolveEffectiveRules(raw.plugins, raw.rules ?? {});
|
|
225
|
+
const { rustRules, tsRules } = splitRulesForAuto(effectiveRules);
|
|
226
|
+
const tsViolations = runKlint(
|
|
227
|
+
{
|
|
228
|
+
root,
|
|
229
|
+
include: raw.include ?? ["."],
|
|
230
|
+
plugins: [],
|
|
231
|
+
rules: { ...customRulesMap, ...tsRules },
|
|
232
|
+
},
|
|
233
|
+
customRules
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const rustOutput = runAutoRustSubset({
|
|
237
|
+
raw,
|
|
238
|
+
root,
|
|
239
|
+
rustRules,
|
|
240
|
+
});
|
|
241
|
+
const tsOutput = toJsonPayload(tsViolations);
|
|
242
|
+
const merged = mergeJsonOutputs(rustOutput, tsOutput);
|
|
243
|
+
if (json) {
|
|
244
|
+
process.stdout.write(JSON.stringify(merged));
|
|
245
|
+
process.exit(merged.summary.errors > 0 ? 2 : 0);
|
|
246
|
+
}
|
|
247
|
+
writeTextOutput(merged.violations, startedAt);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function runAutoRustSubset({
|
|
251
|
+
raw,
|
|
252
|
+
root,
|
|
253
|
+
rustRules,
|
|
254
|
+
}: {
|
|
255
|
+
raw: {
|
|
256
|
+
include?: string[];
|
|
257
|
+
arch?: unknown;
|
|
258
|
+
};
|
|
259
|
+
root: string;
|
|
260
|
+
rustRules: Record<string, RuleConfigValue>;
|
|
261
|
+
}): JsonPayload {
|
|
262
|
+
const hasRustRules = Object.values(rustRules).some(
|
|
263
|
+
(value) => ruleConfigSeverity(value) !== "off"
|
|
264
|
+
);
|
|
265
|
+
if (!raw.arch && !hasRustRules) {
|
|
266
|
+
return emptyJsonPayload();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const tempDir = mkdtempSync(join(tmpdir(), "klint-auto-rust-"));
|
|
270
|
+
try {
|
|
271
|
+
writeFileSync(
|
|
272
|
+
join(tempDir, "klint.config.json"),
|
|
273
|
+
JSON.stringify({
|
|
274
|
+
root,
|
|
275
|
+
include: raw.include ?? ["."],
|
|
276
|
+
rules: rustRules,
|
|
277
|
+
arch: raw.arch,
|
|
278
|
+
})
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const command = resolveRustEngineCommand(tempDir);
|
|
282
|
+
const result = spawnSync(command.bin, command.args, {
|
|
283
|
+
cwd: PACKAGE_ROOT,
|
|
284
|
+
encoding: "utf-8",
|
|
285
|
+
});
|
|
286
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
287
|
+
if ((result.status ?? 1) !== 0 && (result.status ?? 1) !== 2) {
|
|
288
|
+
process.stderr.write(result.stdout);
|
|
289
|
+
process.exit(result.status ?? 1);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const payload = parseJsonPayload(result.stdout);
|
|
293
|
+
if (!isJsonPayload(payload)) {
|
|
294
|
+
process.stderr.write("klint: --engine auto failed to parse Rust JSON output\n");
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
return payload;
|
|
298
|
+
} finally {
|
|
299
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
300
|
+
}
|
|
301
|
+
}
|