zonefence 0.0.4 → 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.
- package/dist/cli/index.cjs +39 -12
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +39 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +39 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +39 -12
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/cli/index.cjs
CHANGED
|
@@ -137,13 +137,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
137
137
|
const allowRules = imports.allow ?? [];
|
|
138
138
|
const denyRules = imports.deny ?? [];
|
|
139
139
|
const pathToMatch = getPathToMatch(importInfo, rootDir);
|
|
140
|
+
const isExternal = importInfo.isExternal;
|
|
140
141
|
if (mode === "allow-first") {
|
|
141
|
-
const denyMatch = findMatchingRule(
|
|
142
|
+
const denyMatch = findMatchingRule(
|
|
143
|
+
pathToMatch,
|
|
144
|
+
denyRules,
|
|
145
|
+
importInfo.sourceFile,
|
|
146
|
+
rootDir,
|
|
147
|
+
isExternal
|
|
148
|
+
);
|
|
142
149
|
if (denyMatch) {
|
|
143
150
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
144
151
|
}
|
|
145
152
|
if (allowRules.length > 0) {
|
|
146
|
-
const allowMatch = findMatchingRule(
|
|
153
|
+
const allowMatch = findMatchingRule(
|
|
154
|
+
pathToMatch,
|
|
155
|
+
allowRules,
|
|
156
|
+
importInfo.sourceFile,
|
|
157
|
+
rootDir,
|
|
158
|
+
isExternal
|
|
159
|
+
);
|
|
147
160
|
if (!allowMatch) {
|
|
148
161
|
return createViolation(
|
|
149
162
|
importInfo,
|
|
@@ -157,11 +170,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
157
170
|
}
|
|
158
171
|
}
|
|
159
172
|
} else {
|
|
160
|
-
const allowMatch = findMatchingRule(
|
|
173
|
+
const allowMatch = findMatchingRule(
|
|
174
|
+
pathToMatch,
|
|
175
|
+
allowRules,
|
|
176
|
+
importInfo.sourceFile,
|
|
177
|
+
rootDir,
|
|
178
|
+
isExternal
|
|
179
|
+
);
|
|
161
180
|
if (allowMatch) {
|
|
162
181
|
return null;
|
|
163
182
|
}
|
|
164
|
-
const denyMatch = findMatchingRule(
|
|
183
|
+
const denyMatch = findMatchingRule(
|
|
184
|
+
pathToMatch,
|
|
185
|
+
denyRules,
|
|
186
|
+
importInfo.sourceFile,
|
|
187
|
+
rootDir,
|
|
188
|
+
isExternal
|
|
189
|
+
);
|
|
165
190
|
if (denyMatch) {
|
|
166
191
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
167
192
|
}
|
|
@@ -198,9 +223,9 @@ function getPathToMatch(importInfo, rootDir) {
|
|
|
198
223
|
}
|
|
199
224
|
return importInfo.moduleSpecifier;
|
|
200
225
|
}
|
|
201
|
-
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
|
|
226
|
+
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
|
|
202
227
|
for (const rule of rules) {
|
|
203
|
-
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
|
|
228
|
+
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
|
|
204
229
|
return rule;
|
|
205
230
|
}
|
|
206
231
|
}
|
|
@@ -216,18 +241,20 @@ function getPackageName(moduleSpecifier) {
|
|
|
216
241
|
}
|
|
217
242
|
return moduleSpecifier.split("/")[0];
|
|
218
243
|
}
|
|
219
|
-
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
|
|
244
|
+
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
|
|
220
245
|
if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
221
246
|
const sourceDir = import_node_path.default.dirname(sourceFile);
|
|
222
247
|
const resolvedPattern = import_node_path.default.relative(rootDir, import_node_path.default.resolve(sourceDir, pattern));
|
|
223
|
-
return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern
|
|
248
|
+
return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern);
|
|
224
249
|
}
|
|
225
250
|
if (pattern.includes("*")) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
251
|
+
if (isExternal) {
|
|
252
|
+
const packageName = getPackageName(pathToMatch);
|
|
253
|
+
if ((0, import_minimatch.minimatch)(packageName, pattern)) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
229
256
|
}
|
|
230
|
-
return (0, import_minimatch.minimatch)(pathToMatch, pattern
|
|
257
|
+
return (0, import_minimatch.minimatch)(pathToMatch, pattern);
|
|
231
258
|
}
|
|
232
259
|
const pathPackageName = getPackageName(pathToMatch);
|
|
233
260
|
const patternPackageName = getPackageName(pattern);
|
package/dist/cli/index.cjs.map
CHANGED
|
@@ -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\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\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":["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":";;;;;;;;;;;;;;;;;;;;;;;;;;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;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,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,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,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,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,iBAAAA,QAAK,QAAQ,UAAU;AACzC,UAAM,kBAAkB,iBAAAA,QAAK,SAAS,SAAS,iBAAAA,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;;;AC3BA,IAAAC,oBAAiB;AACjB,mBAAkB;AAIX,SAAS,gBAAgB,QAA0B,UAA2B,CAAC,GAAW;AAChG,QAAM,EAAE,YAAY,cAAc,eAAe,IAAI;AACrD,QAAM,WAAW,QAAQ,UAAU;AAEnC,QAAM,IAAI,WACP,aAAAC,UACA;AAAA,IACA,KAAK,CAAC,MAAc;AAAA,IACpB,QAAQ,CAAC,MAAc;AAAA,IACvB,OAAO,CAAC,MAAc;AAAA,IACtB,MAAM,CAAC,MAAc;AAAA,IACrB,MAAM,CAAC,MAAc;AAAA,IACrB,MAAM,CAAC,MAAc;AAAA,IACrB,KAAK,CAAC,MAAc;AAAA,EACrB;AAEF,MAAI,WAAW,WAAW,GAAG;AAC5B,YAAQ,IAAI,EAAE,MAAM,4CAAuC,CAAC;AAC5D,YAAQ,IAAI,EAAE,IAAI,aAAa,cAAc,mBAAmB,YAAY,QAAQ,CAAC;AACrF,WAAO;AAAA,EACR;AAGA,QAAM,mBAAmB,YAAY,UAAU;AAE/C,aAAW,CAAC,UAAU,cAAc,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAC1E,YAAQ,IAAI;AACZ,YAAQ,IAAI,EAAE,KAAK,QAAQ,CAAC;AAE5B,eAAW,aAAa,gBAAgB;AACvC,YAAM,WAAW,EAAE,IAAI,GAAG,UAAU,IAAI,IAAI,UAAU,MAAM,EAAE;AAC9D,YAAM,YAAY,EAAE,IAAI,OAAO;AAC/B,YAAM,OAAO,EAAE,IAAI,IAAI,UAAU,IAAI,GAAG;AAExC,cAAQ,IAAI,KAAK,QAAQ,KAAK,SAAS,KAAK,UAAU,OAAO,KAAK,IAAI,EAAE;AAExE,UAAI,UAAU,cAAc;AAC3B,gBAAQ,IAAI,EAAE,KAAK,sBAAsB,UAAU,YAAY,EAAE,CAAC;AAAA,MACnE;AAEA,UAAI,UAAU,YAAY;AACzB,gBAAQ,IAAI,EAAE,OAAO,mBAAmB,UAAU,UAAU,EAAE,CAAC;AAAA,MAChE;AAEA,YAAM,WAAW,kBAAAC,QAAK,SAAS,QAAQ,IAAI,GAAG,UAAU,YAAY;AACpE,cAAQ,IAAI,EAAE,KAAK,aAAa,QAAQ,EAAE,CAAC;AAAA,IAC5C;AAAA,EACD;AAEA,UAAQ,IAAI;AAEZ,QAAM,aAAa,WAAW;AAC9B,QAAM,YAAY,OAAO,KAAK,gBAAgB,EAAE;AAChD,QAAM,UAAU,UAAK,UAAU,SAAS,eAAe,IAAI,MAAM,EAAE,OAAO,SAAS,QAAQ,cAAc,IAAI,MAAM,EAAE;AAErH,UAAQ,IAAI,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,CAAC;AAElC,SAAO;AACR;AAEA,SAAS,YAAY,YAAsD;AAC1E,QAAM,UAAuC,CAAC;AAE9C,aAAW,aAAa,YAAY;AACnC,UAAM,eAAe,kBAAAA,QAAK,SAAS,QAAQ,IAAI,GAAG,UAAU,UAAU;AACtE,QAAI,CAAC,QAAQ,YAAY,GAAG;AAC3B,cAAQ,YAAY,IAAI,CAAC;AAAA,IAC1B;AACA,YAAQ,YAAY,EAAE,KAAK,SAAS;AAAA,EACrC;AAGA,aAAWC,eAAc,OAAO,OAAO,OAAO,GAAG;AAChD,IAAAA,YAAW,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM;AAAA,EACjE;AAEA,SAAO;AACR;;;AClFA,qBAAe;AACf,IAAAC,oBAAiB;AACjB,kBAAmC;;;ACFnC,iBAAkB;AAElB,IAAM,mBAAmB,aAAE,MAAM;AAAA,EAChC,aAAE,OAAO,EAAE,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE;AAAA,EACzC,aAAE,OAAO;AAAA,IACR,MAAM,aAAE,OAAO;AAAA,IACf,SAAS,aAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,CAAC;AACF,CAAC;AAEM,IAAM,wBAAwB,aAAE,OAAO;AAAA,EAC7C,SAAS,aAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACnC,aAAa,aAAE,OAAO,EAAE,SAAS;AAAA,EACjC,OAAO,aACL,OAAO;AAAA,IACP,OAAO,aAAE,KAAK,CAAC,QAAQ,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,aAAa;AAAA,IACvE,SAAS,aAAE,MAAM,aAAE,OAAO,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,EACnD,CAAC,EACA,SAAS,EACT,QAAQ,CAAC,CAAC;AAAA,EACZ,SAAS,aACP,OAAO;AAAA,IACP,OAAO,aAAE,MAAM,gBAAgB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,IACtD,MAAM,aAAE,MAAM,gBAAgB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,IACrD,MAAM,aAAE,KAAK,CAAC,eAAe,YAAY,CAAC,EAAE,SAAS,EAAE,QAAQ,aAAa;AAAA,EAC7E,CAAC,EACA,SAAS,EACT,QAAQ,CAAC,CAAC;AACb,CAAC;AAIM,SAAS,YAAY,MAAsC;AACjE,SAAO,sBAAsB,MAAM,IAAI;AACxC;;;AD5BA,IAAM,iBAAiB;AAEvB,eAAsB,sBAAsB,SAA4C;AACvF,QAAM,QAA0B,CAAC;AAEjC,QAAM,cAAc,SAAS,SAAS,KAAK;AAE3C,SAAO;AACR;AAEA,eAAe,cACd,YACA,SACA,OACgB;AAChB,QAAM,eAAe,kBAAAC,QAAK,KAAK,YAAY,cAAc;AAEzD,MAAI,eAAAC,QAAG,WAAW,YAAY,GAAG;AAChC,UAAM,SAAS,MAAM,aAAa,YAAY;AAC9C,UAAM,UAAU,IAAI;AAAA,MACnB;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAGA,QAAM,UAAU,eAAAA,QAAG,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC;AAClE,aAAW,SAAS,SAAS;AAC5B,QAAI,MAAM,YAAY,KAAK,CAAC,oBAAoB,MAAM,IAAI,GAAG;AAC5D,YAAM,SAAS,kBAAAD,QAAK,KAAK,YAAY,MAAM,IAAI;AAC/C,YAAM,cAAc,QAAQ,SAAS,KAAK;AAAA,IAC3C;AAAA,EACD;AACD;AAEA,eAAe,aAAa,UAA4C;AACvE,QAAM,UAAU,eAAAC,QAAG,aAAa,UAAU,OAAO;AACjD,QAAM,aAAS,YAAAC,OAAU,OAAO;AAChC,SAAO,YAAY,MAAM;AAC1B;AAEA,SAAS,oBAAoB,MAAuB;AACnD,QAAM,WAAW,CAAC,gBAAgB,QAAQ,QAAQ,SAAS,UAAU;AACrE,SAAO,SAAS,SAAS,IAAI,KAAK,KAAK,WAAW,GAAG;AACtD;;;AElDA,IAAAC,oBAAiB;AAGV,SAAS,aAAa,kBAAoD;AAChF,QAAM,gBAAgC,CAAC;AACvC,QAAM,cAAc,OAAO,KAAK,gBAAgB,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAEpF,aAAW,aAAa,aAAa;AACpC,UAAM,EAAE,QAAQ,aAAa,IAAI,iBAAiB,SAAS;AAG3D,UAAM,cAAc,gBAAgB,WAAW,aAAa,gBAAgB;AAG5E,UAAM,eAAe,aAAa,aAAa,MAAM;AAGrD,UAAM,kBAAkB,uBAAuB,YAAY;AAE3D,kBAAc,KAAK;AAAA,MAClB;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,IACD,CAAC;AAAA,EACF;AAEA,SAAO;AACR;AAEA,SAAS,gBACR,WACA,gBACA,kBACoB;AACpB,QAAM,UAA6B,CAAC;AAEpC,aAAW,mBAAmB,gBAAgB;AAC7C,QAAI,oBAAoB,UAAW;AAGnC,UAAM,WAAW,kBAAAC,QAAK,SAAS,iBAAiB,SAAS;AACzD,QAAI,CAAC,SAAS,WAAW,IAAI,KAAK,CAAC,kBAAAA,QAAK,WAAW,QAAQ,GAAG;AAC7D,YAAM,eAAe,iBAAiB,eAAe,EAAE;AAGvD,YAAM,aAAa,aAAa,OAAO,SAAS;AAChD,UAAI,eAAe,eAAe;AACjC,gBAAQ,KAAK,YAAY;AAAA,MAC1B;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,aAAa,SAA4B,OAAyC;AAC1F,MAAI,QAAQ,WAAW,GAAG;AACzB,WAAO;AAAA,EACR;AAGA,MAAI,SAA0B,EAAE,SAAS,MAAM,QAAQ;AAEvD,aAAW,UAAU,SAAS;AAC7B,aAAS,gBAAgB,QAAQ,MAAM;AAAA,EACxC;AAGA,WAAS,gBAAgB,QAAQ,KAAK;AAEtC,SAAO;AACR;AAEA,SAAS,gBAAgB,MAAuB,UAA4C;AAC3F,QAAM,SAA0B;AAAA,IAC/B,SAAS,SAAS,WAAW,KAAK;AAAA,IAClC,aAAa,SAAS,eAAe,KAAK;AAAA,EAC3C;AAGA,MAAI,KAAK,SAAS,SAAS,OAAO;AACjC,WAAO,QAAQ;AAAA,MACd,OAAO,SAAS,OAAO,SAAS,KAAK,OAAO,SAAS;AAAA,MACrD,SAAS,YAAY,KAAK,OAAO,SAAS,SAAS,OAAO,OAAO;AAAA,IAClE;AAAA,EACD;AAGA,MAAI,KAAK,WAAW,SAAS,SAAS;AACrC,WAAO,UAAU;AAAA,MAChB,OAAO,iBAAiB,KAAK,SAAS,OAAO,SAAS,SAAS,KAAK;AAAA,MACpE,MAAM,iBAAiB,KAAK,SAAS,MAAM,SAAS,SAAS,IAAI;AAAA,MACjE,MAAM,SAAS,SAAS,QAAQ,KAAK,SAAS,QAAQ;AAAA,IACvD;AAAA,EACD;AAEA,SAAO;AACR;AAEA,SAAS,YAAe,MAAY,UAAqB;AACxD,QAAM,SAAc,CAAC;AACrB,MAAI,KAAM,QAAO,KAAK,GAAG,IAAI;AAC7B,MAAI,SAAU,QAAO,KAAK,GAAG,QAAQ;AACrC,SAAO;AACR;AAEA,SAAS,iBAAiB,MAAqB,UAAuC;AACrF,QAAM,SAAuB,CAAC;AAC9B,MAAI,KAAM,QAAO,KAAK,GAAG,IAAI;AAC7B,MAAI,SAAU,QAAO,KAAK,GAAG,QAAQ;AACrC,SAAO;AACR;AAEA,SAAS,uBAAuB,QAAmC;AAClE,SAAO,OAAO,OAAO,WAAW,CAAC;AAClC;;;ARvGA,eAAsB,aAAa,YAAoB,SAAsC;AAC5F,QAAM,eAAe,kBAAAC,QAAK,QAAQ,UAAU;AAE5C,UAAQ,IAAI,kCAAkC,YAAY;AAAA,CAAI;AAE9D,MAAI;AACH,UAAM,UAAU,cAAc;AAAA,MAC7B,kBAAkB,QAAQ;AAAA,MAC1B,SAAS;AAAA,IACV,CAAC;AAED,UAAM,UAAU,eAAe,SAAS,YAAY;AAEpD,QAAI,QAAQ,WAAW,GAAG;AACzB,cAAQ,IAAI,4BAA4B;AACxC,cAAQ,KAAK,CAAC;AAAA,IACf;AAEA,UAAM,aAAa,MAAM,sBAAsB,YAAY;AAC3D,UAAM,gBAAgB,aAAa,UAAU;AAE7C,UAAM,UAAU,SAAS,SAAS,eAAe,YAAY;AAE7D,UAAM,WAAW,gBAAgB,SAAS;AAAA,MACzC,OAAO,QAAQ,UAAU;AAAA,IAC1B,CAAC;AAED,YAAQ,KAAK,QAAQ;AAAA,EACtB,SAAS,OAAO;AACf,QAAI,iBAAiB,OAAO;AAC3B,cAAQ,MAAM,UAAU,MAAM,OAAO,EAAE;AAAA,IACxC,OAAO;AACN,cAAQ,MAAM,2BAA2B;AAAA,IAC1C;AACA,YAAQ,KAAK,CAAC;AAAA,EACf;AACD;;;AD5CA,IAAM,UAAU,IAAI,yBAAQ;AAE5B,QACE,KAAK,WAAW,EAChB,YAAY,8DAA8D,EAC1E,QAAQ,OAAO;AAEjB,QACE,QAAQ,OAAO,EACf,YAAY,oDAAoD,EAChE,SAAS,UAAU,iBAAiB,GAAG,EACvC,OAAO,uBAAuB,uBAAuB,EACrD,OAAO,cAAc,wBAAwB,EAC7C,OAAO,YAAY;AAErB,QAAQ,MAAM;","names":["import_node_path","path","import_node_path","chalk","path","violations","import_node_path","path","fs","parseYaml","import_node_path","path","path"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -114,13 +114,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
114
114
|
const allowRules = imports.allow ?? [];
|
|
115
115
|
const denyRules = imports.deny ?? [];
|
|
116
116
|
const pathToMatch = getPathToMatch(importInfo, rootDir);
|
|
117
|
+
const isExternal = importInfo.isExternal;
|
|
117
118
|
if (mode === "allow-first") {
|
|
118
|
-
const denyMatch = findMatchingRule(
|
|
119
|
+
const denyMatch = findMatchingRule(
|
|
120
|
+
pathToMatch,
|
|
121
|
+
denyRules,
|
|
122
|
+
importInfo.sourceFile,
|
|
123
|
+
rootDir,
|
|
124
|
+
isExternal
|
|
125
|
+
);
|
|
119
126
|
if (denyMatch) {
|
|
120
127
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
121
128
|
}
|
|
122
129
|
if (allowRules.length > 0) {
|
|
123
|
-
const allowMatch = findMatchingRule(
|
|
130
|
+
const allowMatch = findMatchingRule(
|
|
131
|
+
pathToMatch,
|
|
132
|
+
allowRules,
|
|
133
|
+
importInfo.sourceFile,
|
|
134
|
+
rootDir,
|
|
135
|
+
isExternal
|
|
136
|
+
);
|
|
124
137
|
if (!allowMatch) {
|
|
125
138
|
return createViolation(
|
|
126
139
|
importInfo,
|
|
@@ -134,11 +147,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
134
147
|
}
|
|
135
148
|
}
|
|
136
149
|
} else {
|
|
137
|
-
const allowMatch = findMatchingRule(
|
|
150
|
+
const allowMatch = findMatchingRule(
|
|
151
|
+
pathToMatch,
|
|
152
|
+
allowRules,
|
|
153
|
+
importInfo.sourceFile,
|
|
154
|
+
rootDir,
|
|
155
|
+
isExternal
|
|
156
|
+
);
|
|
138
157
|
if (allowMatch) {
|
|
139
158
|
return null;
|
|
140
159
|
}
|
|
141
|
-
const denyMatch = findMatchingRule(
|
|
160
|
+
const denyMatch = findMatchingRule(
|
|
161
|
+
pathToMatch,
|
|
162
|
+
denyRules,
|
|
163
|
+
importInfo.sourceFile,
|
|
164
|
+
rootDir,
|
|
165
|
+
isExternal
|
|
166
|
+
);
|
|
142
167
|
if (denyMatch) {
|
|
143
168
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
144
169
|
}
|
|
@@ -175,9 +200,9 @@ function getPathToMatch(importInfo, rootDir) {
|
|
|
175
200
|
}
|
|
176
201
|
return importInfo.moduleSpecifier;
|
|
177
202
|
}
|
|
178
|
-
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
|
|
203
|
+
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
|
|
179
204
|
for (const rule of rules) {
|
|
180
|
-
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
|
|
205
|
+
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
|
|
181
206
|
return rule;
|
|
182
207
|
}
|
|
183
208
|
}
|
|
@@ -193,18 +218,20 @@ function getPackageName(moduleSpecifier) {
|
|
|
193
218
|
}
|
|
194
219
|
return moduleSpecifier.split("/")[0];
|
|
195
220
|
}
|
|
196
|
-
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
|
|
221
|
+
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
|
|
197
222
|
if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
198
223
|
const sourceDir = path.dirname(sourceFile);
|
|
199
224
|
const resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));
|
|
200
|
-
return minimatch(pathToMatch, resolvedPattern
|
|
225
|
+
return minimatch(pathToMatch, resolvedPattern);
|
|
201
226
|
}
|
|
202
227
|
if (pattern.includes("*")) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
228
|
+
if (isExternal) {
|
|
229
|
+
const packageName = getPackageName(pathToMatch);
|
|
230
|
+
if (minimatch(packageName, pattern)) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
206
233
|
}
|
|
207
|
-
return minimatch(pathToMatch, pattern
|
|
234
|
+
return minimatch(pathToMatch, pattern);
|
|
208
235
|
}
|
|
209
236
|
const pathPackageName = getPackageName(pathToMatch);
|
|
210
237
|
const patternPackageName = getPackageName(pattern);
|
package/dist/cli/index.js.map
CHANGED
|
@@ -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\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\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
|
@@ -268,13 +268,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
268
268
|
const allowRules = imports.allow ?? [];
|
|
269
269
|
const denyRules = imports.deny ?? [];
|
|
270
270
|
const pathToMatch = getPathToMatch(importInfo, rootDir);
|
|
271
|
+
const isExternal = importInfo.isExternal;
|
|
271
272
|
if (mode === "allow-first") {
|
|
272
|
-
const denyMatch = findMatchingRule(
|
|
273
|
+
const denyMatch = findMatchingRule(
|
|
274
|
+
pathToMatch,
|
|
275
|
+
denyRules,
|
|
276
|
+
importInfo.sourceFile,
|
|
277
|
+
rootDir,
|
|
278
|
+
isExternal
|
|
279
|
+
);
|
|
273
280
|
if (denyMatch) {
|
|
274
281
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
275
282
|
}
|
|
276
283
|
if (allowRules.length > 0) {
|
|
277
|
-
const allowMatch = findMatchingRule(
|
|
284
|
+
const allowMatch = findMatchingRule(
|
|
285
|
+
pathToMatch,
|
|
286
|
+
allowRules,
|
|
287
|
+
importInfo.sourceFile,
|
|
288
|
+
rootDir,
|
|
289
|
+
isExternal
|
|
290
|
+
);
|
|
278
291
|
if (!allowMatch) {
|
|
279
292
|
return createViolation(
|
|
280
293
|
importInfo,
|
|
@@ -288,11 +301,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
288
301
|
}
|
|
289
302
|
}
|
|
290
303
|
} else {
|
|
291
|
-
const allowMatch = findMatchingRule(
|
|
304
|
+
const allowMatch = findMatchingRule(
|
|
305
|
+
pathToMatch,
|
|
306
|
+
allowRules,
|
|
307
|
+
importInfo.sourceFile,
|
|
308
|
+
rootDir,
|
|
309
|
+
isExternal
|
|
310
|
+
);
|
|
292
311
|
if (allowMatch) {
|
|
293
312
|
return null;
|
|
294
313
|
}
|
|
295
|
-
const denyMatch = findMatchingRule(
|
|
314
|
+
const denyMatch = findMatchingRule(
|
|
315
|
+
pathToMatch,
|
|
316
|
+
denyRules,
|
|
317
|
+
importInfo.sourceFile,
|
|
318
|
+
rootDir,
|
|
319
|
+
isExternal
|
|
320
|
+
);
|
|
296
321
|
if (denyMatch) {
|
|
297
322
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
298
323
|
}
|
|
@@ -329,9 +354,9 @@ function getPathToMatch(importInfo, rootDir) {
|
|
|
329
354
|
}
|
|
330
355
|
return importInfo.moduleSpecifier;
|
|
331
356
|
}
|
|
332
|
-
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
|
|
357
|
+
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
|
|
333
358
|
for (const rule of rules) {
|
|
334
|
-
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
|
|
359
|
+
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
|
|
335
360
|
return rule;
|
|
336
361
|
}
|
|
337
362
|
}
|
|
@@ -347,18 +372,20 @@ function getPackageName(moduleSpecifier) {
|
|
|
347
372
|
}
|
|
348
373
|
return moduleSpecifier.split("/")[0];
|
|
349
374
|
}
|
|
350
|
-
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
|
|
375
|
+
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
|
|
351
376
|
if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
352
377
|
const sourceDir = import_node_path3.default.dirname(sourceFile);
|
|
353
378
|
const resolvedPattern = import_node_path3.default.relative(rootDir, import_node_path3.default.resolve(sourceDir, pattern));
|
|
354
|
-
return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern
|
|
379
|
+
return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern);
|
|
355
380
|
}
|
|
356
381
|
if (pattern.includes("*")) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
382
|
+
if (isExternal) {
|
|
383
|
+
const packageName = getPackageName(pathToMatch);
|
|
384
|
+
if ((0, import_minimatch.minimatch)(packageName, pattern)) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
360
387
|
}
|
|
361
|
-
return (0, import_minimatch.minimatch)(pathToMatch, pattern
|
|
388
|
+
return (0, import_minimatch.minimatch)(pathToMatch, pattern);
|
|
362
389
|
}
|
|
363
390
|
const pathPackageName = getPackageName(pathToMatch);
|
|
364
391
|
const patternPackageName = getPackageName(pattern);
|
package/dist/index.cjs.map
CHANGED
|
@@ -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\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\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":"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","names":["fs","parseYaml","import_node_path","path","import_node_path","path"]}
|
package/dist/index.js
CHANGED
|
@@ -228,13 +228,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
228
228
|
const allowRules = imports.allow ?? [];
|
|
229
229
|
const denyRules = imports.deny ?? [];
|
|
230
230
|
const pathToMatch = getPathToMatch(importInfo, rootDir);
|
|
231
|
+
const isExternal = importInfo.isExternal;
|
|
231
232
|
if (mode === "allow-first") {
|
|
232
|
-
const denyMatch = findMatchingRule(
|
|
233
|
+
const denyMatch = findMatchingRule(
|
|
234
|
+
pathToMatch,
|
|
235
|
+
denyRules,
|
|
236
|
+
importInfo.sourceFile,
|
|
237
|
+
rootDir,
|
|
238
|
+
isExternal
|
|
239
|
+
);
|
|
233
240
|
if (denyMatch) {
|
|
234
241
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
235
242
|
}
|
|
236
243
|
if (allowRules.length > 0) {
|
|
237
|
-
const allowMatch = findMatchingRule(
|
|
244
|
+
const allowMatch = findMatchingRule(
|
|
245
|
+
pathToMatch,
|
|
246
|
+
allowRules,
|
|
247
|
+
importInfo.sourceFile,
|
|
248
|
+
rootDir,
|
|
249
|
+
isExternal
|
|
250
|
+
);
|
|
238
251
|
if (!allowMatch) {
|
|
239
252
|
return createViolation(
|
|
240
253
|
importInfo,
|
|
@@ -248,11 +261,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
248
261
|
}
|
|
249
262
|
}
|
|
250
263
|
} else {
|
|
251
|
-
const allowMatch = findMatchingRule(
|
|
264
|
+
const allowMatch = findMatchingRule(
|
|
265
|
+
pathToMatch,
|
|
266
|
+
allowRules,
|
|
267
|
+
importInfo.sourceFile,
|
|
268
|
+
rootDir,
|
|
269
|
+
isExternal
|
|
270
|
+
);
|
|
252
271
|
if (allowMatch) {
|
|
253
272
|
return null;
|
|
254
273
|
}
|
|
255
|
-
const denyMatch = findMatchingRule(
|
|
274
|
+
const denyMatch = findMatchingRule(
|
|
275
|
+
pathToMatch,
|
|
276
|
+
denyRules,
|
|
277
|
+
importInfo.sourceFile,
|
|
278
|
+
rootDir,
|
|
279
|
+
isExternal
|
|
280
|
+
);
|
|
256
281
|
if (denyMatch) {
|
|
257
282
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
258
283
|
}
|
|
@@ -289,9 +314,9 @@ function getPathToMatch(importInfo, rootDir) {
|
|
|
289
314
|
}
|
|
290
315
|
return importInfo.moduleSpecifier;
|
|
291
316
|
}
|
|
292
|
-
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
|
|
317
|
+
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
|
|
293
318
|
for (const rule of rules) {
|
|
294
|
-
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
|
|
319
|
+
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
|
|
295
320
|
return rule;
|
|
296
321
|
}
|
|
297
322
|
}
|
|
@@ -307,18 +332,20 @@ function getPackageName(moduleSpecifier) {
|
|
|
307
332
|
}
|
|
308
333
|
return moduleSpecifier.split("/")[0];
|
|
309
334
|
}
|
|
310
|
-
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
|
|
335
|
+
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
|
|
311
336
|
if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
312
337
|
const sourceDir = path3.dirname(sourceFile);
|
|
313
338
|
const resolvedPattern = path3.relative(rootDir, path3.resolve(sourceDir, pattern));
|
|
314
|
-
return minimatch(pathToMatch, resolvedPattern
|
|
339
|
+
return minimatch(pathToMatch, resolvedPattern);
|
|
315
340
|
}
|
|
316
341
|
if (pattern.includes("*")) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
342
|
+
if (isExternal) {
|
|
343
|
+
const packageName = getPackageName(pathToMatch);
|
|
344
|
+
if (minimatch(packageName, pattern)) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
320
347
|
}
|
|
321
|
-
return minimatch(pathToMatch, pattern
|
|
348
|
+
return minimatch(pathToMatch, pattern);
|
|
322
349
|
}
|
|
323
350
|
const pathPackageName = getPackageName(pathToMatch);
|
|
324
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\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\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":";AAAA,SAAS,eAAe;AAGjB,SAAS,cAAc,SAAkC;AAC/D,QAAM,UAAU,IAAI,QAAQ;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,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,SAAS,iBAAiB;;;ACFnC,SAAS,SAAS;AAElB,IAAM,mBAAmB,EAAE,MAAM;AAAA,EAChC,EAAE,OAAO,EAAE,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE;AAAA,EACzC,EAAE,OAAO;AAAA,IACR,MAAM,EAAE,OAAO;AAAA,IACf,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,CAAC;AACF,CAAC;AAEM,IAAM,wBAAwB,EAAE,OAAO;AAAA,EAC7C,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACnC,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,OAAO,EACL,OAAO;AAAA,IACP,OAAO,EAAE,KAAK,CAAC,QAAQ,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,aAAa;AAAA,IACvE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,EACnD,CAAC,EACA,SAAS,EACT,QAAQ,CAAC,CAAC;AAAA,EACZ,SAAS,EACP,OAAO;AAAA,IACP,OAAO,EAAE,MAAM,gBAAgB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,IACtD,MAAM,EAAE,MAAM,gBAAgB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,IACrD,MAAM,EAAE,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,GAAG,aAAa,UAAU,OAAO;AACjD,QAAM,SAAS,UAAU,OAAO;AAChC,SAAO,YAAY,MAAM;AAC1B;;;AExDA,OAAOA,WAAU;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,WAAWA,MAAK,SAAS,iBAAiB,SAAS;AACzD,QAAI,CAAC,SAAS,WAAW,IAAI,KAAK,CAACA,MAAK,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,OAAOC,WAAU;AACjB,SAAS,iBAAiB;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,WAAWA,MAAK,SAAS,KAAK,WAAW,QAAQ;AAEvD,QAAI,CAAC,SAAS,WAAW,IAAI,KAAK,CAACA,MAAK,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,eAAeA,MAAK,SAAS,SAAS,QAAQ;AAEpD,aAAW,WAAW,KAAK,iBAAiB;AAC3C,QAAI,UAAU,cAAc,OAAO,KAAK,UAAUA,MAAK,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,WAAOA,MAAK,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,YAAYA,MAAK,QAAQ,UAAU;AACzC,UAAM,kBAAkBA,MAAK,SAAS,SAASA,MAAK,QAAQ,WAAW,OAAO,CAAC;AAC/E,WAAO,UAAU,aAAa,iBAAiB,EAAE,WAAW,KAAK,CAAC;AAAA,EACnE;AAGA,MAAI,QAAQ,SAAS,GAAG,GAAG;AAG1B,UAAM,cAAc,eAAe,WAAW;AAC9C,QAAI,UAAU,aAAa,SAAS,EAAE,WAAW,KAAK,CAAC,GAAG;AACzD,aAAO;AAAA,IACR;AAEA,WAAO,UAAU,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;","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
|
+
"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",
|