zonefence 0.0.2 → 0.0.3

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.ja.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  TypeScriptプロジェクト向けのフォルダ単位アーキテクチャ・ガードレール
4
4
 
5
+ [English README](./README.md)
6
+
5
7
  ## インストール
6
8
 
7
9
  ```bash
@@ -24,7 +26,7 @@ description: "Domain層 - 外部依存を持たない純粋なビジネスロジ
24
26
  imports:
25
27
  allow:
26
28
  - from: "./**" # 同フォルダ内のimportを許可
27
- - from: "@/shared/**" # 共有モジュールを許可
29
+ - from: "src/shared/**" # 共有モジュールを許可
28
30
  deny:
29
31
  - from: "axios"
30
32
  message: "Domain層は外部HTTPライブラリに依存してはいけません"
@@ -54,7 +56,7 @@ scope:
54
56
  imports:
55
57
  allow:
56
58
  - from: "./**" # 同フォルダ内
57
- - from: "@/shared/**" # 共有モジュール
59
+ - from: "src/shared/**" # 共有モジュール
58
60
  deny:
59
61
  - from: "../infrastructure/**"
60
62
  message: "Domain層からInfrastructure層への依存は禁止"
@@ -90,7 +92,19 @@ imports:
90
92
 
91
93
  ### 解決済みパス
92
94
 
93
- 相対パス(`../shared/utils`)とエイリアス(`@/shared/utils`)が同じファイルを指す場合、ts-morphで解決した絶対パスに対してマッチングを行います。これにより、書き方に関係なく同じルールが適用されます。
95
+ ローカルインポートは、コードに書かれたモジュール指定子ではなく、プロジェクトルートからの**解決済みファイルパス**に対してマッチングを行います。
96
+
97
+ 例えば、インポートが `@/api/helpers/errorHandler` で、これが `src/api/helpers/errorHandler.ts` に解決される場合、ルールのパターンは実際のパスを使用する必要があります:
98
+
99
+ ```yaml
100
+ imports:
101
+ deny:
102
+ # ❌ パスエイリアスはパターンでサポートされていません
103
+ # - from: "@/api/**"
104
+
105
+ # ✅ 実際のファイルパスを使用
106
+ - from: "src/api/**"
107
+ ```
94
108
 
95
109
  ### 外部パッケージ
96
110
 
@@ -99,8 +113,8 @@ imports:
99
113
  ```yaml
100
114
  imports:
101
115
  allow:
102
- - from: "**/shared/**" # 解決後パスにマッチ
103
- - from: "lodash" # 外部パッケージは指定子そのまま
116
+ - from: "src/shared/**" # 解決後パスにマッチ
117
+ - from: "lodash" # 外部パッケージは指定子そのまま
104
118
  ```
105
119
 
106
120
  ## ルールの継承
package/README.md CHANGED
@@ -8,6 +8,8 @@ Folder-based architecture guardrails for TypeScript projects.
8
8
 
9
9
  ```bash
10
10
  npm install -D zonefence
11
+ # or
12
+ pnpm add -D zonefence
11
13
  ```
12
14
 
13
15
  ## Usage
@@ -19,15 +21,17 @@ Create a `zonefence.yaml` file in any folder you want to protect:
19
21
  ```yaml
20
22
  version: 1
21
23
 
22
- description: "Domain layer - no external dependencies allowed"
24
+ description: "Domain layer - pure business logic with no external dependencies"
23
25
 
24
26
  imports:
25
27
  allow:
26
- - from: "./**"
27
- - from: "@/shared/**"
28
+ - from: "./**" # Allow imports from the same folder
29
+ - from: "src/shared/**" # Allow shared modules
28
30
  deny:
29
31
  - from: "axios"
30
32
  message: "Domain layer should not depend on external HTTP libraries"
33
+ - from: "../infrastructure/**"
34
+ message: "Domain layer cannot depend on Infrastructure layer"
31
35
  ```
32
36
 
33
37
  ### Run the check
@@ -52,13 +56,124 @@ scope:
52
56
  imports:
53
57
  allow:
54
58
  - from: "./**" # Same folder
55
- - from: "@/shared/**" # Shared modules
59
+ - from: "src/shared/**" # Shared modules
56
60
  deny:
57
61
  - from: "../infrastructure/**"
58
62
  message: "Domain layer cannot depend on Infrastructure layer"
59
63
  mode: allow-first # default
60
64
  ```
61
65
 
66
+ ### Configuration Options
67
+
68
+ | Option | Description | Default |
69
+ |--------|-------------|---------|
70
+ | `version` | Schema version (currently only 1) | Required |
71
+ | `description` | Description of the folder's design intent | - |
72
+ | `scope.apply` | Rule scope (`self`: this folder only, `descendants`: also applies to child folders) | `descendants` |
73
+ | `scope.exclude` | File patterns to exclude from checking | `[]` |
74
+ | `imports.allow` | List of allowed import patterns | `[]` |
75
+ | `imports.deny` | List of denied import patterns | `[]` |
76
+ | `imports.mode` | Evaluation mode (`allow-first`: allow list priority, `deny-first`: deny list priority) | `allow-first` |
77
+
78
+ ### Evaluation Modes
79
+
80
+ #### `allow-first` (default)
81
+
82
+ 1. If import matches a `deny` rule, error
83
+ 2. If `allow` rules are defined, import must match at least one, otherwise error
84
+
85
+ #### `deny-first`
86
+
87
+ 1. If import matches an `allow` rule, allow
88
+ 2. If import matches a `deny` rule, error
89
+ 3. If import matches neither, allow
90
+
91
+ ## Path Matching
92
+
93
+ ### Resolved Paths
94
+
95
+ Local imports are matched against the **resolved file path** relative to the project root, not the module specifier written in the code.
96
+
97
+ For example, if your import is `@/api/helpers/errorHandler` and it resolves to `src/api/helpers/errorHandler.ts`, the rule pattern should use the actual path:
98
+
99
+ ```yaml
100
+ imports:
101
+ deny:
102
+ # ❌ Path aliases are NOT supported in patterns
103
+ # - from: "@/api/**"
104
+
105
+ # ✅ Use actual file paths
106
+ - from: "src/api/**"
107
+ ```
108
+
109
+ ### External Packages
110
+
111
+ Modules that cannot be resolved (external packages) are matched against the original module specifier.
112
+
113
+ ```yaml
114
+ imports:
115
+ allow:
116
+ - from: "src/shared/**" # Matches resolved path
117
+ - from: "lodash" # External packages match the specifier as-is
118
+ ```
119
+
120
+ ## Rule Inheritance
121
+
122
+ Parent folder rules are inherited by child folders. Rules defined in child folders are merged with parent rules.
123
+
124
+ ```
125
+ src/
126
+ ├── zonefence.yaml # Parent rule (scope.apply: descendants)
127
+ └── domain/
128
+ └── zonefence.yaml # Child rule (inherits + extends parent)
129
+ ```
130
+
131
+ With `scope.apply: self`, the rule applies only to the current folder and is not inherited by child folders.
132
+
133
+ ## Error Output Example
134
+
135
+ ```
136
+ src/domain/user/UserService.ts
137
+ 12:1 error Import from "axios" is not allowed (import-boundary)
138
+ Design intent: Domain layer is a pure layer with no external dependencies
139
+ Rule: src/domain/zonefence.yaml
140
+
141
+ ✖ 1 error in 1 file
142
+ ```
143
+
144
+ ## CLI Options
145
+
146
+ ```bash
147
+ npx zonefence check [path] [options]
148
+ ```
149
+
150
+ | Option | Description |
151
+ |--------|-------------|
152
+ | `-c, --config <path>` | Path to tsconfig.json |
153
+ | `--no-color` | Disable colored output |
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ # Install dependencies
159
+ pnpm install
160
+
161
+ # Build
162
+ pnpm run build
163
+
164
+ # Development mode (watch)
165
+ pnpm run dev
166
+
167
+ # Lint
168
+ pnpm run lint
169
+
170
+ # Type check
171
+ pnpm run typecheck
172
+
173
+ # Test
174
+ pnpm test
175
+ ```
176
+
62
177
  ## License
63
178
 
64
179
  MIT
@@ -203,6 +203,16 @@ function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
203
203
  }
204
204
  return null;
205
205
  }
206
+ function getPackageName(moduleSpecifier) {
207
+ if (moduleSpecifier.startsWith("@")) {
208
+ const parts = moduleSpecifier.split("/");
209
+ if (parts.length >= 2) {
210
+ return `${parts[0]}/${parts[1]}`;
211
+ }
212
+ return moduleSpecifier;
213
+ }
214
+ return moduleSpecifier.split("/")[0];
215
+ }
206
216
  function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
207
217
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
208
218
  const sourceDir = import_node_path.default.dirname(sourceFile);
@@ -210,10 +220,21 @@ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
210
220
  return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern, { matchBase: true });
211
221
  }
212
222
  if (pattern.includes("*")) {
223
+ const packageName = getPackageName(pathToMatch);
224
+ if ((0, import_minimatch.minimatch)(packageName, pattern, { matchBase: true })) {
225
+ return true;
226
+ }
213
227
  return (0, import_minimatch.minimatch)(pathToMatch, pattern, { matchBase: true });
214
228
  }
215
- if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
216
- return true;
229
+ const pathPackageName = getPackageName(pathToMatch);
230
+ const patternPackageName = getPackageName(pattern);
231
+ if (pathPackageName === patternPackageName) {
232
+ if (pattern === patternPackageName) {
233
+ return true;
234
+ }
235
+ if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
236
+ return true;
237
+ }
217
238
  }
218
239
  return false;
219
240
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/check.ts","../../src/core/import-collector.ts","../../src/core/project.ts","../../src/evaluator/import-boundary.ts","../../src/evaluator/index.ts","../../src/reporter/console.ts","../../src/rules/loader.ts","../../src/rules/schema.ts","../../src/rules/resolver.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n\t.name(\"zonefence\")\n\t.description(\"Folder-based architecture guardrails for TypeScript projects\")\n\t.version(\"0.1.0\");\n\nprogram\n\t.command(\"check\")\n\t.description(\"Check import boundaries in the specified directory\")\n\t.argument(\"[path]\", \"Path to check\", \".\")\n\t.option(\"-c, --config <path>\", \"Path to tsconfig.json\")\n\t.option(\"--no-color\", \"Disable colored output\")\n\t.action(checkCommand);\n\nprogram.parse();\n","import path from \"node:path\";\nimport { collectImports } from \"../../core/import-collector.js\";\nimport { createProject } from \"../../core/project.js\";\nimport { evaluate } from \"../../evaluator/index.js\";\nimport { reportToConsole } from \"../../reporter/console.js\";\nimport { loadRulesForDirectory } from \"../../rules/loader.js\";\nimport { resolveRules } from \"../../rules/resolver.js\";\n\nexport interface CheckOptions {\n\tconfig?: string;\n\tcolor?: boolean;\n}\n\nexport async function checkCommand(targetPath: string, options: CheckOptions): Promise<void> {\n\tconst absolutePath = path.resolve(targetPath);\n\n\tconsole.log(`Checking import boundaries in: ${absolutePath}\\n`);\n\n\ttry {\n\t\tconst project = createProject({\n\t\t\ttsConfigFilePath: options.config,\n\t\t\trootDir: absolutePath,\n\t\t});\n\n\t\tconst imports = collectImports(project, absolutePath);\n\n\t\tif (imports.length === 0) {\n\t\t\tconsole.log(\"No imports found to check.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\tconst rulesByDir = await loadRulesForDirectory(absolutePath);\n\t\tconst resolvedRules = resolveRules(rulesByDir);\n\n\t\tconst results = evaluate(imports, resolvedRules, absolutePath);\n\n\t\tconst exitCode = reportToConsole(results, {\n\t\t\tcolor: options.color !== false,\n\t\t});\n\n\t\tprocess.exit(exitCode);\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error(\"An unknown error occurred\");\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// Exact match for external packages\n\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport type { EvaluationResult, Violation } from \"../evaluator/types.js\";\nimport type { ReporterOptions } from \"./types.js\";\n\nexport function reportToConsole(result: EvaluationResult, options: ReporterOptions = {}): number {\n\tconst { violations, filesChecked, importsChecked } = result;\n\tconst useColor = options.color !== false;\n\n\tconst c = useColor\n\t\t? chalk\n\t\t: {\n\t\t\t\tred: (s: string) => s,\n\t\t\t\tyellow: (s: string) => s,\n\t\t\t\tgreen: (s: string) => s,\n\t\t\t\tcyan: (s: string) => s,\n\t\t\t\tgray: (s: string) => s,\n\t\t\t\tbold: (s: string) => s,\n\t\t\t\tdim: (s: string) => s,\n\t\t\t};\n\n\tif (violations.length === 0) {\n\t\tconsole.log(c.green(\"✓ No import boundary violations found\"));\n\t\tconsole.log(c.dim(` Checked ${importsChecked} imports across ${filesChecked} files`));\n\t\treturn 0;\n\t}\n\n\t// Group violations by file\n\tconst violationsByFile = groupByFile(violations);\n\n\tfor (const [filePath, fileViolations] of Object.entries(violationsByFile)) {\n\t\tconsole.log();\n\t\tconsole.log(c.bold(filePath));\n\n\t\tfor (const violation of fileViolations) {\n\t\t\tconst location = c.dim(`${violation.line}:${violation.column}`);\n\t\t\tconst errorType = c.red(\"error\");\n\t\t\tconst rule = c.dim(`(${violation.rule})`);\n\n\t\t\tconsole.log(` ${location} ${errorType} ${violation.message} ${rule}`);\n\n\t\t\tif (violation.designIntent) {\n\t\t\t\tconsole.log(c.cyan(` Design intent: ${violation.designIntent}`));\n\t\t\t}\n\n\t\t\tif (violation.suggestion) {\n\t\t\t\tconsole.log(c.yellow(` Suggestion: ${violation.suggestion}`));\n\t\t\t}\n\n\t\t\tconst ruleFile = path.relative(process.cwd(), violation.ruleFilePath);\n\t\t\tconsole.log(c.gray(` Rule: ${ruleFile}`));\n\t\t}\n\t}\n\n\tconsole.log();\n\n\tconst errorCount = violations.length;\n\tconst fileCount = Object.keys(violationsByFile).length;\n\tconst summary = `✖ ${errorCount} error${errorCount !== 1 ? \"s\" : \"\"} in ${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tconsole.log(c.red(c.bold(summary)));\n\n\treturn 1;\n}\n\nfunction groupByFile(violations: Violation[]): Record<string, Violation[]> {\n\tconst grouped: Record<string, Violation[]> = {};\n\n\tfor (const violation of violations) {\n\t\tconst relativePath = path.relative(process.cwd(), violation.sourceFile);\n\t\tif (!grouped[relativePath]) {\n\t\t\tgrouped[relativePath] = [];\n\t\t}\n\t\tgrouped[relativePath].push(violation);\n\t}\n\n\t// Sort violations within each file by line number\n\tfor (const violations of Object.values(grouped)) {\n\t\tviolations.sort((a, b) => a.line - b.line || a.column - b.column);\n\t}\n\n\treturn grouped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n"],"mappings":"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","names":["import_node_path","path","import_node_path","chalk","path","violations","import_node_path","path","fs","parseYaml","import_node_path","path","path"]}
1
+ {"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/check.ts","../../src/core/import-collector.ts","../../src/core/project.ts","../../src/evaluator/import-boundary.ts","../../src/evaluator/index.ts","../../src/reporter/console.ts","../../src/rules/loader.ts","../../src/rules/schema.ts","../../src/rules/resolver.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n\t.name(\"zonefence\")\n\t.description(\"Folder-based architecture guardrails for TypeScript projects\")\n\t.version(\"0.1.0\");\n\nprogram\n\t.command(\"check\")\n\t.description(\"Check import boundaries in the specified directory\")\n\t.argument(\"[path]\", \"Path to check\", \".\")\n\t.option(\"-c, --config <path>\", \"Path to tsconfig.json\")\n\t.option(\"--no-color\", \"Disable colored output\")\n\t.action(checkCommand);\n\nprogram.parse();\n","import path from \"node:path\";\nimport { collectImports } from \"../../core/import-collector.js\";\nimport { createProject } from \"../../core/project.js\";\nimport { evaluate } from \"../../evaluator/index.js\";\nimport { reportToConsole } from \"../../reporter/console.js\";\nimport { loadRulesForDirectory } from \"../../rules/loader.js\";\nimport { resolveRules } from \"../../rules/resolver.js\";\n\nexport interface CheckOptions {\n\tconfig?: string;\n\tcolor?: boolean;\n}\n\nexport async function checkCommand(targetPath: string, options: CheckOptions): Promise<void> {\n\tconst absolutePath = path.resolve(targetPath);\n\n\tconsole.log(`Checking import boundaries in: ${absolutePath}\\n`);\n\n\ttry {\n\t\tconst project = createProject({\n\t\t\ttsConfigFilePath: options.config,\n\t\t\trootDir: absolutePath,\n\t\t});\n\n\t\tconst imports = collectImports(project, absolutePath);\n\n\t\tif (imports.length === 0) {\n\t\t\tconsole.log(\"No imports found to check.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\tconst rulesByDir = await loadRulesForDirectory(absolutePath);\n\t\tconst resolvedRules = resolveRules(rulesByDir);\n\n\t\tconst results = evaluate(imports, resolvedRules, absolutePath);\n\n\t\tconst exitCode = reportToConsole(results, {\n\t\t\tcolor: options.color !== false,\n\t\t});\n\n\t\tprocess.exit(exitCode);\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error(\"An unknown error occurred\");\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\t// For external packages with glob patterns, match against the package name\n\t\t// and also allow subpaths of matching packages\n\t\tconst packageName = getPackageName(pathToMatch);\n\t\tif (minimatch(packageName, pattern, { matchBase: true })) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also try matching the full path for more specific patterns\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport type { EvaluationResult, Violation } from \"../evaluator/types.js\";\nimport type { ReporterOptions } from \"./types.js\";\n\nexport function reportToConsole(result: EvaluationResult, options: ReporterOptions = {}): number {\n\tconst { violations, filesChecked, importsChecked } = result;\n\tconst useColor = options.color !== false;\n\n\tconst c = useColor\n\t\t? chalk\n\t\t: {\n\t\t\t\tred: (s: string) => s,\n\t\t\t\tyellow: (s: string) => s,\n\t\t\t\tgreen: (s: string) => s,\n\t\t\t\tcyan: (s: string) => s,\n\t\t\t\tgray: (s: string) => s,\n\t\t\t\tbold: (s: string) => s,\n\t\t\t\tdim: (s: string) => s,\n\t\t\t};\n\n\tif (violations.length === 0) {\n\t\tconsole.log(c.green(\"✓ No import boundary violations found\"));\n\t\tconsole.log(c.dim(` Checked ${importsChecked} imports across ${filesChecked} files`));\n\t\treturn 0;\n\t}\n\n\t// Group violations by file\n\tconst violationsByFile = groupByFile(violations);\n\n\tfor (const [filePath, fileViolations] of Object.entries(violationsByFile)) {\n\t\tconsole.log();\n\t\tconsole.log(c.bold(filePath));\n\n\t\tfor (const violation of fileViolations) {\n\t\t\tconst location = c.dim(`${violation.line}:${violation.column}`);\n\t\t\tconst errorType = c.red(\"error\");\n\t\t\tconst rule = c.dim(`(${violation.rule})`);\n\n\t\t\tconsole.log(` ${location} ${errorType} ${violation.message} ${rule}`);\n\n\t\t\tif (violation.designIntent) {\n\t\t\t\tconsole.log(c.cyan(` Design intent: ${violation.designIntent}`));\n\t\t\t}\n\n\t\t\tif (violation.suggestion) {\n\t\t\t\tconsole.log(c.yellow(` Suggestion: ${violation.suggestion}`));\n\t\t\t}\n\n\t\t\tconst ruleFile = path.relative(process.cwd(), violation.ruleFilePath);\n\t\t\tconsole.log(c.gray(` Rule: ${ruleFile}`));\n\t\t}\n\t}\n\n\tconsole.log();\n\n\tconst errorCount = violations.length;\n\tconst fileCount = Object.keys(violationsByFile).length;\n\tconst summary = `✖ ${errorCount} error${errorCount !== 1 ? \"s\" : \"\"} in ${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tconsole.log(c.red(c.bold(summary)));\n\n\treturn 1;\n}\n\nfunction groupByFile(violations: Violation[]): Record<string, Violation[]> {\n\tconst grouped: Record<string, Violation[]> = {};\n\n\tfor (const violation of violations) {\n\t\tconst relativePath = path.relative(process.cwd(), violation.sourceFile);\n\t\tif (!grouped[relativePath]) {\n\t\t\tgrouped[relativePath] = [];\n\t\t}\n\t\tgrouped[relativePath].push(violation);\n\t}\n\n\t// Sort violations within each file by line number\n\tfor (const violations of Object.values(grouped)) {\n\t\tviolations.sort((a, b) => a.line - b.line || a.column - b.column);\n\t}\n\n\treturn grouped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,uBAAwB;;;ACFxB,IAAAA,oBAAiB;;;ACGV,SAAS,eAAe,SAAkB,SAA+B;AAC/E,QAAM,UAAwB,CAAC;AAE/B,aAAW,cAAc,QAAQ,eAAe,GAAG;AAClD,UAAM,cAAc,uBAAuB,YAAY,OAAO;AAC9D,YAAQ,KAAK,GAAG,WAAW;AAAA,EAC5B;AAEA,SAAO;AACR;AAEA,SAAS,uBAAuB,YAAwB,SAA+B;AACtF,QAAM,UAAwB,CAAC;AAC/B,QAAM,WAAW,WAAW,YAAY;AAExC,aAAW,cAAc,WAAW,sBAAsB,GAAG;AAC5D,UAAM,aAAa,uBAAuB,YAAY,UAAU,OAAO;AACvE,YAAQ,KAAK,UAAU;AAAA,EACxB;AAGA,aAAW,cAAc,WAAW,sBAAsB,GAAG;AAC5D,UAAM,kBAAkB,WAAW,mBAAmB;AACtD,QAAI,iBAAiB;AACpB,YAAM,iBAAiB,gBAAgB,gBAAgB;AACvD,YAAM,eAAe,kBAAkB,YAAY,gBAAgB,QAAQ;AAC3E,YAAM,YAAY,WAAW,mBAAmB;AAChD,YAAM,cAAc,WAAW,SAAS,IAAI,WAAW,gBAAgB;AAEvE,cAAQ,KAAK;AAAA,QACZ,YAAY;AAAA,QACZ,iBAAiB;AAAA,QACjB;AAAA,QACA,YAAY,iBAAiB,gBAAgB,YAAY;AAAA,QACzD,MAAM;AAAA,QACN,QAAQ;AAAA,MACT,CAAC;AAAA,IACF;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,uBACR,YACA,UACA,UACa;AACb,QAAM,kBAAkB,WAAW,wBAAwB;AAC3D,QAAM,eAAe,kBAAkB,YAAY,iBAAiB,QAAQ;AAC5E,QAAM,YAAY,WAAW,mBAAmB;AAChD,QAAM,cAAc,WAAW,SAAS,IAAI,WAAW,gBAAgB;AAEvE,SAAO;AAAA,IACN,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY,iBAAiB,iBAAiB,YAAY;AAAA,IAC1D,MAAM;AAAA,IACN,QAAQ;AAAA,EACT;AACD;AAEA,SAAS,kBACR,MACA,iBACA,iBACgB;AAEhB,QAAM,qBAAqB,KAAK,+BAA+B;AAC/D,MAAI,oBAAoB;AACvB,WAAO,mBAAmB,YAAY;AAAA,EACvC;AAGA,MAAI,gBAAgB,WAAW,GAAG,KAAK,gBAAgB,WAAW,GAAG,GAAG;AACvE,WAAO;AAAA,EACR;AAGA,SAAO;AACR;AAEA,SAAS,iBAAiB,iBAAyB,cAAsC;AAExF,MAAI,iBAAiB,MAAM;AAC1B,WAAO;AAAA,EACR;AAGA,MAAI,gBAAgB,WAAW,GAAG,KAAK,gBAAgB,WAAW,GAAG,GAAG;AACvE,WAAO;AAAA,EACR;AAGA,SAAO;AACR;;;ACnGA,sBAAwB;AAGjB,SAAS,cAAc,SAAkC;AAC/D,QAAM,UAAU,IAAI,wBAAQ;AAAA,IAC3B,kBAAkB,QAAQ;AAAA,IAC1B,6BAA6B;AAAA,EAC9B,CAAC;AAED,UAAQ,sBAAsB;AAAA,IAC7B,GAAG,QAAQ,OAAO;AAAA,IAClB,GAAG,QAAQ,OAAO;AAAA,IAClB,IAAI,QAAQ,OAAO;AAAA,IACnB,IAAI,QAAQ,OAAO;AAAA,EACpB,CAAC;AAED,SAAO;AACR;;;ACjBA,uBAAiB;AACjB,uBAA0B;AAKnB,SAAS,uBACf,YACA,OACA,SACmB;AAEnB,QAAM,iBAAiB,mBAAmB,WAAW,YAAY,KAAK;AAEtE,MAAI,CAAC,gBAAgB;AAEpB,WAAO;AAAA,EACR;AAGA,MAAI,WAAW,WAAW,YAAY,gBAAgB,OAAO,GAAG;AAC/D,WAAO;AAAA,EACR;AAEA,QAAM,EAAE,QAAQ,aAAa,IAAI;AACjC,QAAM,UAAU,OAAO;AAEvB,MAAI,CAAC,SAAS;AACb,WAAO;AAAA,EACR;AAEA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,aAAa,QAAQ,SAAS,CAAC;AACrC,QAAM,YAAY,QAAQ,QAAQ,CAAC;AAGnC,QAAM,cAAc,eAAe,YAAY,OAAO;AAEtD,MAAI,SAAS,eAAe;AAE3B,UAAM,YAAY,iBAAiB,aAAa,WAAW,WAAW,YAAY,OAAO;AACzF,QAAI,WAAW;AACd,aAAO,gBAAgB,YAAY,WAAW,cAAc,OAAO,WAAW;AAAA,IAC/E;AAGA,QAAI,WAAW,SAAS,GAAG;AAC1B,YAAM,aAAa,iBAAiB,aAAa,YAAY,WAAW,YAAY,OAAO;AAC3F,UAAI,CAAC,YAAY;AAChB,eAAO;AAAA,UACN;AAAA,UACA;AAAA,YACC,MAAM;AAAA,YACN,SAAS,gBAAgB,WAAW,eAAe;AAAA,UACpD;AAAA,UACA;AAAA,UACA,OAAO;AAAA,QACR;AAAA,MACD;AAAA,IACD;AAAA,EACD,OAAO;AAEN,UAAM,aAAa,iBAAiB,aAAa,YAAY,WAAW,YAAY,OAAO;AAC3F,QAAI,YAAY;AACf,aAAO;AAAA,IACR;AAEA,UAAM,YAAY,iBAAiB,aAAa,WAAW,WAAW,YAAY,OAAO;AACzF,QAAI,WAAW;AACd,aAAO,gBAAgB,YAAY,WAAW,cAAc,OAAO,WAAW;AAAA,IAC/E;AAAA,EAGD;AAEA,SAAO;AACR;AAEA,SAAS,mBAAmB,UAAkB,OAA4C;AAEzF,MAAI,eAAoC;AAExC,aAAW,QAAQ,OAAO;AACzB,UAAM,WAAW,iBAAAC,QAAK,SAAS,KAAK,WAAW,QAAQ;AAEvD,QAAI,CAAC,SAAS,WAAW,IAAI,KAAK,CAAC,iBAAAA,QAAK,WAAW,QAAQ,GAAG;AAC7D,UAAI,CAAC,gBAAgB,KAAK,UAAU,SAAS,aAAa,UAAU,QAAQ;AAC3E,uBAAe;AAAA,MAChB;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,WAAW,UAAkB,MAAoB,SAA0B;AACnF,QAAM,eAAe,iBAAAA,QAAK,SAAS,SAAS,QAAQ;AAEpD,aAAW,WAAW,KAAK,iBAAiB;AAC3C,YAAI,4BAAU,cAAc,OAAO,SAAK,4BAAU,iBAAAA,QAAK,SAAS,QAAQ,GAAG,OAAO,GAAG;AACpF,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,eAAe,YAAwB,SAAyB;AAExE,MAAI,WAAW,YAAY;AAC1B,WAAO,WAAW;AAAA,EACnB;AAGA,MAAI,WAAW,cAAc;AAC5B,WAAO,iBAAAA,QAAK,SAAS,SAAS,WAAW,YAAY;AAAA,EACtD;AAGA,SAAO,WAAW;AACnB;AAEA,SAAS,iBACR,aACA,OACA,YACA,SACoB;AACpB,aAAW,QAAQ,OAAO;AACzB,QAAI,eAAe,aAAa,KAAK,MAAM,YAAY,OAAO,GAAG;AAChE,aAAO;AAAA,IACR;AAAA,EACD;AACA,SAAO;AACR;AAOA,SAAS,eAAe,iBAAiC;AACxD,MAAI,gBAAgB,WAAW,GAAG,GAAG;AAEpC,UAAM,QAAQ,gBAAgB,MAAM,GAAG;AACvC,QAAI,MAAM,UAAU,GAAG;AACtB,aAAO,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAAA,IAC/B;AACA,WAAO;AAAA,EACR;AAEA,SAAO,gBAAgB,MAAM,GAAG,EAAE,CAAC;AACpC;AAEA,SAAS,eACR,aACA,SACA,YACA,SACU;AAEV,MAAI,QAAQ,WAAW,IAAI,KAAK,QAAQ,WAAW,KAAK,GAAG;AAE1D,UAAM,YAAY,iBAAAA,QAAK,QAAQ,UAAU;AACzC,UAAM,kBAAkB,iBAAAA,QAAK,SAAS,SAAS,iBAAAA,QAAK,QAAQ,WAAW,OAAO,CAAC;AAC/E,eAAO,4BAAU,aAAa,iBAAiB,EAAE,WAAW,KAAK,CAAC;AAAA,EACnE;AAGA,MAAI,QAAQ,SAAS,GAAG,GAAG;AAG1B,UAAM,cAAc,eAAe,WAAW;AAC9C,YAAI,4BAAU,aAAa,SAAS,EAAE,WAAW,KAAK,CAAC,GAAG;AACzD,aAAO;AAAA,IACR;AAEA,eAAO,4BAAU,aAAa,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3D;AAGA,QAAM,kBAAkB,eAAe,WAAW;AAClD,QAAM,qBAAqB,eAAe,OAAO;AAGjD,MAAI,oBAAoB,oBAAoB;AAE3C,QAAI,YAAY,oBAAoB;AACnC,aAAO;AAAA,IACR;AAEA,QAAI,gBAAgB,WAAW,YAAY,WAAW,GAAG,OAAO,GAAG,GAAG;AACrE,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,gBACR,YACA,MACA,cACA,cACY;AACZ,SAAO;AAAA,IACN,YAAY,WAAW;AAAA,IACvB,iBAAiB,WAAW;AAAA,IAC5B,MAAM,WAAW;AAAA,IACjB,QAAQ,WAAW;AAAA,IACnB,MAAM;AAAA,IACN,SAAS,KAAK,WAAW,gBAAgB,WAAW,eAAe;AAAA,IACnE;AAAA,IACA;AAAA,EACD;AACD;;;AClNO,SAAS,SACf,SACA,OACA,SACmB;AACnB,QAAM,aAA0B,CAAC;AACjC,QAAM,eAAe,oBAAI,IAAY;AAErC,aAAW,cAAc,SAAS;AACjC,iBAAa,IAAI,WAAW,UAAU;AAEtC,UAAM,YAAY,uBAAuB,YAAY,OAAO,OAAO;AACnE,QAAI,WAAW;AACd,iBAAW,KAAK,SAAS;AAAA,IAC1B;AAAA,EACD;AAEA,SAAO;AAAA,IACN;AAAA,IACA,cAAc,aAAa;AAAA,IAC3B,gBAAgB,QAAQ;AAAA,EACzB;AACD;;;AC3BA,IAAAC,oBAAiB;AACjB,mBAAkB;AAIX,SAAS,gBAAgB,QAA0B,UAA2B,CAAC,GAAW;AAChG,QAAM,EAAE,YAAY,cAAc,eAAe,IAAI;AACrD,QAAM,WAAW,QAAQ,UAAU;AAEnC,QAAM,IAAI,WACP,aAAAC,UACA;AAAA,IACA,KAAK,CAAC,MAAc;AAAA,IACpB,QAAQ,CAAC,MAAc;AAAA,IACvB,OAAO,CAAC,MAAc;AAAA,IACtB,MAAM,CAAC,MAAc;AAAA,IACrB,MAAM,CAAC,MAAc;AAAA,IACrB,MAAM,CAAC,MAAc;AAAA,IACrB,KAAK,CAAC,MAAc;AAAA,EACrB;AAEF,MAAI,WAAW,WAAW,GAAG;AAC5B,YAAQ,IAAI,EAAE,MAAM,4CAAuC,CAAC;AAC5D,YAAQ,IAAI,EAAE,IAAI,aAAa,cAAc,mBAAmB,YAAY,QAAQ,CAAC;AACrF,WAAO;AAAA,EACR;AAGA,QAAM,mBAAmB,YAAY,UAAU;AAE/C,aAAW,CAAC,UAAU,cAAc,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAC1E,YAAQ,IAAI;AACZ,YAAQ,IAAI,EAAE,KAAK,QAAQ,CAAC;AAE5B,eAAW,aAAa,gBAAgB;AACvC,YAAM,WAAW,EAAE,IAAI,GAAG,UAAU,IAAI,IAAI,UAAU,MAAM,EAAE;AAC9D,YAAM,YAAY,EAAE,IAAI,OAAO;AAC/B,YAAM,OAAO,EAAE,IAAI,IAAI,UAAU,IAAI,GAAG;AAExC,cAAQ,IAAI,KAAK,QAAQ,KAAK,SAAS,KAAK,UAAU,OAAO,KAAK,IAAI,EAAE;AAExE,UAAI,UAAU,cAAc;AAC3B,gBAAQ,IAAI,EAAE,KAAK,sBAAsB,UAAU,YAAY,EAAE,CAAC;AAAA,MACnE;AAEA,UAAI,UAAU,YAAY;AACzB,gBAAQ,IAAI,EAAE,OAAO,mBAAmB,UAAU,UAAU,EAAE,CAAC;AAAA,MAChE;AAEA,YAAM,WAAW,kBAAAC,QAAK,SAAS,QAAQ,IAAI,GAAG,UAAU,YAAY;AACpE,cAAQ,IAAI,EAAE,KAAK,aAAa,QAAQ,EAAE,CAAC;AAAA,IAC5C;AAAA,EACD;AAEA,UAAQ,IAAI;AAEZ,QAAM,aAAa,WAAW;AAC9B,QAAM,YAAY,OAAO,KAAK,gBAAgB,EAAE;AAChD,QAAM,UAAU,UAAK,UAAU,SAAS,eAAe,IAAI,MAAM,EAAE,OAAO,SAAS,QAAQ,cAAc,IAAI,MAAM,EAAE;AAErH,UAAQ,IAAI,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,CAAC;AAElC,SAAO;AACR;AAEA,SAAS,YAAY,YAAsD;AAC1E,QAAM,UAAuC,CAAC;AAE9C,aAAW,aAAa,YAAY;AACnC,UAAM,eAAe,kBAAAA,QAAK,SAAS,QAAQ,IAAI,GAAG,UAAU,UAAU;AACtE,QAAI,CAAC,QAAQ,YAAY,GAAG;AAC3B,cAAQ,YAAY,IAAI,CAAC;AAAA,IAC1B;AACA,YAAQ,YAAY,EAAE,KAAK,SAAS;AAAA,EACrC;AAGA,aAAWC,eAAc,OAAO,OAAO,OAAO,GAAG;AAChD,IAAAA,YAAW,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM;AAAA,EACjE;AAEA,SAAO;AACR;;;AClFA,qBAAe;AACf,IAAAC,oBAAiB;AACjB,kBAAmC;;;ACFnC,iBAAkB;AAElB,IAAM,mBAAmB,aAAE,MAAM;AAAA,EAChC,aAAE,OAAO,EAAE,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE;AAAA,EACzC,aAAE,OAAO;AAAA,IACR,MAAM,aAAE,OAAO;AAAA,IACf,SAAS,aAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,CAAC;AACF,CAAC;AAEM,IAAM,wBAAwB,aAAE,OAAO;AAAA,EAC7C,SAAS,aAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACnC,aAAa,aAAE,OAAO,EAAE,SAAS;AAAA,EACjC,OAAO,aACL,OAAO;AAAA,IACP,OAAO,aAAE,KAAK,CAAC,QAAQ,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,aAAa;AAAA,IACvE,SAAS,aAAE,MAAM,aAAE,OAAO,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,EACnD,CAAC,EACA,SAAS,EACT,QAAQ,CAAC,CAAC;AAAA,EACZ,SAAS,aACP,OAAO;AAAA,IACP,OAAO,aAAE,MAAM,gBAAgB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,IACtD,MAAM,aAAE,MAAM,gBAAgB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,IACrD,MAAM,aAAE,KAAK,CAAC,eAAe,YAAY,CAAC,EAAE,SAAS,EAAE,QAAQ,aAAa;AAAA,EAC7E,CAAC,EACA,SAAS,EACT,QAAQ,CAAC,CAAC;AACb,CAAC;AAIM,SAAS,YAAY,MAAsC;AACjE,SAAO,sBAAsB,MAAM,IAAI;AACxC;;;AD5BA,IAAM,iBAAiB;AAEvB,eAAsB,sBAAsB,SAA4C;AACvF,QAAM,QAA0B,CAAC;AAEjC,QAAM,cAAc,SAAS,SAAS,KAAK;AAE3C,SAAO;AACR;AAEA,eAAe,cACd,YACA,SACA,OACgB;AAChB,QAAM,eAAe,kBAAAC,QAAK,KAAK,YAAY,cAAc;AAEzD,MAAI,eAAAC,QAAG,WAAW,YAAY,GAAG;AAChC,UAAM,SAAS,MAAM,aAAa,YAAY;AAC9C,UAAM,UAAU,IAAI;AAAA,MACnB;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAGA,QAAM,UAAU,eAAAA,QAAG,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC;AAClE,aAAW,SAAS,SAAS;AAC5B,QAAI,MAAM,YAAY,KAAK,CAAC,oBAAoB,MAAM,IAAI,GAAG;AAC5D,YAAM,SAAS,kBAAAD,QAAK,KAAK,YAAY,MAAM,IAAI;AAC/C,YAAM,cAAc,QAAQ,SAAS,KAAK;AAAA,IAC3C;AAAA,EACD;AACD;AAEA,eAAe,aAAa,UAA4C;AACvE,QAAM,UAAU,eAAAC,QAAG,aAAa,UAAU,OAAO;AACjD,QAAM,aAAS,YAAAC,OAAU,OAAO;AAChC,SAAO,YAAY,MAAM;AAC1B;AAEA,SAAS,oBAAoB,MAAuB;AACnD,QAAM,WAAW,CAAC,gBAAgB,QAAQ,QAAQ,SAAS,UAAU;AACrE,SAAO,SAAS,SAAS,IAAI,KAAK,KAAK,WAAW,GAAG;AACtD;;;AElDA,IAAAC,oBAAiB;AAGV,SAAS,aAAa,kBAAoD;AAChF,QAAM,gBAAgC,CAAC;AACvC,QAAM,cAAc,OAAO,KAAK,gBAAgB,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAEpF,aAAW,aAAa,aAAa;AACpC,UAAM,EAAE,QAAQ,aAAa,IAAI,iBAAiB,SAAS;AAG3D,UAAM,cAAc,gBAAgB,WAAW,aAAa,gBAAgB;AAG5E,UAAM,eAAe,aAAa,aAAa,MAAM;AAGrD,UAAM,kBAAkB,uBAAuB,YAAY;AAE3D,kBAAc,KAAK;AAAA,MAClB;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AAAA,EACF;AAEA,SAAO;AACR;AAEA,SAAS,gBACR,WACA,gBACA,kBACoB;AACpB,QAAM,UAA6B,CAAC;AAEpC,aAAW,mBAAmB,gBAAgB;AAC7C,QAAI,oBAAoB,UAAW;AAGnC,UAAM,WAAW,kBAAAC,QAAK,SAAS,iBAAiB,SAAS;AACzD,QAAI,CAAC,SAAS,WAAW,IAAI,KAAK,CAAC,kBAAAA,QAAK,WAAW,QAAQ,GAAG;AAC7D,YAAM,eAAe,iBAAiB,eAAe,EAAE;AAGvD,YAAM,aAAa,aAAa,OAAO,SAAS;AAChD,UAAI,eAAe,eAAe;AACjC,gBAAQ,KAAK,YAAY;AAAA,MAC1B;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,aAAa,SAA4B,OAAyC;AAC1F,MAAI,QAAQ,WAAW,GAAG;AACzB,WAAO;AAAA,EACR;AAGA,MAAI,SAA0B,EAAE,SAAS,MAAM,QAAQ;AAEvD,aAAW,UAAU,SAAS;AAC7B,aAAS,gBAAgB,QAAQ,MAAM;AAAA,EACxC;AAGA,WAAS,gBAAgB,QAAQ,KAAK;AAEtC,SAAO;AACR;AAEA,SAAS,gBAAgB,MAAuB,UAA4C;AAC3F,QAAM,SAA0B;AAAA,IAC/B,SAAS,SAAS,WAAW,KAAK;AAAA,IAClC,aAAa,SAAS,eAAe,KAAK;AAAA,EAC3C;AAGA,MAAI,KAAK,SAAS,SAAS,OAAO;AACjC,WAAO,QAAQ;AAAA,MACd,OAAO,SAAS,OAAO,SAAS,KAAK,OAAO,SAAS;AAAA,MACrD,SAAS,YAAY,KAAK,OAAO,SAAS,SAAS,OAAO,OAAO;AAAA,IAClE;AAAA,EACD;AAGA,MAAI,KAAK,WAAW,SAAS,SAAS;AACrC,WAAO,UAAU;AAAA,MAChB,OAAO,iBAAiB,KAAK,SAAS,OAAO,SAAS,SAAS,KAAK;AAAA,MACpE,MAAM,iBAAiB,KAAK,SAAS,MAAM,SAAS,SAAS,IAAI;AAAA,MACjE,MAAM,SAAS,SAAS,QAAQ,KAAK,SAAS,QAAQ;AAAA,IACvD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,YAAe,MAAY,UAAqB;AACxD,QAAM,SAAc,CAAC;AACrB,MAAI,KAAM,QAAO,KAAK,GAAG,IAAI;AAC7B,MAAI,SAAU,QAAO,KAAK,GAAG,QAAQ;AACrC,SAAO;AACR;AAEA,SAAS,iBAAiB,MAAqB,UAAuC;AACrF,QAAM,SAAuB,CAAC;AAC9B,MAAI,KAAM,QAAO,KAAK,GAAG,IAAI;AAC7B,MAAI,SAAU,QAAO,KAAK,GAAG,QAAQ;AACrC,SAAO;AACR;AAEA,SAAS,uBAAuB,QAAmC;AAClE,SAAO,OAAO,OAAO,WAAW,CAAC;AAClC;;;ARvGA,eAAsB,aAAa,YAAoB,SAAsC;AAC5F,QAAM,eAAe,kBAAAC,QAAK,QAAQ,UAAU;AAE5C,UAAQ,IAAI,kCAAkC,YAAY;AAAA,CAAI;AAE9D,MAAI;AACH,UAAM,UAAU,cAAc;AAAA,MAC7B,kBAAkB,QAAQ;AAAA,MAC1B,SAAS;AAAA,IACV,CAAC;AAED,UAAM,UAAU,eAAe,SAAS,YAAY;AAEpD,QAAI,QAAQ,WAAW,GAAG;AACzB,cAAQ,IAAI,4BAA4B;AACxC,cAAQ,KAAK,CAAC;AAAA,IACf;AAEA,UAAM,aAAa,MAAM,sBAAsB,YAAY;AAC3D,UAAM,gBAAgB,aAAa,UAAU;AAE7C,UAAM,UAAU,SAAS,SAAS,eAAe,YAAY;AAE7D,UAAM,WAAW,gBAAgB,SAAS;AAAA,MACzC,OAAO,QAAQ,UAAU;AAAA,IAC1B,CAAC;AAED,YAAQ,KAAK,QAAQ;AAAA,EACtB,SAAS,OAAO;AACf,QAAI,iBAAiB,OAAO;AAC3B,cAAQ,MAAM,UAAU,MAAM,OAAO,EAAE;AAAA,IACxC,OAAO;AACN,cAAQ,MAAM,2BAA2B;AAAA,IAC1C;AACA,YAAQ,KAAK,CAAC;AAAA,EACf;AACD;;;AD5CA,IAAM,UAAU,IAAI,yBAAQ;AAE5B,QACE,KAAK,WAAW,EAChB,YAAY,8DAA8D,EAC1E,QAAQ,OAAO;AAEjB,QACE,QAAQ,OAAO,EACf,YAAY,oDAAoD,EAChE,SAAS,UAAU,iBAAiB,GAAG,EACvC,OAAO,uBAAuB,uBAAuB,EACrD,OAAO,cAAc,wBAAwB,EAC7C,OAAO,YAAY;AAErB,QAAQ,MAAM;","names":["import_node_path","path","import_node_path","chalk","path","violations","import_node_path","path","fs","parseYaml","import_node_path","path","path"]}
package/dist/cli/index.js CHANGED
@@ -180,6 +180,16 @@ function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
180
180
  }
181
181
  return null;
182
182
  }
183
+ function getPackageName(moduleSpecifier) {
184
+ if (moduleSpecifier.startsWith("@")) {
185
+ const parts = moduleSpecifier.split("/");
186
+ if (parts.length >= 2) {
187
+ return `${parts[0]}/${parts[1]}`;
188
+ }
189
+ return moduleSpecifier;
190
+ }
191
+ return moduleSpecifier.split("/")[0];
192
+ }
183
193
  function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
184
194
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
185
195
  const sourceDir = path.dirname(sourceFile);
@@ -187,10 +197,21 @@ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
187
197
  return minimatch(pathToMatch, resolvedPattern, { matchBase: true });
188
198
  }
189
199
  if (pattern.includes("*")) {
200
+ const packageName = getPackageName(pathToMatch);
201
+ if (minimatch(packageName, pattern, { matchBase: true })) {
202
+ return true;
203
+ }
190
204
  return minimatch(pathToMatch, pattern, { matchBase: true });
191
205
  }
192
- if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
193
- return true;
206
+ const pathPackageName = getPackageName(pathToMatch);
207
+ const patternPackageName = getPackageName(pattern);
208
+ if (pathPackageName === patternPackageName) {
209
+ if (pattern === patternPackageName) {
210
+ return true;
211
+ }
212
+ if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
213
+ return true;
214
+ }
194
215
  }
195
216
  return false;
196
217
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/check.ts","../../src/core/import-collector.ts","../../src/core/project.ts","../../src/evaluator/import-boundary.ts","../../src/evaluator/index.ts","../../src/reporter/console.ts","../../src/rules/loader.ts","../../src/rules/schema.ts","../../src/rules/resolver.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n\t.name(\"zonefence\")\n\t.description(\"Folder-based architecture guardrails for TypeScript projects\")\n\t.version(\"0.1.0\");\n\nprogram\n\t.command(\"check\")\n\t.description(\"Check import boundaries in the specified directory\")\n\t.argument(\"[path]\", \"Path to check\", \".\")\n\t.option(\"-c, --config <path>\", \"Path to tsconfig.json\")\n\t.option(\"--no-color\", \"Disable colored output\")\n\t.action(checkCommand);\n\nprogram.parse();\n","import path from \"node:path\";\nimport { collectImports } from \"../../core/import-collector.js\";\nimport { createProject } from \"../../core/project.js\";\nimport { evaluate } from \"../../evaluator/index.js\";\nimport { reportToConsole } from \"../../reporter/console.js\";\nimport { loadRulesForDirectory } from \"../../rules/loader.js\";\nimport { resolveRules } from \"../../rules/resolver.js\";\n\nexport interface CheckOptions {\n\tconfig?: string;\n\tcolor?: boolean;\n}\n\nexport async function checkCommand(targetPath: string, options: CheckOptions): Promise<void> {\n\tconst absolutePath = path.resolve(targetPath);\n\n\tconsole.log(`Checking import boundaries in: ${absolutePath}\\n`);\n\n\ttry {\n\t\tconst project = createProject({\n\t\t\ttsConfigFilePath: options.config,\n\t\t\trootDir: absolutePath,\n\t\t});\n\n\t\tconst imports = collectImports(project, absolutePath);\n\n\t\tif (imports.length === 0) {\n\t\t\tconsole.log(\"No imports found to check.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\tconst rulesByDir = await loadRulesForDirectory(absolutePath);\n\t\tconst resolvedRules = resolveRules(rulesByDir);\n\n\t\tconst results = evaluate(imports, resolvedRules, absolutePath);\n\n\t\tconst exitCode = reportToConsole(results, {\n\t\t\tcolor: options.color !== false,\n\t\t});\n\n\t\tprocess.exit(exitCode);\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error(\"An unknown error occurred\");\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// Exact match for external packages\n\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport type { EvaluationResult, Violation } from \"../evaluator/types.js\";\nimport type { ReporterOptions } from \"./types.js\";\n\nexport function reportToConsole(result: EvaluationResult, options: ReporterOptions = {}): number {\n\tconst { violations, filesChecked, importsChecked } = result;\n\tconst useColor = options.color !== false;\n\n\tconst c = useColor\n\t\t? chalk\n\t\t: {\n\t\t\t\tred: (s: string) => s,\n\t\t\t\tyellow: (s: string) => s,\n\t\t\t\tgreen: (s: string) => s,\n\t\t\t\tcyan: (s: string) => s,\n\t\t\t\tgray: (s: string) => s,\n\t\t\t\tbold: (s: string) => s,\n\t\t\t\tdim: (s: string) => s,\n\t\t\t};\n\n\tif (violations.length === 0) {\n\t\tconsole.log(c.green(\"✓ No import boundary violations found\"));\n\t\tconsole.log(c.dim(` Checked ${importsChecked} imports across ${filesChecked} files`));\n\t\treturn 0;\n\t}\n\n\t// Group violations by file\n\tconst violationsByFile = groupByFile(violations);\n\n\tfor (const [filePath, fileViolations] of Object.entries(violationsByFile)) {\n\t\tconsole.log();\n\t\tconsole.log(c.bold(filePath));\n\n\t\tfor (const violation of fileViolations) {\n\t\t\tconst location = c.dim(`${violation.line}:${violation.column}`);\n\t\t\tconst errorType = c.red(\"error\");\n\t\t\tconst rule = c.dim(`(${violation.rule})`);\n\n\t\t\tconsole.log(` ${location} ${errorType} ${violation.message} ${rule}`);\n\n\t\t\tif (violation.designIntent) {\n\t\t\t\tconsole.log(c.cyan(` Design intent: ${violation.designIntent}`));\n\t\t\t}\n\n\t\t\tif (violation.suggestion) {\n\t\t\t\tconsole.log(c.yellow(` Suggestion: ${violation.suggestion}`));\n\t\t\t}\n\n\t\t\tconst ruleFile = path.relative(process.cwd(), violation.ruleFilePath);\n\t\t\tconsole.log(c.gray(` Rule: ${ruleFile}`));\n\t\t}\n\t}\n\n\tconsole.log();\n\n\tconst errorCount = violations.length;\n\tconst fileCount = Object.keys(violationsByFile).length;\n\tconst summary = `✖ ${errorCount} error${errorCount !== 1 ? \"s\" : \"\"} in ${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tconsole.log(c.red(c.bold(summary)));\n\n\treturn 1;\n}\n\nfunction groupByFile(violations: Violation[]): Record<string, Violation[]> {\n\tconst grouped: Record<string, Violation[]> = {};\n\n\tfor (const violation of violations) {\n\t\tconst relativePath = path.relative(process.cwd(), violation.sourceFile);\n\t\tif (!grouped[relativePath]) {\n\t\t\tgrouped[relativePath] = [];\n\t\t}\n\t\tgrouped[relativePath].push(violation);\n\t}\n\n\t// Sort violations within each file by line number\n\tfor (const violations of Object.values(grouped)) {\n\t\tviolations.sort((a, b) => a.line - b.line || a.column - b.column);\n\t}\n\n\treturn grouped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n"],"mappings":"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","names":["path","path","violations","path","path","path","path"]}
1
+ {"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/check.ts","../../src/core/import-collector.ts","../../src/core/project.ts","../../src/evaluator/import-boundary.ts","../../src/evaluator/index.ts","../../src/reporter/console.ts","../../src/rules/loader.ts","../../src/rules/schema.ts","../../src/rules/resolver.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n\t.name(\"zonefence\")\n\t.description(\"Folder-based architecture guardrails for TypeScript projects\")\n\t.version(\"0.1.0\");\n\nprogram\n\t.command(\"check\")\n\t.description(\"Check import boundaries in the specified directory\")\n\t.argument(\"[path]\", \"Path to check\", \".\")\n\t.option(\"-c, --config <path>\", \"Path to tsconfig.json\")\n\t.option(\"--no-color\", \"Disable colored output\")\n\t.action(checkCommand);\n\nprogram.parse();\n","import path from \"node:path\";\nimport { collectImports } from \"../../core/import-collector.js\";\nimport { createProject } from \"../../core/project.js\";\nimport { evaluate } from \"../../evaluator/index.js\";\nimport { reportToConsole } from \"../../reporter/console.js\";\nimport { loadRulesForDirectory } from \"../../rules/loader.js\";\nimport { resolveRules } from \"../../rules/resolver.js\";\n\nexport interface CheckOptions {\n\tconfig?: string;\n\tcolor?: boolean;\n}\n\nexport async function checkCommand(targetPath: string, options: CheckOptions): Promise<void> {\n\tconst absolutePath = path.resolve(targetPath);\n\n\tconsole.log(`Checking import boundaries in: ${absolutePath}\\n`);\n\n\ttry {\n\t\tconst project = createProject({\n\t\t\ttsConfigFilePath: options.config,\n\t\t\trootDir: absolutePath,\n\t\t});\n\n\t\tconst imports = collectImports(project, absolutePath);\n\n\t\tif (imports.length === 0) {\n\t\t\tconsole.log(\"No imports found to check.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\tconst rulesByDir = await loadRulesForDirectory(absolutePath);\n\t\tconst resolvedRules = resolveRules(rulesByDir);\n\n\t\tconst results = evaluate(imports, resolvedRules, absolutePath);\n\n\t\tconst exitCode = reportToConsole(results, {\n\t\t\tcolor: options.color !== false,\n\t\t});\n\n\t\tprocess.exit(exitCode);\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error(\"An unknown error occurred\");\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\t// For external packages with glob patterns, match against the package name\n\t\t// and also allow subpaths of matching packages\n\t\tconst packageName = getPackageName(pathToMatch);\n\t\tif (minimatch(packageName, pattern, { matchBase: true })) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also try matching the full path for more specific patterns\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport type { EvaluationResult, Violation } from \"../evaluator/types.js\";\nimport type { ReporterOptions } from \"./types.js\";\n\nexport function reportToConsole(result: EvaluationResult, options: ReporterOptions = {}): number {\n\tconst { violations, filesChecked, importsChecked } = result;\n\tconst useColor = options.color !== false;\n\n\tconst c = useColor\n\t\t? chalk\n\t\t: {\n\t\t\t\tred: (s: string) => s,\n\t\t\t\tyellow: (s: string) => s,\n\t\t\t\tgreen: (s: string) => s,\n\t\t\t\tcyan: (s: string) => s,\n\t\t\t\tgray: (s: string) => s,\n\t\t\t\tbold: (s: string) => s,\n\t\t\t\tdim: (s: string) => s,\n\t\t\t};\n\n\tif (violations.length === 0) {\n\t\tconsole.log(c.green(\"✓ No import boundary violations found\"));\n\t\tconsole.log(c.dim(` Checked ${importsChecked} imports across ${filesChecked} files`));\n\t\treturn 0;\n\t}\n\n\t// Group violations by file\n\tconst violationsByFile = groupByFile(violations);\n\n\tfor (const [filePath, fileViolations] of Object.entries(violationsByFile)) {\n\t\tconsole.log();\n\t\tconsole.log(c.bold(filePath));\n\n\t\tfor (const violation of fileViolations) {\n\t\t\tconst location = c.dim(`${violation.line}:${violation.column}`);\n\t\t\tconst errorType = c.red(\"error\");\n\t\t\tconst rule = c.dim(`(${violation.rule})`);\n\n\t\t\tconsole.log(` ${location} ${errorType} ${violation.message} ${rule}`);\n\n\t\t\tif (violation.designIntent) {\n\t\t\t\tconsole.log(c.cyan(` Design intent: ${violation.designIntent}`));\n\t\t\t}\n\n\t\t\tif (violation.suggestion) {\n\t\t\t\tconsole.log(c.yellow(` Suggestion: ${violation.suggestion}`));\n\t\t\t}\n\n\t\t\tconst ruleFile = path.relative(process.cwd(), violation.ruleFilePath);\n\t\t\tconsole.log(c.gray(` Rule: ${ruleFile}`));\n\t\t}\n\t}\n\n\tconsole.log();\n\n\tconst errorCount = violations.length;\n\tconst fileCount = Object.keys(violationsByFile).length;\n\tconst summary = `✖ ${errorCount} error${errorCount !== 1 ? \"s\" : \"\"} in ${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tconsole.log(c.red(c.bold(summary)));\n\n\treturn 1;\n}\n\nfunction groupByFile(violations: Violation[]): Record<string, Violation[]> {\n\tconst grouped: Record<string, Violation[]> = {};\n\n\tfor (const violation of violations) {\n\t\tconst relativePath = path.relative(process.cwd(), violation.sourceFile);\n\t\tif (!grouped[relativePath]) {\n\t\t\tgrouped[relativePath] = [];\n\t\t}\n\t\tgrouped[relativePath].push(violation);\n\t}\n\n\t// Sort violations within each file by line number\n\tfor (const violations of Object.values(grouped)) {\n\t\tviolations.sort((a, b) => a.line - b.line || a.column - b.column);\n\t}\n\n\treturn grouped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n"],"mappings":"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","names":["path","path","violations","path","path","path","path"]}
package/dist/index.cjs CHANGED
@@ -334,6 +334,16 @@ function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
334
334
  }
335
335
  return null;
336
336
  }
337
+ function getPackageName(moduleSpecifier) {
338
+ if (moduleSpecifier.startsWith("@")) {
339
+ const parts = moduleSpecifier.split("/");
340
+ if (parts.length >= 2) {
341
+ return `${parts[0]}/${parts[1]}`;
342
+ }
343
+ return moduleSpecifier;
344
+ }
345
+ return moduleSpecifier.split("/")[0];
346
+ }
337
347
  function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
338
348
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
339
349
  const sourceDir = import_node_path3.default.dirname(sourceFile);
@@ -341,10 +351,21 @@ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
341
351
  return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern, { matchBase: true });
342
352
  }
343
353
  if (pattern.includes("*")) {
354
+ const packageName = getPackageName(pathToMatch);
355
+ if ((0, import_minimatch.minimatch)(packageName, pattern, { matchBase: true })) {
356
+ return true;
357
+ }
344
358
  return (0, import_minimatch.minimatch)(pathToMatch, pattern, { matchBase: true });
345
359
  }
346
- if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
347
- return true;
360
+ const pathPackageName = getPackageName(pathToMatch);
361
+ const patternPackageName = getPackageName(pattern);
362
+ if (pathPackageName === patternPackageName) {
363
+ if (pattern === patternPackageName) {
364
+ return true;
365
+ }
366
+ if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
367
+ return true;
368
+ }
348
369
  }
349
370
  return false;
350
371
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core/project.ts","../src/core/import-collector.ts","../src/rules/loader.ts","../src/rules/schema.ts","../src/rules/resolver.ts","../src/evaluator/import-boundary.ts","../src/evaluator/index.ts"],"sourcesContent":["export { checkProject } from \"./core/project.js\";\nexport { collectImports } from \"./core/import-collector.js\";\nexport { loadRules } from \"./rules/loader.js\";\nexport { resolveRules } from \"./rules/resolver.js\";\nexport { evaluate } from \"./evaluator/index.js\";\n\nexport type { ImportInfo, ProjectOptions } from \"./core/types.js\";\nexport type { ZoneFenceConfig, ImportRule } from \"./rules/types.js\";\nexport type { EvaluationResult, Violation } from \"./evaluator/types.js\";\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// Exact match for external packages\n\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n"],"mappings":"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","names":["fs","parseYaml","import_node_path","path","import_node_path","path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core/project.ts","../src/core/import-collector.ts","../src/rules/loader.ts","../src/rules/schema.ts","../src/rules/resolver.ts","../src/evaluator/import-boundary.ts","../src/evaluator/index.ts"],"sourcesContent":["export { checkProject } from \"./core/project.js\";\nexport { collectImports } from \"./core/import-collector.js\";\nexport { loadRules } from \"./rules/loader.js\";\nexport { resolveRules } from \"./rules/resolver.js\";\nexport { evaluate } from \"./evaluator/index.js\";\n\nexport type { ImportInfo, ProjectOptions } from \"./core/types.js\";\nexport type { ZoneFenceConfig, ImportRule } from \"./rules/types.js\";\nexport type { EvaluationResult, Violation } from \"./evaluator/types.js\";\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\t// For external packages with glob patterns, match against the package name\n\t\t// and also allow subpaths of matching packages\n\t\tconst packageName = getPackageName(pathToMatch);\n\t\tif (minimatch(packageName, pattern, { matchBase: true })) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also try matching the full path for more specific patterns\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n"],"mappings":"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","names":["fs","parseYaml","import_node_path","path","import_node_path","path"]}
package/dist/index.js CHANGED
@@ -294,6 +294,16 @@ function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
294
294
  }
295
295
  return null;
296
296
  }
297
+ function getPackageName(moduleSpecifier) {
298
+ if (moduleSpecifier.startsWith("@")) {
299
+ const parts = moduleSpecifier.split("/");
300
+ if (parts.length >= 2) {
301
+ return `${parts[0]}/${parts[1]}`;
302
+ }
303
+ return moduleSpecifier;
304
+ }
305
+ return moduleSpecifier.split("/")[0];
306
+ }
297
307
  function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
298
308
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
299
309
  const sourceDir = path3.dirname(sourceFile);
@@ -301,10 +311,21 @@ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
301
311
  return minimatch(pathToMatch, resolvedPattern, { matchBase: true });
302
312
  }
303
313
  if (pattern.includes("*")) {
314
+ const packageName = getPackageName(pathToMatch);
315
+ if (minimatch(packageName, pattern, { matchBase: true })) {
316
+ return true;
317
+ }
304
318
  return minimatch(pathToMatch, pattern, { matchBase: true });
305
319
  }
306
- if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
307
- return true;
320
+ const pathPackageName = getPackageName(pathToMatch);
321
+ const patternPackageName = getPackageName(pattern);
322
+ if (pathPackageName === patternPackageName) {
323
+ if (pattern === patternPackageName) {
324
+ return true;
325
+ }
326
+ if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
327
+ return true;
328
+ }
308
329
  }
309
330
  return false;
310
331
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/project.ts","../src/core/import-collector.ts","../src/rules/loader.ts","../src/rules/schema.ts","../src/rules/resolver.ts","../src/evaluator/import-boundary.ts","../src/evaluator/index.ts"],"sourcesContent":["import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// Exact match for external packages\n\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n"],"mappings":"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","names":["path","path"]}
1
+ {"version":3,"sources":["../src/core/project.ts","../src/core/import-collector.ts","../src/rules/loader.ts","../src/rules/schema.ts","../src/rules/resolver.ts","../src/evaluator/import-boundary.ts","../src/evaluator/index.ts"],"sourcesContent":["import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\t// For external packages with glob patterns, match against the package name\n\t\t// and also allow subpaths of matching packages\n\t\tconst packageName = getPackageName(pathToMatch);\n\t\tif (minimatch(packageName, pattern, { matchBase: true })) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also try matching the full path for more specific patterns\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n"],"mappings":"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","names":["path","path"]}
package/package.json CHANGED
@@ -1,68 +1,58 @@
1
1
  {
2
- "name": "zonefence",
3
- "version": "0.0.2",
4
- "description": "Folder-based architecture guardrails for TypeScript projects",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "bin": {
9
- "zonefence": "./bin/zonefence.js"
10
- },
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.js",
15
- "require": "./dist/index.cjs"
16
- }
17
- },
18
- "files": [
19
- "dist",
20
- "bin"
21
- ],
22
- "scripts": {
23
- "build": "tsup",
24
- "dev": "tsup --watch",
25
- "test": "vitest",
26
- "lint": "biome check src",
27
- "format": "biome format --write src",
28
- "typecheck": "tsc --noEmit",
29
- "prepublishOnly": "npm run build"
30
- },
31
- "keywords": [
32
- "architecture",
33
- "typescript",
34
- "cli",
35
- "linter",
36
- "import",
37
- "dependency",
38
- "boundary"
39
- ],
40
- "author": "",
41
- "license": "MIT",
42
- "repository": {
43
- "type": "git",
44
- "url": "git+https://github.com/Glider2355/zonefence.git"
45
- },
46
- "bugs": {
47
- "url": "https://github.com/Glider2355/zonefence/issues"
48
- },
49
- "homepage": "https://github.com/Glider2355/zonefence#readme",
50
- "engines": {
51
- "node": ">=18.0.0"
52
- },
53
- "dependencies": {
54
- "chalk": "^5.3.0",
55
- "commander": "^12.0.0",
56
- "minimatch": "^9.0.0",
57
- "ts-morph": "^24.0.0",
58
- "yaml": "^2.4.0",
59
- "zod": "^3.23.0"
60
- },
61
- "devDependencies": {
62
- "@biomejs/biome": "^1.9.0",
63
- "@types/node": "^20.0.0",
64
- "tsup": "^8.0.0",
65
- "typescript": "^5.4.0",
66
- "vitest": "^1.0.0"
67
- }
2
+ "name": "zonefence",
3
+ "version": "0.0.3",
4
+ "description": "Folder-based architecture guardrails for TypeScript projects",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "zonefence": "./bin/zonefence.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": ["dist", "bin"],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest",
23
+ "lint": "biome check src",
24
+ "format": "biome format --write src",
25
+ "typecheck": "tsc --noEmit",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "keywords": ["architecture", "typescript", "cli", "linter", "import", "dependency", "boundary"],
29
+ "author": "",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/Glider2355/zonefence.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/Glider2355/zonefence/issues"
37
+ },
38
+ "homepage": "https://github.com/Glider2355/zonefence#readme",
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "dependencies": {
43
+ "chalk": "^5.3.0",
44
+ "commander": "^12.0.0",
45
+ "minimatch": "^9.0.0",
46
+ "ts-morph": "^24.0.0",
47
+ "yaml": "^2.4.0",
48
+ "zod": "^3.23.0"
49
+ },
50
+ "devDependencies": {
51
+ "@biomejs/biome": "^1.9.0",
52
+ "@types/node": "^20.0.0",
53
+ "lefthook": "^2.0.15",
54
+ "tsup": "^8.0.0",
55
+ "typescript": "^5.4.0",
56
+ "vitest": "^1.0.0"
57
+ }
68
58
  }