@konvert7/klint 0.9.0 → 0.10.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/cli.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+ import { spawnSync } from "node:child_process";
2
3
  import {
3
4
  cpSync,
4
5
  existsSync,
@@ -11,6 +12,7 @@ import { dirname, join, relative, resolve } from "node:path";
11
12
  import * as clack from "@clack/prompts";
12
13
  import { parse as parseYaml } from "yaml";
13
14
  import { applyFixes } from "./core/fixer";
15
+ import { resolveNativePackageBinary } from "./core/native-binary";
14
16
  import { runKlint } from "./core/runner";
15
17
  import type { ArchConfig, KlintConfig, KlintRule, RuleConfigValue } from "./core/types";
16
18
  import { BUILT_IN_PLUGINS } from "./plugins/index";
@@ -80,6 +82,12 @@ export async function main(opts: CliOptions = {}): Promise<void> {
80
82
  process.exit(1);
81
83
  }
82
84
  const root = resolve(configDir, raw.root ?? ".");
85
+ const engine = process.env.KLINT_ENGINE;
86
+
87
+ if (engine === "rust") {
88
+ runRustEngine({ configDir, fix, json, raw, rulesFile });
89
+ return;
90
+ }
83
91
 
84
92
  let customRules: Record<string, KlintRule> = {};
85
93
  const defaultRulesPath = resolve(configDir, "klint.rules.ts");
@@ -174,6 +182,105 @@ export async function main(opts: CliOptions = {}): Promise<void> {
174
182
  process.exit(0);
175
183
  }
176
184
 
185
+ function runRustEngine({
186
+ configDir,
187
+ fix,
188
+ json,
189
+ raw,
190
+ rulesFile,
191
+ }: {
192
+ configDir: string;
193
+ fix: boolean;
194
+ json: boolean;
195
+ raw: {
196
+ plugins?: string[];
197
+ rules?: Record<string, RuleConfigValue>;
198
+ arch?: unknown;
199
+ };
200
+ rulesFile?: string;
201
+ }): void {
202
+ const unsupportedReason = rustEngineUnsupportedReason({
203
+ fix,
204
+ json,
205
+ raw,
206
+ rulesFile,
207
+ });
208
+
209
+ if (unsupportedReason) {
210
+ process.stderr.write(`klint: ${unsupportedReason}\n`);
211
+ process.exit(1);
212
+ }
213
+
214
+ const command = resolveRustEngineCommand(configDir);
215
+ const result = spawnSync(command.bin, command.args, {
216
+ cwd: import.meta.dir,
217
+ encoding: "utf-8",
218
+ });
219
+
220
+ if (result.stdout) process.stdout.write(result.stdout);
221
+ if (result.stderr) process.stderr.write(result.stderr);
222
+ process.exit(result.status ?? 1);
223
+ }
224
+
225
+ interface RustEngineCommand {
226
+ bin: string;
227
+ args: string[];
228
+ }
229
+
230
+ function resolveRustEngineCommand(configDir: string): RustEngineCommand {
231
+ const args = ["--config", configDir, "--json"];
232
+ const explicitBin = process.env.KLINT_RUST_BIN;
233
+ if (explicitBin) {
234
+ return { bin: explicitBin, args };
235
+ }
236
+
237
+ const nativePackageBin = resolveNativePackageBinary({ packageRoot: import.meta.dir });
238
+ if (nativePackageBin) {
239
+ return { bin: nativePackageBin, args };
240
+ }
241
+
242
+ const localBin = join(
243
+ import.meta.dir,
244
+ "target",
245
+ "debug",
246
+ process.platform === "win32" ? "klint-rs.exe" : "klint-rs"
247
+ );
248
+ if (existsSync(localBin)) {
249
+ return { bin: localBin, args };
250
+ }
251
+
252
+ return {
253
+ bin: "cargo",
254
+ args: ["run", "--quiet", "-p", "klint-rs", "--", ...args],
255
+ };
256
+ }
257
+
258
+ function rustEngineUnsupportedReason({
259
+ fix,
260
+ json,
261
+ raw,
262
+ rulesFile,
263
+ }: {
264
+ fix: boolean;
265
+ json: boolean;
266
+ raw: {
267
+ plugins?: string[];
268
+ rules?: Record<string, RuleConfigValue>;
269
+ arch?: unknown;
270
+ };
271
+ rulesFile?: string;
272
+ }): string | undefined {
273
+ if (!json) return "KLINT_ENGINE=rust currently requires --json";
274
+ if (fix) return "KLINT_ENGINE=rust does not support --fix";
275
+ if (rulesFile) return "KLINT_ENGINE=rust does not support --rules";
276
+ if (!raw.arch) return "KLINT_ENGINE=rust requires an arch config";
277
+ if ((raw.plugins?.length ?? 0) > 0) return "KLINT_ENGINE=rust does not support plugins";
278
+ if (Object.values(raw.rules ?? {}).some((value) => value !== "off")) {
279
+ return "KLINT_ENGINE=rust does not support TypeScript rules";
280
+ }
281
+ return undefined;
282
+ }
283
+
177
284
  const AGENT_TARGETS = [
178
285
  { value: "claude", label: "Claude Code" },
179
286
  { value: "opencode", label: "opencode" },
@@ -0,0 +1,95 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join } from "node:path";
4
+
5
+ type NativePlatform = "darwin" | "linux" | "win32";
6
+ type NativeArch = "arm64" | "x64";
7
+
8
+ export interface NativePackage {
9
+ platform: NativePlatform;
10
+ arch: NativeArch;
11
+ packageName: string;
12
+ binaryPath: string;
13
+ }
14
+
15
+ const NATIVE_PACKAGES: Record<string, NativePackage> = {
16
+ "darwin-arm64": {
17
+ platform: "darwin",
18
+ arch: "arm64",
19
+ packageName: "@konvert7/klint-darwin-arm64",
20
+ binaryPath: "bin/klint-rs",
21
+ },
22
+ "darwin-x64": {
23
+ platform: "darwin",
24
+ arch: "x64",
25
+ packageName: "@konvert7/klint-darwin-x64",
26
+ binaryPath: "bin/klint-rs",
27
+ },
28
+ "linux-x64": {
29
+ platform: "linux",
30
+ arch: "x64",
31
+ packageName: "@konvert7/klint-linux-x64",
32
+ binaryPath: "bin/klint-rs",
33
+ },
34
+ "win32-x64": {
35
+ platform: "win32",
36
+ arch: "x64",
37
+ packageName: "@konvert7/klint-win32-x64",
38
+ binaryPath: "bin/klint-rs.exe",
39
+ },
40
+ };
41
+
42
+ interface NativeBinaryOptions {
43
+ packageRoot: string;
44
+ platform?: string;
45
+ arch?: string;
46
+ exists?: (path: string) => boolean;
47
+ resolvePackageJson?: (packageName: string) => string;
48
+ }
49
+
50
+ export function nativePackageForPlatform(
51
+ platform: string = process.platform,
52
+ arch: string = process.arch
53
+ ): NativePackage | undefined {
54
+ return NATIVE_PACKAGES[`${platform}-${arch}`];
55
+ }
56
+
57
+ export function nativePackages(): NativePackage[] {
58
+ return Object.values(NATIVE_PACKAGES);
59
+ }
60
+
61
+ export function resolveNativePackageBinary({
62
+ packageRoot,
63
+ platform = process.platform,
64
+ arch = process.arch,
65
+ exists = existsSync,
66
+ resolvePackageJson,
67
+ }: NativeBinaryOptions): string | undefined {
68
+ const nativePackage = nativePackageForPlatform(platform, arch);
69
+ if (!nativePackage) return undefined;
70
+
71
+ const packageJsonPath = resolveOptionalPackageJson(
72
+ packageRoot,
73
+ nativePackage.packageName,
74
+ resolvePackageJson
75
+ );
76
+ if (!packageJsonPath) return undefined;
77
+
78
+ const binaryPath = join(dirname(packageJsonPath), nativePackage.binaryPath);
79
+ return exists(binaryPath) ? binaryPath : undefined;
80
+ }
81
+
82
+ function resolveOptionalPackageJson(
83
+ packageRoot: string,
84
+ packageName: string,
85
+ resolvePackageJson?: (packageName: string) => string
86
+ ): string | undefined {
87
+ try {
88
+ if (resolvePackageJson) return resolvePackageJson(packageName);
89
+ return createRequire(join(packageRoot, "package.json")).resolve(
90
+ `${packageName}/package.json`
91
+ );
92
+ } catch {
93
+ return undefined;
94
+ }
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konvert7/klint",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Architecture-as-Code linter for TypeScript. Enforce layer boundaries, import rules, and singleton patterns in YAML.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,6 +35,10 @@
35
35
  "scripts": {
36
36
  "type-check": "tsc --noEmit",
37
37
  "test": "bun test",
38
+ "test:rust-engine": "bun test tests/rust-engine-cli.test.ts",
39
+ "pack:check": "bun tests/pack-check.ts",
40
+ "native:stage": "bun tests/stage-native-binary.ts",
41
+ "native:smoke": "bun tests/smoke-native-binary.ts",
38
42
  "check": "biome check",
39
43
  "check-write": "biome check --write",
40
44
  "knip": "knip-bun",