zonefence 0.0.3 → 0.0.5

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.
@@ -89,10 +89,13 @@ function resolveModulePath(decl, moduleSpecifier, _sourceFilePath) {
89
89
  return null;
90
90
  }
91
91
  function isExternalImport(moduleSpecifier, resolvedPath) {
92
- if (resolvedPath !== null) {
92
+ if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
93
93
  return false;
94
94
  }
95
- if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
95
+ if (resolvedPath?.includes("node_modules")) {
96
+ return true;
97
+ }
98
+ if (resolvedPath !== null) {
96
99
  return false;
97
100
  }
98
101
  return true;
@@ -134,13 +137,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
134
137
  const allowRules = imports.allow ?? [];
135
138
  const denyRules = imports.deny ?? [];
136
139
  const pathToMatch = getPathToMatch(importInfo, rootDir);
140
+ const isExternal = importInfo.isExternal;
137
141
  if (mode === "allow-first") {
138
- const denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);
142
+ const denyMatch = findMatchingRule(
143
+ pathToMatch,
144
+ denyRules,
145
+ importInfo.sourceFile,
146
+ rootDir,
147
+ isExternal
148
+ );
139
149
  if (denyMatch) {
140
150
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
141
151
  }
142
152
  if (allowRules.length > 0) {
143
- const allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);
153
+ const allowMatch = findMatchingRule(
154
+ pathToMatch,
155
+ allowRules,
156
+ importInfo.sourceFile,
157
+ rootDir,
158
+ isExternal
159
+ );
144
160
  if (!allowMatch) {
145
161
  return createViolation(
146
162
  importInfo,
@@ -154,11 +170,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
154
170
  }
155
171
  }
156
172
  } else {
157
- const allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);
173
+ const allowMatch = findMatchingRule(
174
+ pathToMatch,
175
+ allowRules,
176
+ importInfo.sourceFile,
177
+ rootDir,
178
+ isExternal
179
+ );
158
180
  if (allowMatch) {
159
181
  return null;
160
182
  }
161
- const denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);
183
+ const denyMatch = findMatchingRule(
184
+ pathToMatch,
185
+ denyRules,
186
+ importInfo.sourceFile,
187
+ rootDir,
188
+ isExternal
189
+ );
162
190
  if (denyMatch) {
163
191
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
164
192
  }
@@ -195,9 +223,9 @@ function getPathToMatch(importInfo, rootDir) {
195
223
  }
196
224
  return importInfo.moduleSpecifier;
197
225
  }
198
- function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
226
+ function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
199
227
  for (const rule of rules) {
200
- if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
228
+ if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
201
229
  return rule;
202
230
  }
203
231
  }
@@ -213,18 +241,20 @@ function getPackageName(moduleSpecifier) {
213
241
  }
214
242
  return moduleSpecifier.split("/")[0];
215
243
  }
216
- function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
244
+ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
217
245
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
218
246
  const sourceDir = import_node_path.default.dirname(sourceFile);
219
247
  const resolvedPattern = import_node_path.default.relative(rootDir, import_node_path.default.resolve(sourceDir, pattern));
220
- return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern, { matchBase: true });
248
+ return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern);
221
249
  }
222
250
  if (pattern.includes("*")) {
223
- const packageName = getPackageName(pathToMatch);
224
- if ((0, import_minimatch.minimatch)(packageName, pattern, { matchBase: true })) {
225
- return true;
251
+ if (isExternal) {
252
+ const packageName = getPackageName(pathToMatch);
253
+ if ((0, import_minimatch.minimatch)(packageName, pattern)) {
254
+ return true;
255
+ }
226
256
  }
227
- return (0, import_minimatch.minimatch)(pathToMatch, pattern, { matchBase: true });
257
+ return (0, import_minimatch.minimatch)(pathToMatch, pattern);
228
258
  }
229
259
  const pathPackageName = getPackageName(pathToMatch);
230
260
  const patternPackageName = getPackageName(pattern);
@@ -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\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"]}
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\n/** @internal Exported for testing */\nexport function isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If it starts with . or /, it's a local import\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// If resolved path contains node_modules, it's an external package\n\tif (resolvedPath?.includes(\"node_modules\")) {\n\t\treturn true;\n\t}\n\n\t// If we have a resolved path that's not in node_modules, it's local\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package (unresolved bare specifier)\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\tconst isExternal = importInfo.isExternal;\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\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(\n\t\t\t\tpathToMatch,\n\t\t\t\tallowRules,\n\t\t\t\timportInfo.sourceFile,\n\t\t\t\trootDir,\n\t\t\t\tisExternal,\n\t\t\t);\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(\n\t\t\tpathToMatch,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\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\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\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\tisExternal: boolean,\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);\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\tif (isExternal) {\n\t\t\t// For external packages, also match against the package name\n\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\tif (minimatch(packageName, pattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// For internal paths (and external full path match), use direct minimatch\n\t\treturn minimatch(pathToMatch, pattern);\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":["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
@@ -66,10 +66,13 @@ function resolveModulePath(decl, moduleSpecifier, _sourceFilePath) {
66
66
  return null;
67
67
  }
68
68
  function isExternalImport(moduleSpecifier, resolvedPath) {
69
- if (resolvedPath !== null) {
69
+ if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
70
70
  return false;
71
71
  }
72
- if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
72
+ if (resolvedPath?.includes("node_modules")) {
73
+ return true;
74
+ }
75
+ if (resolvedPath !== null) {
73
76
  return false;
74
77
  }
75
78
  return true;
@@ -111,13 +114,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
111
114
  const allowRules = imports.allow ?? [];
112
115
  const denyRules = imports.deny ?? [];
113
116
  const pathToMatch = getPathToMatch(importInfo, rootDir);
117
+ const isExternal = importInfo.isExternal;
114
118
  if (mode === "allow-first") {
115
- const denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);
119
+ const denyMatch = findMatchingRule(
120
+ pathToMatch,
121
+ denyRules,
122
+ importInfo.sourceFile,
123
+ rootDir,
124
+ isExternal
125
+ );
116
126
  if (denyMatch) {
117
127
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
118
128
  }
119
129
  if (allowRules.length > 0) {
120
- const allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);
130
+ const allowMatch = findMatchingRule(
131
+ pathToMatch,
132
+ allowRules,
133
+ importInfo.sourceFile,
134
+ rootDir,
135
+ isExternal
136
+ );
121
137
  if (!allowMatch) {
122
138
  return createViolation(
123
139
  importInfo,
@@ -131,11 +147,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
131
147
  }
132
148
  }
133
149
  } else {
134
- const allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);
150
+ const allowMatch = findMatchingRule(
151
+ pathToMatch,
152
+ allowRules,
153
+ importInfo.sourceFile,
154
+ rootDir,
155
+ isExternal
156
+ );
135
157
  if (allowMatch) {
136
158
  return null;
137
159
  }
138
- const denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);
160
+ const denyMatch = findMatchingRule(
161
+ pathToMatch,
162
+ denyRules,
163
+ importInfo.sourceFile,
164
+ rootDir,
165
+ isExternal
166
+ );
139
167
  if (denyMatch) {
140
168
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
141
169
  }
@@ -172,9 +200,9 @@ function getPathToMatch(importInfo, rootDir) {
172
200
  }
173
201
  return importInfo.moduleSpecifier;
174
202
  }
175
- function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
203
+ function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
176
204
  for (const rule of rules) {
177
- if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
205
+ if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
178
206
  return rule;
179
207
  }
180
208
  }
@@ -190,18 +218,20 @@ function getPackageName(moduleSpecifier) {
190
218
  }
191
219
  return moduleSpecifier.split("/")[0];
192
220
  }
193
- function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
221
+ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
194
222
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
195
223
  const sourceDir = path.dirname(sourceFile);
196
224
  const resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));
197
- return minimatch(pathToMatch, resolvedPattern, { matchBase: true });
225
+ return minimatch(pathToMatch, resolvedPattern);
198
226
  }
199
227
  if (pattern.includes("*")) {
200
- const packageName = getPackageName(pathToMatch);
201
- if (minimatch(packageName, pattern, { matchBase: true })) {
202
- return true;
228
+ if (isExternal) {
229
+ const packageName = getPackageName(pathToMatch);
230
+ if (minimatch(packageName, pattern)) {
231
+ return true;
232
+ }
203
233
  }
204
- return minimatch(pathToMatch, pattern, { matchBase: true });
234
+ return minimatch(pathToMatch, pattern);
205
235
  }
206
236
  const pathPackageName = getPackageName(pathToMatch);
207
237
  const patternPackageName = getPackageName(pattern);
@@ -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\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"]}
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\n/** @internal Exported for testing */\nexport function isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If it starts with . or /, it's a local import\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// If resolved path contains node_modules, it's an external package\n\tif (resolvedPath?.includes(\"node_modules\")) {\n\t\treturn true;\n\t}\n\n\t// If we have a resolved path that's not in node_modules, it's local\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package (unresolved bare specifier)\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\tconst isExternal = importInfo.isExternal;\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\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(\n\t\t\t\tpathToMatch,\n\t\t\t\tallowRules,\n\t\t\t\timportInfo.sourceFile,\n\t\t\t\trootDir,\n\t\t\t\tisExternal,\n\t\t\t);\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(\n\t\t\tpathToMatch,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\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\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\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\tisExternal: boolean,\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);\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\tif (isExternal) {\n\t\t\t// For external packages, also match against the package name\n\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\tif (minimatch(packageName, pattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// For internal paths (and external full path match), use direct minimatch\n\t\treturn minimatch(pathToMatch, pattern);\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
@@ -117,10 +117,13 @@ function resolveModulePath(decl, moduleSpecifier, _sourceFilePath) {
117
117
  return null;
118
118
  }
119
119
  function isExternalImport(moduleSpecifier, resolvedPath) {
120
- if (resolvedPath !== null) {
120
+ if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
121
121
  return false;
122
122
  }
123
- if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
123
+ if (resolvedPath?.includes("node_modules")) {
124
+ return true;
125
+ }
126
+ if (resolvedPath !== null) {
124
127
  return false;
125
128
  }
126
129
  return true;
@@ -265,13 +268,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
265
268
  const allowRules = imports.allow ?? [];
266
269
  const denyRules = imports.deny ?? [];
267
270
  const pathToMatch = getPathToMatch(importInfo, rootDir);
271
+ const isExternal = importInfo.isExternal;
268
272
  if (mode === "allow-first") {
269
- const denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);
273
+ const denyMatch = findMatchingRule(
274
+ pathToMatch,
275
+ denyRules,
276
+ importInfo.sourceFile,
277
+ rootDir,
278
+ isExternal
279
+ );
270
280
  if (denyMatch) {
271
281
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
272
282
  }
273
283
  if (allowRules.length > 0) {
274
- const allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);
284
+ const allowMatch = findMatchingRule(
285
+ pathToMatch,
286
+ allowRules,
287
+ importInfo.sourceFile,
288
+ rootDir,
289
+ isExternal
290
+ );
275
291
  if (!allowMatch) {
276
292
  return createViolation(
277
293
  importInfo,
@@ -285,11 +301,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
285
301
  }
286
302
  }
287
303
  } else {
288
- const allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);
304
+ const allowMatch = findMatchingRule(
305
+ pathToMatch,
306
+ allowRules,
307
+ importInfo.sourceFile,
308
+ rootDir,
309
+ isExternal
310
+ );
289
311
  if (allowMatch) {
290
312
  return null;
291
313
  }
292
- const denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);
314
+ const denyMatch = findMatchingRule(
315
+ pathToMatch,
316
+ denyRules,
317
+ importInfo.sourceFile,
318
+ rootDir,
319
+ isExternal
320
+ );
293
321
  if (denyMatch) {
294
322
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
295
323
  }
@@ -326,9 +354,9 @@ function getPathToMatch(importInfo, rootDir) {
326
354
  }
327
355
  return importInfo.moduleSpecifier;
328
356
  }
329
- function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
357
+ function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
330
358
  for (const rule of rules) {
331
- if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
359
+ if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
332
360
  return rule;
333
361
  }
334
362
  }
@@ -344,18 +372,20 @@ function getPackageName(moduleSpecifier) {
344
372
  }
345
373
  return moduleSpecifier.split("/")[0];
346
374
  }
347
- function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
375
+ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
348
376
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
349
377
  const sourceDir = import_node_path3.default.dirname(sourceFile);
350
378
  const resolvedPattern = import_node_path3.default.relative(rootDir, import_node_path3.default.resolve(sourceDir, pattern));
351
- return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern, { matchBase: true });
379
+ return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern);
352
380
  }
353
381
  if (pattern.includes("*")) {
354
- const packageName = getPackageName(pathToMatch);
355
- if ((0, import_minimatch.minimatch)(packageName, pattern, { matchBase: true })) {
356
- return true;
382
+ if (isExternal) {
383
+ const packageName = getPackageName(pathToMatch);
384
+ if ((0, import_minimatch.minimatch)(packageName, pattern)) {
385
+ return true;
386
+ }
357
387
  }
358
- return (0, import_minimatch.minimatch)(pathToMatch, pattern, { matchBase: true });
388
+ return (0, import_minimatch.minimatch)(pathToMatch, pattern);
359
389
  }
360
390
  const pathPackageName = getPackageName(pathToMatch);
361
391
  const patternPackageName = getPackageName(pattern);
@@ -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\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"]}
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\n/** @internal Exported for testing */\nexport function isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If it starts with . or /, it's a local import\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// If resolved path contains node_modules, it's an external package\n\tif (resolvedPath?.includes(\"node_modules\")) {\n\t\treturn true;\n\t}\n\n\t// If we have a resolved path that's not in node_modules, it's local\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package (unresolved bare specifier)\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\tconst isExternal = importInfo.isExternal;\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\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(\n\t\t\t\tpathToMatch,\n\t\t\t\tallowRules,\n\t\t\t\timportInfo.sourceFile,\n\t\t\t\trootDir,\n\t\t\t\tisExternal,\n\t\t\t);\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(\n\t\t\tpathToMatch,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\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\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\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\tisExternal: boolean,\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);\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\tif (isExternal) {\n\t\t\t// For external packages, also match against the package name\n\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\tif (minimatch(packageName, pattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// For internal paths (and external full path match), use direct minimatch\n\t\treturn minimatch(pathToMatch, pattern);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,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;AAEO,SAAS,aAAa,SAAkC;AAC9D,SAAO,cAAc,OAAO;AAC7B;;;AClBO,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;AAGO,SAAS,iBAAiB,iBAAyB,cAAsC;AAE/F,MAAI,gBAAgB,WAAW,GAAG,KAAK,gBAAgB,WAAW,GAAG,GAAG;AACvE,WAAO;AAAA,EACR;AAGA,MAAI,cAAc,SAAS,cAAc,GAAG;AAC3C,WAAO;AAAA,EACR;AAGA,MAAI,iBAAiB,MAAM;AAC1B,WAAO;AAAA,EACR;AAGA,SAAO;AACR;;;ACzGA,qBAAe;AACf,uBAAiB;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;;;ADkBO,SAAS,UAAU,UAAmC;AAC5D,QAAM,UAAU,eAAAA,QAAG,aAAa,UAAU,OAAO;AACjD,QAAM,aAAS,YAAAC,OAAU,OAAO;AAChC,SAAO,YAAY,MAAM;AAC1B;;;AExDA,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;;;ACpHA,IAAAC,oBAAiB;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,QAAM,aAAa,WAAW;AAE9B,MAAI,SAAS,eAAe;AAE3B,UAAM,YAAY;AAAA,MACjB;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACD;AACA,QAAI,WAAW;AACd,aAAO,gBAAgB,YAAY,WAAW,cAAc,OAAO,WAAW;AAAA,IAC/E;AAGA,QAAI,WAAW,SAAS,GAAG;AAC1B,YAAM,aAAa;AAAA,QAClB;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX;AAAA,QACA;AAAA,MACD;AACA,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;AAAA,MAClB;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACD;AACA,QAAI,YAAY;AACf,aAAO;AAAA,IACR;AAEA,UAAM,YAAY;AAAA,MACjB;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACD;AACA,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,kBAAAC,QAAK,SAAS,KAAK,WAAW,QAAQ;AAEvD,QAAI,CAAC,SAAS,WAAW,IAAI,KAAK,CAAC,kBAAAA,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,kBAAAA,QAAK,SAAS,SAAS,QAAQ;AAEpD,aAAW,WAAW,KAAK,iBAAiB;AAC3C,YAAI,4BAAU,cAAc,OAAO,SAAK,4BAAU,kBAAAA,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,kBAAAA,QAAK,SAAS,SAAS,WAAW,YAAY;AAAA,EACtD;AAGA,SAAO,WAAW;AACnB;AAEA,SAAS,iBACR,aACA,OACA,YACA,SACA,YACoB;AACpB,aAAW,QAAQ,OAAO;AACzB,QAAI,eAAe,aAAa,KAAK,MAAM,YAAY,SAAS,UAAU,GAAG;AAC5E,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,SACA,YACU;AAEV,MAAI,QAAQ,WAAW,IAAI,KAAK,QAAQ,WAAW,KAAK,GAAG;AAE1D,UAAM,YAAY,kBAAAA,QAAK,QAAQ,UAAU;AACzC,UAAM,kBAAkB,kBAAAA,QAAK,SAAS,SAAS,kBAAAA,QAAK,QAAQ,WAAW,OAAO,CAAC;AAC/E,eAAO,4BAAU,aAAa,eAAe;AAAA,EAC9C;AAGA,MAAI,QAAQ,SAAS,GAAG,GAAG;AAC1B,QAAI,YAAY;AAGf,YAAM,cAAc,eAAe,WAAW;AAC9C,cAAI,4BAAU,aAAa,OAAO,GAAG;AACpC,eAAO;AAAA,MACR;AAAA,IACD;AAEA,eAAO,4BAAU,aAAa,OAAO;AAAA,EACtC;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;;;AChPO,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;","names":["fs","parseYaml","import_node_path","path","import_node_path","path"]}
package/dist/index.js CHANGED
@@ -77,10 +77,13 @@ function resolveModulePath(decl, moduleSpecifier, _sourceFilePath) {
77
77
  return null;
78
78
  }
79
79
  function isExternalImport(moduleSpecifier, resolvedPath) {
80
- if (resolvedPath !== null) {
80
+ if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
81
81
  return false;
82
82
  }
83
- if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
83
+ if (resolvedPath?.includes("node_modules")) {
84
+ return true;
85
+ }
86
+ if (resolvedPath !== null) {
84
87
  return false;
85
88
  }
86
89
  return true;
@@ -225,13 +228,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
225
228
  const allowRules = imports.allow ?? [];
226
229
  const denyRules = imports.deny ?? [];
227
230
  const pathToMatch = getPathToMatch(importInfo, rootDir);
231
+ const isExternal = importInfo.isExternal;
228
232
  if (mode === "allow-first") {
229
- const denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);
233
+ const denyMatch = findMatchingRule(
234
+ pathToMatch,
235
+ denyRules,
236
+ importInfo.sourceFile,
237
+ rootDir,
238
+ isExternal
239
+ );
230
240
  if (denyMatch) {
231
241
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
232
242
  }
233
243
  if (allowRules.length > 0) {
234
- const allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);
244
+ const allowMatch = findMatchingRule(
245
+ pathToMatch,
246
+ allowRules,
247
+ importInfo.sourceFile,
248
+ rootDir,
249
+ isExternal
250
+ );
235
251
  if (!allowMatch) {
236
252
  return createViolation(
237
253
  importInfo,
@@ -245,11 +261,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
245
261
  }
246
262
  }
247
263
  } else {
248
- const allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);
264
+ const allowMatch = findMatchingRule(
265
+ pathToMatch,
266
+ allowRules,
267
+ importInfo.sourceFile,
268
+ rootDir,
269
+ isExternal
270
+ );
249
271
  if (allowMatch) {
250
272
  return null;
251
273
  }
252
- const denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);
274
+ const denyMatch = findMatchingRule(
275
+ pathToMatch,
276
+ denyRules,
277
+ importInfo.sourceFile,
278
+ rootDir,
279
+ isExternal
280
+ );
253
281
  if (denyMatch) {
254
282
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
255
283
  }
@@ -286,9 +314,9 @@ function getPathToMatch(importInfo, rootDir) {
286
314
  }
287
315
  return importInfo.moduleSpecifier;
288
316
  }
289
- function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
317
+ function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
290
318
  for (const rule of rules) {
291
- if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
319
+ if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
292
320
  return rule;
293
321
  }
294
322
  }
@@ -304,18 +332,20 @@ function getPackageName(moduleSpecifier) {
304
332
  }
305
333
  return moduleSpecifier.split("/")[0];
306
334
  }
307
- function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
335
+ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
308
336
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
309
337
  const sourceDir = path3.dirname(sourceFile);
310
338
  const resolvedPattern = path3.relative(rootDir, path3.resolve(sourceDir, pattern));
311
- return minimatch(pathToMatch, resolvedPattern, { matchBase: true });
339
+ return minimatch(pathToMatch, resolvedPattern);
312
340
  }
313
341
  if (pattern.includes("*")) {
314
- const packageName = getPackageName(pathToMatch);
315
- if (minimatch(packageName, pattern, { matchBase: true })) {
316
- return true;
342
+ if (isExternal) {
343
+ const packageName = getPackageName(pathToMatch);
344
+ if (minimatch(packageName, pattern)) {
345
+ return true;
346
+ }
317
347
  }
318
- return minimatch(pathToMatch, pattern, { matchBase: true });
348
+ return minimatch(pathToMatch, pattern);
319
349
  }
320
350
  const pathPackageName = getPackageName(pathToMatch);
321
351
  const patternPackageName = getPackageName(pattern);
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\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"]}
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\n/** @internal Exported for testing */\nexport function isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If it starts with . or /, it's a local import\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// If resolved path contains node_modules, it's an external package\n\tif (resolvedPath?.includes(\"node_modules\")) {\n\t\treturn true;\n\t}\n\n\t// If we have a resolved path that's not in node_modules, it's local\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package (unresolved bare specifier)\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\tconst isExternal = importInfo.isExternal;\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\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(\n\t\t\t\tpathToMatch,\n\t\t\t\tallowRules,\n\t\t\t\timportInfo.sourceFile,\n\t\t\t\trootDir,\n\t\t\t\tisExternal,\n\t\t\t);\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(\n\t\t\tpathToMatch,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\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\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\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\tisExternal: boolean,\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);\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\tif (isExternal) {\n\t\t\t// For external packages, also match against the package name\n\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\tif (minimatch(packageName, pattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// For internal paths (and external full path match), use direct minimatch\n\t\treturn minimatch(pathToMatch, pattern);\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,6 +1,6 @@
1
1
  {
2
2
  "name": "zonefence",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Folder-based architecture guardrails for TypeScript projects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,7 +19,8 @@
19
19
  "scripts": {
20
20
  "build": "tsup",
21
21
  "dev": "tsup --watch",
22
- "test": "vitest",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
23
24
  "lint": "biome check src",
24
25
  "format": "biome format --write src",
25
26
  "typecheck": "tsc --noEmit",