@konvert7/klint 0.1.0 → 0.3.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.
Files changed (3) hide show
  1. package/cli.ts +5 -0
  2. package/core/arch.ts +15 -8
  3. package/package.json +1 -1
package/cli.ts CHANGED
@@ -24,6 +24,11 @@ interface CliOptions {
24
24
  export async function main(opts: CliOptions = {}): Promise<void> {
25
25
  const args = process.argv.slice(2);
26
26
 
27
+ if (args[0] === "help" || args[0] === "h") {
28
+ printHelp();
29
+ process.exit(0);
30
+ }
31
+
27
32
  if (args[0] === "install-skill") {
28
33
  await installSkill(args.slice(1));
29
34
  return;
package/core/arch.ts CHANGED
@@ -1,8 +1,10 @@
1
- import { dirname, relative, resolve } from "node:path";
1
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
2
2
  import ts from "typescript";
3
3
  import { walkAst } from "./ast";
4
4
  import type { ArchConfig, Severity, Violation } from "./types";
5
5
 
6
+ const toSlash = (p: string) => p.replaceAll("\\", "/");
7
+
6
8
  interface AliasEntry {
7
9
  /** The prefix to match (pattern with `/*` stripped, e.g. `"@"` from `"@/*"`). */
8
10
  prefix: string;
@@ -29,7 +31,7 @@ function loadPathAliases(root: string): AliasEntry[] {
29
31
  const prefix = isWildcard ? pattern.slice(0, -2) : pattern;
30
32
  const targetStr = targets[0];
31
33
  const targetBase = targetStr.endsWith("/*") ? targetStr.slice(0, -2) : targetStr;
32
- entries.push({ prefix, base: resolve(base, targetBase), isWildcard });
34
+ entries.push({ prefix, base: toSlash(resolve(base, targetBase)), isWildcard });
33
35
  }
34
36
  return entries;
35
37
  }
@@ -39,7 +41,7 @@ function resolveAlias(importPath: string, aliases: AliasEntry[]): string | undef
39
41
  if (alias.isWildcard) {
40
42
  const matchPrefix = `${alias.prefix}/`;
41
43
  if (importPath.startsWith(matchPrefix)) {
42
- return resolve(alias.base, importPath.slice(matchPrefix.length));
44
+ return toSlash(resolve(alias.base, importPath.slice(matchPrefix.length)));
43
45
  }
44
46
  } else if (importPath === alias.prefix) {
45
47
  return alias.base;
@@ -56,11 +58,11 @@ interface ImportRecord {
56
58
  }
57
59
 
58
60
  function isBareSpecifier(path: string): boolean {
59
- return !path.startsWith(".") && !path.startsWith("/");
61
+ return !path.startsWith(".") && !isAbsolute(path);
60
62
  }
61
63
 
62
64
  function globToPrefix(glob: string, root: string): string {
63
- return resolve(root, glob.split("/**")[0].split("/*")[0].split("*")[0]);
65
+ return toSlash(resolve(root, glob.split("/**")[0].split("/*")[0].split("*")[0]));
64
66
  }
65
67
 
66
68
  function resolveGlobs(
@@ -90,7 +92,12 @@ function resolveLayerFiles(
90
92
  }
91
93
 
92
94
  function inPrefixes(absPath: string, prefixes: string[]): boolean {
93
- return prefixes.some((p) => absPath === p || absPath.startsWith(`${p}/`));
95
+ return prefixes.some((p) => {
96
+ if (absPath === p || absPath.startsWith(`${p}/`)) return true;
97
+ // Extension-less imports ("../cli") won't match a prefix like "{root}/cli.ts"
98
+ const bare = p.replace(/\.(tsx?|jsx?|mts|cts)$/, "");
99
+ return bare !== p && (absPath === bare || absPath.startsWith(`${bare}/`));
100
+ });
94
101
  }
95
102
 
96
103
  function scanImports(
@@ -126,7 +133,7 @@ function scanImports(
126
133
  if (isBareSpecifier(path)) {
127
134
  resolved = resolveAlias(path, aliases) ?? path;
128
135
  } else {
129
- resolved = resolve(fileDir, path);
136
+ resolved = toSlash(resolve(fileDir, path));
130
137
  }
131
138
  const { line } = src.getLineAndCharacterOfPosition(specifierNode.getStart());
132
139
  records.push({ path, resolved, isTypeOnly, line: line + 1 });
@@ -210,7 +217,7 @@ export function runArchRules(
210
217
 
211
218
  for (const rule of arch.singleton ?? []) {
212
219
  const severity: Severity = rule.severity ?? "error";
213
- const onlyFile = resolve(root, rule.only);
220
+ const onlyFile = toSlash(resolve(root, rule.only));
214
221
  const inFiles = rule.in
215
222
  ? resolveLayerFiles(rule.in, layers, root, allFiles)
216
223
  : allFiles;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konvert7/klint",
3
- "version": "0.1.0",
3
+ "version": "0.3.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": {