zonefence 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +44 -14
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +44 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +44 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +44 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/cli/index.cjs
CHANGED
|
@@ -89,10 +89,13 @@ function resolveModulePath(decl, moduleSpecifier, _sourceFilePath) {
|
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
91
|
function isExternalImport(moduleSpecifier, resolvedPath) {
|
|
92
|
-
if (
|
|
92
|
+
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
|
|
93
93
|
return false;
|
|
94
94
|
}
|
|
95
|
-
if (
|
|
95
|
+
if (resolvedPath?.includes("node_modules")) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (resolvedPath !== null) {
|
|
96
99
|
return false;
|
|
97
100
|
}
|
|
98
101
|
return true;
|
|
@@ -134,13 +137,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
134
137
|
const allowRules = imports.allow ?? [];
|
|
135
138
|
const denyRules = imports.deny ?? [];
|
|
136
139
|
const pathToMatch = getPathToMatch(importInfo, rootDir);
|
|
140
|
+
const isExternal = importInfo.isExternal;
|
|
137
141
|
if (mode === "allow-first") {
|
|
138
|
-
const denyMatch = findMatchingRule(
|
|
142
|
+
const denyMatch = findMatchingRule(
|
|
143
|
+
pathToMatch,
|
|
144
|
+
denyRules,
|
|
145
|
+
importInfo.sourceFile,
|
|
146
|
+
rootDir,
|
|
147
|
+
isExternal
|
|
148
|
+
);
|
|
139
149
|
if (denyMatch) {
|
|
140
150
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
141
151
|
}
|
|
142
152
|
if (allowRules.length > 0) {
|
|
143
|
-
const allowMatch = findMatchingRule(
|
|
153
|
+
const allowMatch = findMatchingRule(
|
|
154
|
+
pathToMatch,
|
|
155
|
+
allowRules,
|
|
156
|
+
importInfo.sourceFile,
|
|
157
|
+
rootDir,
|
|
158
|
+
isExternal
|
|
159
|
+
);
|
|
144
160
|
if (!allowMatch) {
|
|
145
161
|
return createViolation(
|
|
146
162
|
importInfo,
|
|
@@ -154,11 +170,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
154
170
|
}
|
|
155
171
|
}
|
|
156
172
|
} else {
|
|
157
|
-
const allowMatch = findMatchingRule(
|
|
173
|
+
const allowMatch = findMatchingRule(
|
|
174
|
+
pathToMatch,
|
|
175
|
+
allowRules,
|
|
176
|
+
importInfo.sourceFile,
|
|
177
|
+
rootDir,
|
|
178
|
+
isExternal
|
|
179
|
+
);
|
|
158
180
|
if (allowMatch) {
|
|
159
181
|
return null;
|
|
160
182
|
}
|
|
161
|
-
const denyMatch = findMatchingRule(
|
|
183
|
+
const denyMatch = findMatchingRule(
|
|
184
|
+
pathToMatch,
|
|
185
|
+
denyRules,
|
|
186
|
+
importInfo.sourceFile,
|
|
187
|
+
rootDir,
|
|
188
|
+
isExternal
|
|
189
|
+
);
|
|
162
190
|
if (denyMatch) {
|
|
163
191
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
164
192
|
}
|
|
@@ -195,9 +223,9 @@ function getPathToMatch(importInfo, rootDir) {
|
|
|
195
223
|
}
|
|
196
224
|
return importInfo.moduleSpecifier;
|
|
197
225
|
}
|
|
198
|
-
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
|
|
226
|
+
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
|
|
199
227
|
for (const rule of rules) {
|
|
200
|
-
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
|
|
228
|
+
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
|
|
201
229
|
return rule;
|
|
202
230
|
}
|
|
203
231
|
}
|
|
@@ -213,18 +241,20 @@ function getPackageName(moduleSpecifier) {
|
|
|
213
241
|
}
|
|
214
242
|
return moduleSpecifier.split("/")[0];
|
|
215
243
|
}
|
|
216
|
-
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
|
|
244
|
+
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
|
|
217
245
|
if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
218
246
|
const sourceDir = import_node_path.default.dirname(sourceFile);
|
|
219
247
|
const resolvedPattern = import_node_path.default.relative(rootDir, import_node_path.default.resolve(sourceDir, pattern));
|
|
220
|
-
return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern
|
|
248
|
+
return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern);
|
|
221
249
|
}
|
|
222
250
|
if (pattern.includes("*")) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
251
|
+
if (isExternal) {
|
|
252
|
+
const packageName = getPackageName(pathToMatch);
|
|
253
|
+
if ((0, import_minimatch.minimatch)(packageName, pattern)) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
226
256
|
}
|
|
227
|
-
return (0, import_minimatch.minimatch)(pathToMatch, pattern
|
|
257
|
+
return (0, import_minimatch.minimatch)(pathToMatch, pattern);
|
|
228
258
|
}
|
|
229
259
|
const pathPackageName = getPackageName(pathToMatch);
|
|
230
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\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\t// For external packages with glob patterns, match against the package name\n\t\t// and also allow subpaths of matching packages\n\t\tconst packageName = getPackageName(pathToMatch);\n\t\tif (minimatch(packageName, pattern, { matchBase: true })) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also try matching the full path for more specific patterns\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport type { EvaluationResult, Violation } from \"../evaluator/types.js\";\nimport type { ReporterOptions } from \"./types.js\";\n\nexport function reportToConsole(result: EvaluationResult, options: ReporterOptions = {}): number {\n\tconst { violations, filesChecked, importsChecked } = result;\n\tconst useColor = options.color !== false;\n\n\tconst c = useColor\n\t\t? chalk\n\t\t: {\n\t\t\t\tred: (s: string) => s,\n\t\t\t\tyellow: (s: string) => s,\n\t\t\t\tgreen: (s: string) => s,\n\t\t\t\tcyan: (s: string) => s,\n\t\t\t\tgray: (s: string) => s,\n\t\t\t\tbold: (s: string) => s,\n\t\t\t\tdim: (s: string) => s,\n\t\t\t};\n\n\tif (violations.length === 0) {\n\t\tconsole.log(c.green(\"✓ No import boundary violations found\"));\n\t\tconsole.log(c.dim(` Checked ${importsChecked} imports across ${filesChecked} files`));\n\t\treturn 0;\n\t}\n\n\t// Group violations by file\n\tconst violationsByFile = groupByFile(violations);\n\n\tfor (const [filePath, fileViolations] of Object.entries(violationsByFile)) {\n\t\tconsole.log();\n\t\tconsole.log(c.bold(filePath));\n\n\t\tfor (const violation of fileViolations) {\n\t\t\tconst location = c.dim(`${violation.line}:${violation.column}`);\n\t\t\tconst errorType = c.red(\"error\");\n\t\t\tconst rule = c.dim(`(${violation.rule})`);\n\n\t\t\tconsole.log(` ${location} ${errorType} ${violation.message} ${rule}`);\n\n\t\t\tif (violation.designIntent) {\n\t\t\t\tconsole.log(c.cyan(` Design intent: ${violation.designIntent}`));\n\t\t\t}\n\n\t\t\tif (violation.suggestion) {\n\t\t\t\tconsole.log(c.yellow(` Suggestion: ${violation.suggestion}`));\n\t\t\t}\n\n\t\t\tconst ruleFile = path.relative(process.cwd(), violation.ruleFilePath);\n\t\t\tconsole.log(c.gray(` Rule: ${ruleFile}`));\n\t\t}\n\t}\n\n\tconsole.log();\n\n\tconst errorCount = violations.length;\n\tconst fileCount = Object.keys(violationsByFile).length;\n\tconst summary = `✖ ${errorCount} error${errorCount !== 1 ? \"s\" : \"\"} in ${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tconsole.log(c.red(c.bold(summary)));\n\n\treturn 1;\n}\n\nfunction groupByFile(violations: Violation[]): Record<string, Violation[]> {\n\tconst grouped: Record<string, Violation[]> = {};\n\n\tfor (const violation of violations) {\n\t\tconst relativePath = path.relative(process.cwd(), violation.sourceFile);\n\t\tif (!grouped[relativePath]) {\n\t\t\tgrouped[relativePath] = [];\n\t\t}\n\t\tgrouped[relativePath].push(violation);\n\t}\n\n\t// Sort violations within each file by line number\n\tfor (const violations of Object.values(grouped)) {\n\t\tviolations.sort((a, b) => a.line - b.line || a.column - b.column);\n\t}\n\n\treturn grouped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n"],"mappings":"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","names":["import_node_path","path","import_node_path","chalk","path","violations","import_node_path","path","fs","parseYaml","import_node_path","path","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/check.ts","../../src/core/import-collector.ts","../../src/core/project.ts","../../src/evaluator/import-boundary.ts","../../src/evaluator/index.ts","../../src/reporter/console.ts","../../src/rules/loader.ts","../../src/rules/schema.ts","../../src/rules/resolver.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n\t.name(\"zonefence\")\n\t.description(\"Folder-based architecture guardrails for TypeScript projects\")\n\t.version(\"0.1.0\");\n\nprogram\n\t.command(\"check\")\n\t.description(\"Check import boundaries in the specified directory\")\n\t.argument(\"[path]\", \"Path to check\", \".\")\n\t.option(\"-c, --config <path>\", \"Path to tsconfig.json\")\n\t.option(\"--no-color\", \"Disable colored output\")\n\t.action(checkCommand);\n\nprogram.parse();\n","import path from \"node:path\";\nimport { collectImports } from \"../../core/import-collector.js\";\nimport { createProject } from \"../../core/project.js\";\nimport { evaluate } from \"../../evaluator/index.js\";\nimport { reportToConsole } from \"../../reporter/console.js\";\nimport { loadRulesForDirectory } from \"../../rules/loader.js\";\nimport { resolveRules } from \"../../rules/resolver.js\";\n\nexport interface CheckOptions {\n\tconfig?: string;\n\tcolor?: boolean;\n}\n\nexport async function checkCommand(targetPath: string, options: CheckOptions): Promise<void> {\n\tconst absolutePath = path.resolve(targetPath);\n\n\tconsole.log(`Checking import boundaries in: ${absolutePath}\\n`);\n\n\ttry {\n\t\tconst project = createProject({\n\t\t\ttsConfigFilePath: options.config,\n\t\t\trootDir: absolutePath,\n\t\t});\n\n\t\tconst imports = collectImports(project, absolutePath);\n\n\t\tif (imports.length === 0) {\n\t\t\tconsole.log(\"No imports found to check.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\tconst rulesByDir = await loadRulesForDirectory(absolutePath);\n\t\tconst resolvedRules = resolveRules(rulesByDir);\n\n\t\tconst results = evaluate(imports, resolvedRules, absolutePath);\n\n\t\tconst exitCode = reportToConsole(results, {\n\t\t\tcolor: options.color !== false,\n\t\t});\n\n\t\tprocess.exit(exitCode);\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error(\"An unknown error occurred\");\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\n/** @internal Exported for testing */\nexport function isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If it starts with . or /, it's a local import\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// If resolved path contains node_modules, it's an external package\n\tif (resolvedPath?.includes(\"node_modules\")) {\n\t\treturn true;\n\t}\n\n\t// If we have a resolved path that's not in node_modules, it's local\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package (unresolved bare specifier)\n\treturn true;\n}\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tconst isExternal = importInfo.isExternal;\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(\n\t\t\t\tpathToMatch,\n\t\t\t\tallowRules,\n\t\t\t\timportInfo.sourceFile,\n\t\t\t\trootDir,\n\t\t\t\tisExternal,\n\t\t\t);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern);\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\tif (isExternal) {\n\t\t\t// For external packages, also match against the package name\n\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\tif (minimatch(packageName, pattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// For internal paths (and external full path match), use direct minimatch\n\t\treturn minimatch(pathToMatch, pattern);\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport type { EvaluationResult, Violation } from \"../evaluator/types.js\";\nimport type { ReporterOptions } from \"./types.js\";\n\nexport function reportToConsole(result: EvaluationResult, options: ReporterOptions = {}): number {\n\tconst { violations, filesChecked, importsChecked } = result;\n\tconst useColor = options.color !== false;\n\n\tconst c = useColor\n\t\t? chalk\n\t\t: {\n\t\t\t\tred: (s: string) => s,\n\t\t\t\tyellow: (s: string) => s,\n\t\t\t\tgreen: (s: string) => s,\n\t\t\t\tcyan: (s: string) => s,\n\t\t\t\tgray: (s: string) => s,\n\t\t\t\tbold: (s: string) => s,\n\t\t\t\tdim: (s: string) => s,\n\t\t\t};\n\n\tif (violations.length === 0) {\n\t\tconsole.log(c.green(\"✓ No import boundary violations found\"));\n\t\tconsole.log(c.dim(` Checked ${importsChecked} imports across ${filesChecked} files`));\n\t\treturn 0;\n\t}\n\n\t// Group violations by file\n\tconst violationsByFile = groupByFile(violations);\n\n\tfor (const [filePath, fileViolations] of Object.entries(violationsByFile)) {\n\t\tconsole.log();\n\t\tconsole.log(c.bold(filePath));\n\n\t\tfor (const violation of fileViolations) {\n\t\t\tconst location = c.dim(`${violation.line}:${violation.column}`);\n\t\t\tconst errorType = c.red(\"error\");\n\t\t\tconst rule = c.dim(`(${violation.rule})`);\n\n\t\t\tconsole.log(` ${location} ${errorType} ${violation.message} ${rule}`);\n\n\t\t\tif (violation.designIntent) {\n\t\t\t\tconsole.log(c.cyan(` Design intent: ${violation.designIntent}`));\n\t\t\t}\n\n\t\t\tif (violation.suggestion) {\n\t\t\t\tconsole.log(c.yellow(` Suggestion: ${violation.suggestion}`));\n\t\t\t}\n\n\t\t\tconst ruleFile = path.relative(process.cwd(), violation.ruleFilePath);\n\t\t\tconsole.log(c.gray(` Rule: ${ruleFile}`));\n\t\t}\n\t}\n\n\tconsole.log();\n\n\tconst errorCount = violations.length;\n\tconst fileCount = Object.keys(violationsByFile).length;\n\tconst summary = `✖ ${errorCount} error${errorCount !== 1 ? \"s\" : \"\"} in ${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tconsole.log(c.red(c.bold(summary)));\n\n\treturn 1;\n}\n\nfunction groupByFile(violations: Violation[]): Record<string, Violation[]> {\n\tconst grouped: Record<string, Violation[]> = {};\n\n\tfor (const violation of violations) {\n\t\tconst relativePath = path.relative(process.cwd(), violation.sourceFile);\n\t\tif (!grouped[relativePath]) {\n\t\t\tgrouped[relativePath] = [];\n\t\t}\n\t\tgrouped[relativePath].push(violation);\n\t}\n\n\t// Sort violations within each file by line number\n\tfor (const violations of Object.values(grouped)) {\n\t\tviolations.sort((a, b) => a.line - b.line || a.column - b.column);\n\t}\n\n\treturn grouped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n"],"mappings":"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","names":["import_node_path","path","import_node_path","chalk","path","violations","import_node_path","path","fs","parseYaml","import_node_path","path","path"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -66,10 +66,13 @@ function resolveModulePath(decl, moduleSpecifier, _sourceFilePath) {
|
|
|
66
66
|
return null;
|
|
67
67
|
}
|
|
68
68
|
function isExternalImport(moduleSpecifier, resolvedPath) {
|
|
69
|
-
if (
|
|
69
|
+
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
|
|
70
70
|
return false;
|
|
71
71
|
}
|
|
72
|
-
if (
|
|
72
|
+
if (resolvedPath?.includes("node_modules")) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (resolvedPath !== null) {
|
|
73
76
|
return false;
|
|
74
77
|
}
|
|
75
78
|
return true;
|
|
@@ -111,13 +114,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
111
114
|
const allowRules = imports.allow ?? [];
|
|
112
115
|
const denyRules = imports.deny ?? [];
|
|
113
116
|
const pathToMatch = getPathToMatch(importInfo, rootDir);
|
|
117
|
+
const isExternal = importInfo.isExternal;
|
|
114
118
|
if (mode === "allow-first") {
|
|
115
|
-
const denyMatch = findMatchingRule(
|
|
119
|
+
const denyMatch = findMatchingRule(
|
|
120
|
+
pathToMatch,
|
|
121
|
+
denyRules,
|
|
122
|
+
importInfo.sourceFile,
|
|
123
|
+
rootDir,
|
|
124
|
+
isExternal
|
|
125
|
+
);
|
|
116
126
|
if (denyMatch) {
|
|
117
127
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
118
128
|
}
|
|
119
129
|
if (allowRules.length > 0) {
|
|
120
|
-
const allowMatch = findMatchingRule(
|
|
130
|
+
const allowMatch = findMatchingRule(
|
|
131
|
+
pathToMatch,
|
|
132
|
+
allowRules,
|
|
133
|
+
importInfo.sourceFile,
|
|
134
|
+
rootDir,
|
|
135
|
+
isExternal
|
|
136
|
+
);
|
|
121
137
|
if (!allowMatch) {
|
|
122
138
|
return createViolation(
|
|
123
139
|
importInfo,
|
|
@@ -131,11 +147,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
131
147
|
}
|
|
132
148
|
}
|
|
133
149
|
} else {
|
|
134
|
-
const allowMatch = findMatchingRule(
|
|
150
|
+
const allowMatch = findMatchingRule(
|
|
151
|
+
pathToMatch,
|
|
152
|
+
allowRules,
|
|
153
|
+
importInfo.sourceFile,
|
|
154
|
+
rootDir,
|
|
155
|
+
isExternal
|
|
156
|
+
);
|
|
135
157
|
if (allowMatch) {
|
|
136
158
|
return null;
|
|
137
159
|
}
|
|
138
|
-
const denyMatch = findMatchingRule(
|
|
160
|
+
const denyMatch = findMatchingRule(
|
|
161
|
+
pathToMatch,
|
|
162
|
+
denyRules,
|
|
163
|
+
importInfo.sourceFile,
|
|
164
|
+
rootDir,
|
|
165
|
+
isExternal
|
|
166
|
+
);
|
|
139
167
|
if (denyMatch) {
|
|
140
168
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
141
169
|
}
|
|
@@ -172,9 +200,9 @@ function getPathToMatch(importInfo, rootDir) {
|
|
|
172
200
|
}
|
|
173
201
|
return importInfo.moduleSpecifier;
|
|
174
202
|
}
|
|
175
|
-
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
|
|
203
|
+
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
|
|
176
204
|
for (const rule of rules) {
|
|
177
|
-
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
|
|
205
|
+
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
|
|
178
206
|
return rule;
|
|
179
207
|
}
|
|
180
208
|
}
|
|
@@ -190,18 +218,20 @@ function getPackageName(moduleSpecifier) {
|
|
|
190
218
|
}
|
|
191
219
|
return moduleSpecifier.split("/")[0];
|
|
192
220
|
}
|
|
193
|
-
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
|
|
221
|
+
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
|
|
194
222
|
if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
195
223
|
const sourceDir = path.dirname(sourceFile);
|
|
196
224
|
const resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));
|
|
197
|
-
return minimatch(pathToMatch, resolvedPattern
|
|
225
|
+
return minimatch(pathToMatch, resolvedPattern);
|
|
198
226
|
}
|
|
199
227
|
if (pattern.includes("*")) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
228
|
+
if (isExternal) {
|
|
229
|
+
const packageName = getPackageName(pathToMatch);
|
|
230
|
+
if (minimatch(packageName, pattern)) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
203
233
|
}
|
|
204
|
-
return minimatch(pathToMatch, pattern
|
|
234
|
+
return minimatch(pathToMatch, pattern);
|
|
205
235
|
}
|
|
206
236
|
const pathPackageName = getPackageName(pathToMatch);
|
|
207
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\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\t// For external packages with glob patterns, match against the package name\n\t\t// and also allow subpaths of matching packages\n\t\tconst packageName = getPackageName(pathToMatch);\n\t\tif (minimatch(packageName, pattern, { matchBase: true })) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also try matching the full path for more specific patterns\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport type { EvaluationResult, Violation } from \"../evaluator/types.js\";\nimport type { ReporterOptions } from \"./types.js\";\n\nexport function reportToConsole(result: EvaluationResult, options: ReporterOptions = {}): number {\n\tconst { violations, filesChecked, importsChecked } = result;\n\tconst useColor = options.color !== false;\n\n\tconst c = useColor\n\t\t? chalk\n\t\t: {\n\t\t\t\tred: (s: string) => s,\n\t\t\t\tyellow: (s: string) => s,\n\t\t\t\tgreen: (s: string) => s,\n\t\t\t\tcyan: (s: string) => s,\n\t\t\t\tgray: (s: string) => s,\n\t\t\t\tbold: (s: string) => s,\n\t\t\t\tdim: (s: string) => s,\n\t\t\t};\n\n\tif (violations.length === 0) {\n\t\tconsole.log(c.green(\"✓ No import boundary violations found\"));\n\t\tconsole.log(c.dim(` Checked ${importsChecked} imports across ${filesChecked} files`));\n\t\treturn 0;\n\t}\n\n\t// Group violations by file\n\tconst violationsByFile = groupByFile(violations);\n\n\tfor (const [filePath, fileViolations] of Object.entries(violationsByFile)) {\n\t\tconsole.log();\n\t\tconsole.log(c.bold(filePath));\n\n\t\tfor (const violation of fileViolations) {\n\t\t\tconst location = c.dim(`${violation.line}:${violation.column}`);\n\t\t\tconst errorType = c.red(\"error\");\n\t\t\tconst rule = c.dim(`(${violation.rule})`);\n\n\t\t\tconsole.log(` ${location} ${errorType} ${violation.message} ${rule}`);\n\n\t\t\tif (violation.designIntent) {\n\t\t\t\tconsole.log(c.cyan(` Design intent: ${violation.designIntent}`));\n\t\t\t}\n\n\t\t\tif (violation.suggestion) {\n\t\t\t\tconsole.log(c.yellow(` Suggestion: ${violation.suggestion}`));\n\t\t\t}\n\n\t\t\tconst ruleFile = path.relative(process.cwd(), violation.ruleFilePath);\n\t\t\tconsole.log(c.gray(` Rule: ${ruleFile}`));\n\t\t}\n\t}\n\n\tconsole.log();\n\n\tconst errorCount = violations.length;\n\tconst fileCount = Object.keys(violationsByFile).length;\n\tconst summary = `✖ ${errorCount} error${errorCount !== 1 ? \"s\" : \"\"} in ${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tconsole.log(c.red(c.bold(summary)));\n\n\treturn 1;\n}\n\nfunction groupByFile(violations: Violation[]): Record<string, Violation[]> {\n\tconst grouped: Record<string, Violation[]> = {};\n\n\tfor (const violation of violations) {\n\t\tconst relativePath = path.relative(process.cwd(), violation.sourceFile);\n\t\tif (!grouped[relativePath]) {\n\t\t\tgrouped[relativePath] = [];\n\t\t}\n\t\tgrouped[relativePath].push(violation);\n\t}\n\n\t// Sort violations within each file by line number\n\tfor (const violations of Object.values(grouped)) {\n\t\tviolations.sort((a, b) => a.line - b.line || a.column - b.column);\n\t}\n\n\treturn grouped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n"],"mappings":"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","names":["path","path","violations","path","path","path","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/commands/check.ts","../../src/core/import-collector.ts","../../src/core/project.ts","../../src/evaluator/import-boundary.ts","../../src/evaluator/index.ts","../../src/reporter/console.ts","../../src/rules/loader.ts","../../src/rules/schema.ts","../../src/rules/resolver.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n\t.name(\"zonefence\")\n\t.description(\"Folder-based architecture guardrails for TypeScript projects\")\n\t.version(\"0.1.0\");\n\nprogram\n\t.command(\"check\")\n\t.description(\"Check import boundaries in the specified directory\")\n\t.argument(\"[path]\", \"Path to check\", \".\")\n\t.option(\"-c, --config <path>\", \"Path to tsconfig.json\")\n\t.option(\"--no-color\", \"Disable colored output\")\n\t.action(checkCommand);\n\nprogram.parse();\n","import path from \"node:path\";\nimport { collectImports } from \"../../core/import-collector.js\";\nimport { createProject } from \"../../core/project.js\";\nimport { evaluate } from \"../../evaluator/index.js\";\nimport { reportToConsole } from \"../../reporter/console.js\";\nimport { loadRulesForDirectory } from \"../../rules/loader.js\";\nimport { resolveRules } from \"../../rules/resolver.js\";\n\nexport interface CheckOptions {\n\tconfig?: string;\n\tcolor?: boolean;\n}\n\nexport async function checkCommand(targetPath: string, options: CheckOptions): Promise<void> {\n\tconst absolutePath = path.resolve(targetPath);\n\n\tconsole.log(`Checking import boundaries in: ${absolutePath}\\n`);\n\n\ttry {\n\t\tconst project = createProject({\n\t\t\ttsConfigFilePath: options.config,\n\t\t\trootDir: absolutePath,\n\t\t});\n\n\t\tconst imports = collectImports(project, absolutePath);\n\n\t\tif (imports.length === 0) {\n\t\t\tconsole.log(\"No imports found to check.\");\n\t\t\tprocess.exit(0);\n\t\t}\n\n\t\tconst rulesByDir = await loadRulesForDirectory(absolutePath);\n\t\tconst resolvedRules = resolveRules(rulesByDir);\n\n\t\tconst results = evaluate(imports, resolvedRules, absolutePath);\n\n\t\tconst exitCode = reportToConsole(results, {\n\t\t\tcolor: options.color !== false,\n\t\t});\n\n\t\tprocess.exit(exitCode);\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error(\"An unknown error occurred\");\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\n/** @internal Exported for testing */\nexport function isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If it starts with . or /, it's a local import\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// If resolved path contains node_modules, it's an external package\n\tif (resolvedPath?.includes(\"node_modules\")) {\n\t\treturn true;\n\t}\n\n\t// If we have a resolved path that's not in node_modules, it's local\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package (unresolved bare specifier)\n\treturn true;\n}\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tconst isExternal = importInfo.isExternal;\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(\n\t\t\t\tpathToMatch,\n\t\t\t\tallowRules,\n\t\t\t\timportInfo.sourceFile,\n\t\t\t\trootDir,\n\t\t\t\tisExternal,\n\t\t\t);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern);\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\tif (isExternal) {\n\t\t\t// For external packages, also match against the package name\n\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\tif (minimatch(packageName, pattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// For internal paths (and external full path match), use direct minimatch\n\t\treturn minimatch(pathToMatch, pattern);\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n","import path from \"node:path\";\nimport chalk from \"chalk\";\nimport type { EvaluationResult, Violation } from \"../evaluator/types.js\";\nimport type { ReporterOptions } from \"./types.js\";\n\nexport function reportToConsole(result: EvaluationResult, options: ReporterOptions = {}): number {\n\tconst { violations, filesChecked, importsChecked } = result;\n\tconst useColor = options.color !== false;\n\n\tconst c = useColor\n\t\t? chalk\n\t\t: {\n\t\t\t\tred: (s: string) => s,\n\t\t\t\tyellow: (s: string) => s,\n\t\t\t\tgreen: (s: string) => s,\n\t\t\t\tcyan: (s: string) => s,\n\t\t\t\tgray: (s: string) => s,\n\t\t\t\tbold: (s: string) => s,\n\t\t\t\tdim: (s: string) => s,\n\t\t\t};\n\n\tif (violations.length === 0) {\n\t\tconsole.log(c.green(\"✓ No import boundary violations found\"));\n\t\tconsole.log(c.dim(` Checked ${importsChecked} imports across ${filesChecked} files`));\n\t\treturn 0;\n\t}\n\n\t// Group violations by file\n\tconst violationsByFile = groupByFile(violations);\n\n\tfor (const [filePath, fileViolations] of Object.entries(violationsByFile)) {\n\t\tconsole.log();\n\t\tconsole.log(c.bold(filePath));\n\n\t\tfor (const violation of fileViolations) {\n\t\t\tconst location = c.dim(`${violation.line}:${violation.column}`);\n\t\t\tconst errorType = c.red(\"error\");\n\t\t\tconst rule = c.dim(`(${violation.rule})`);\n\n\t\t\tconsole.log(` ${location} ${errorType} ${violation.message} ${rule}`);\n\n\t\t\tif (violation.designIntent) {\n\t\t\t\tconsole.log(c.cyan(` Design intent: ${violation.designIntent}`));\n\t\t\t}\n\n\t\t\tif (violation.suggestion) {\n\t\t\t\tconsole.log(c.yellow(` Suggestion: ${violation.suggestion}`));\n\t\t\t}\n\n\t\t\tconst ruleFile = path.relative(process.cwd(), violation.ruleFilePath);\n\t\t\tconsole.log(c.gray(` Rule: ${ruleFile}`));\n\t\t}\n\t}\n\n\tconsole.log();\n\n\tconst errorCount = violations.length;\n\tconst fileCount = Object.keys(violationsByFile).length;\n\tconst summary = `✖ ${errorCount} error${errorCount !== 1 ? \"s\" : \"\"} in ${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tconsole.log(c.red(c.bold(summary)));\n\n\treturn 1;\n}\n\nfunction groupByFile(violations: Violation[]): Record<string, Violation[]> {\n\tconst grouped: Record<string, Violation[]> = {};\n\n\tfor (const violation of violations) {\n\t\tconst relativePath = path.relative(process.cwd(), violation.sourceFile);\n\t\tif (!grouped[relativePath]) {\n\t\t\tgrouped[relativePath] = [];\n\t\t}\n\t\tgrouped[relativePath].push(violation);\n\t}\n\n\t// Sort violations within each file by line number\n\tfor (const violations of Object.values(grouped)) {\n\t\tviolations.sort((a, b) => a.line - b.line || a.column - b.column);\n\t}\n\n\treturn grouped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n"],"mappings":"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","names":["path","path","violations","path","path","path","path"]}
|
package/dist/index.cjs
CHANGED
|
@@ -117,10 +117,13 @@ function resolveModulePath(decl, moduleSpecifier, _sourceFilePath) {
|
|
|
117
117
|
return null;
|
|
118
118
|
}
|
|
119
119
|
function isExternalImport(moduleSpecifier, resolvedPath) {
|
|
120
|
-
if (
|
|
120
|
+
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
|
|
121
121
|
return false;
|
|
122
122
|
}
|
|
123
|
-
if (
|
|
123
|
+
if (resolvedPath?.includes("node_modules")) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (resolvedPath !== null) {
|
|
124
127
|
return false;
|
|
125
128
|
}
|
|
126
129
|
return true;
|
|
@@ -265,13 +268,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
265
268
|
const allowRules = imports.allow ?? [];
|
|
266
269
|
const denyRules = imports.deny ?? [];
|
|
267
270
|
const pathToMatch = getPathToMatch(importInfo, rootDir);
|
|
271
|
+
const isExternal = importInfo.isExternal;
|
|
268
272
|
if (mode === "allow-first") {
|
|
269
|
-
const denyMatch = findMatchingRule(
|
|
273
|
+
const denyMatch = findMatchingRule(
|
|
274
|
+
pathToMatch,
|
|
275
|
+
denyRules,
|
|
276
|
+
importInfo.sourceFile,
|
|
277
|
+
rootDir,
|
|
278
|
+
isExternal
|
|
279
|
+
);
|
|
270
280
|
if (denyMatch) {
|
|
271
281
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
272
282
|
}
|
|
273
283
|
if (allowRules.length > 0) {
|
|
274
|
-
const allowMatch = findMatchingRule(
|
|
284
|
+
const allowMatch = findMatchingRule(
|
|
285
|
+
pathToMatch,
|
|
286
|
+
allowRules,
|
|
287
|
+
importInfo.sourceFile,
|
|
288
|
+
rootDir,
|
|
289
|
+
isExternal
|
|
290
|
+
);
|
|
275
291
|
if (!allowMatch) {
|
|
276
292
|
return createViolation(
|
|
277
293
|
importInfo,
|
|
@@ -285,11 +301,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
285
301
|
}
|
|
286
302
|
}
|
|
287
303
|
} else {
|
|
288
|
-
const allowMatch = findMatchingRule(
|
|
304
|
+
const allowMatch = findMatchingRule(
|
|
305
|
+
pathToMatch,
|
|
306
|
+
allowRules,
|
|
307
|
+
importInfo.sourceFile,
|
|
308
|
+
rootDir,
|
|
309
|
+
isExternal
|
|
310
|
+
);
|
|
289
311
|
if (allowMatch) {
|
|
290
312
|
return null;
|
|
291
313
|
}
|
|
292
|
-
const denyMatch = findMatchingRule(
|
|
314
|
+
const denyMatch = findMatchingRule(
|
|
315
|
+
pathToMatch,
|
|
316
|
+
denyRules,
|
|
317
|
+
importInfo.sourceFile,
|
|
318
|
+
rootDir,
|
|
319
|
+
isExternal
|
|
320
|
+
);
|
|
293
321
|
if (denyMatch) {
|
|
294
322
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
295
323
|
}
|
|
@@ -326,9 +354,9 @@ function getPathToMatch(importInfo, rootDir) {
|
|
|
326
354
|
}
|
|
327
355
|
return importInfo.moduleSpecifier;
|
|
328
356
|
}
|
|
329
|
-
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
|
|
357
|
+
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
|
|
330
358
|
for (const rule of rules) {
|
|
331
|
-
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
|
|
359
|
+
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
|
|
332
360
|
return rule;
|
|
333
361
|
}
|
|
334
362
|
}
|
|
@@ -344,18 +372,20 @@ function getPackageName(moduleSpecifier) {
|
|
|
344
372
|
}
|
|
345
373
|
return moduleSpecifier.split("/")[0];
|
|
346
374
|
}
|
|
347
|
-
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
|
|
375
|
+
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
|
|
348
376
|
if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
349
377
|
const sourceDir = import_node_path3.default.dirname(sourceFile);
|
|
350
378
|
const resolvedPattern = import_node_path3.default.relative(rootDir, import_node_path3.default.resolve(sourceDir, pattern));
|
|
351
|
-
return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern
|
|
379
|
+
return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern);
|
|
352
380
|
}
|
|
353
381
|
if (pattern.includes("*")) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
382
|
+
if (isExternal) {
|
|
383
|
+
const packageName = getPackageName(pathToMatch);
|
|
384
|
+
if ((0, import_minimatch.minimatch)(packageName, pattern)) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
357
387
|
}
|
|
358
|
-
return (0, import_minimatch.minimatch)(pathToMatch, pattern
|
|
388
|
+
return (0, import_minimatch.minimatch)(pathToMatch, pattern);
|
|
359
389
|
}
|
|
360
390
|
const pathPackageName = getPackageName(pathToMatch);
|
|
361
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\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\t// For external packages with glob patterns, match against the package name\n\t\t// and also allow subpaths of matching packages\n\t\tconst packageName = getPackageName(pathToMatch);\n\t\tif (minimatch(packageName, pattern, { matchBase: true })) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also try matching the full path for more specific patterns\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n"],"mappings":"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","names":["fs","parseYaml","import_node_path","path","import_node_path","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/project.ts","../src/core/import-collector.ts","../src/rules/loader.ts","../src/rules/schema.ts","../src/rules/resolver.ts","../src/evaluator/import-boundary.ts","../src/evaluator/index.ts"],"sourcesContent":["export { checkProject } from \"./core/project.js\";\nexport { collectImports } from \"./core/import-collector.js\";\nexport { loadRules } from \"./rules/loader.js\";\nexport { resolveRules } from \"./rules/resolver.js\";\nexport { evaluate } from \"./evaluator/index.js\";\n\nexport type { ImportInfo, ProjectOptions } from \"./core/types.js\";\nexport type { ZoneFenceConfig, ImportRule } from \"./rules/types.js\";\nexport type { EvaluationResult, Violation } from \"./evaluator/types.js\";\n","import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\n/** @internal Exported for testing */\nexport function isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If it starts with . or /, it's a local import\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// If resolved path contains node_modules, it's an external package\n\tif (resolvedPath?.includes(\"node_modules\")) {\n\t\treturn true;\n\t}\n\n\t// If we have a resolved path that's not in node_modules, it's local\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package (unresolved bare specifier)\n\treturn true;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tconst isExternal = importInfo.isExternal;\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(\n\t\t\t\tpathToMatch,\n\t\t\t\tallowRules,\n\t\t\t\timportInfo.sourceFile,\n\t\t\t\trootDir,\n\t\t\t\tisExternal,\n\t\t\t);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern);\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\tif (isExternal) {\n\t\t\t// For external packages, also match against the package name\n\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\tif (minimatch(packageName, pattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// For internal paths (and external full path match), use direct minimatch\n\t\treturn minimatch(pathToMatch, pattern);\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n"],"mappings":"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","names":["fs","parseYaml","import_node_path","path","import_node_path","path"]}
|
package/dist/index.js
CHANGED
|
@@ -77,10 +77,13 @@ function resolveModulePath(decl, moduleSpecifier, _sourceFilePath) {
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
function isExternalImport(moduleSpecifier, resolvedPath) {
|
|
80
|
-
if (
|
|
80
|
+
if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
|
-
if (
|
|
83
|
+
if (resolvedPath?.includes("node_modules")) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (resolvedPath !== null) {
|
|
84
87
|
return false;
|
|
85
88
|
}
|
|
86
89
|
return true;
|
|
@@ -225,13 +228,26 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
225
228
|
const allowRules = imports.allow ?? [];
|
|
226
229
|
const denyRules = imports.deny ?? [];
|
|
227
230
|
const pathToMatch = getPathToMatch(importInfo, rootDir);
|
|
231
|
+
const isExternal = importInfo.isExternal;
|
|
228
232
|
if (mode === "allow-first") {
|
|
229
|
-
const denyMatch = findMatchingRule(
|
|
233
|
+
const denyMatch = findMatchingRule(
|
|
234
|
+
pathToMatch,
|
|
235
|
+
denyRules,
|
|
236
|
+
importInfo.sourceFile,
|
|
237
|
+
rootDir,
|
|
238
|
+
isExternal
|
|
239
|
+
);
|
|
230
240
|
if (denyMatch) {
|
|
231
241
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
232
242
|
}
|
|
233
243
|
if (allowRules.length > 0) {
|
|
234
|
-
const allowMatch = findMatchingRule(
|
|
244
|
+
const allowMatch = findMatchingRule(
|
|
245
|
+
pathToMatch,
|
|
246
|
+
allowRules,
|
|
247
|
+
importInfo.sourceFile,
|
|
248
|
+
rootDir,
|
|
249
|
+
isExternal
|
|
250
|
+
);
|
|
235
251
|
if (!allowMatch) {
|
|
236
252
|
return createViolation(
|
|
237
253
|
importInfo,
|
|
@@ -245,11 +261,23 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
|
|
|
245
261
|
}
|
|
246
262
|
}
|
|
247
263
|
} else {
|
|
248
|
-
const allowMatch = findMatchingRule(
|
|
264
|
+
const allowMatch = findMatchingRule(
|
|
265
|
+
pathToMatch,
|
|
266
|
+
allowRules,
|
|
267
|
+
importInfo.sourceFile,
|
|
268
|
+
rootDir,
|
|
269
|
+
isExternal
|
|
270
|
+
);
|
|
249
271
|
if (allowMatch) {
|
|
250
272
|
return null;
|
|
251
273
|
}
|
|
252
|
-
const denyMatch = findMatchingRule(
|
|
274
|
+
const denyMatch = findMatchingRule(
|
|
275
|
+
pathToMatch,
|
|
276
|
+
denyRules,
|
|
277
|
+
importInfo.sourceFile,
|
|
278
|
+
rootDir,
|
|
279
|
+
isExternal
|
|
280
|
+
);
|
|
253
281
|
if (denyMatch) {
|
|
254
282
|
return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
|
|
255
283
|
}
|
|
@@ -286,9 +314,9 @@ function getPathToMatch(importInfo, rootDir) {
|
|
|
286
314
|
}
|
|
287
315
|
return importInfo.moduleSpecifier;
|
|
288
316
|
}
|
|
289
|
-
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir) {
|
|
317
|
+
function findMatchingRule(pathToMatch, rules, sourceFile, rootDir, isExternal) {
|
|
290
318
|
for (const rule of rules) {
|
|
291
|
-
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {
|
|
319
|
+
if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
|
|
292
320
|
return rule;
|
|
293
321
|
}
|
|
294
322
|
}
|
|
@@ -304,18 +332,20 @@ function getPackageName(moduleSpecifier) {
|
|
|
304
332
|
}
|
|
305
333
|
return moduleSpecifier.split("/")[0];
|
|
306
334
|
}
|
|
307
|
-
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir) {
|
|
335
|
+
function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
|
|
308
336
|
if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
309
337
|
const sourceDir = path3.dirname(sourceFile);
|
|
310
338
|
const resolvedPattern = path3.relative(rootDir, path3.resolve(sourceDir, pattern));
|
|
311
|
-
return minimatch(pathToMatch, resolvedPattern
|
|
339
|
+
return minimatch(pathToMatch, resolvedPattern);
|
|
312
340
|
}
|
|
313
341
|
if (pattern.includes("*")) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
342
|
+
if (isExternal) {
|
|
343
|
+
const packageName = getPackageName(pathToMatch);
|
|
344
|
+
if (minimatch(packageName, pattern)) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
317
347
|
}
|
|
318
|
-
return minimatch(pathToMatch, pattern
|
|
348
|
+
return minimatch(pathToMatch, pattern);
|
|
319
349
|
}
|
|
320
350
|
const pathPackageName = getPackageName(pathToMatch);
|
|
321
351
|
const patternPackageName = getPackageName(pattern);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/project.ts","../src/core/import-collector.ts","../src/rules/loader.ts","../src/rules/schema.ts","../src/rules/resolver.ts","../src/evaluator/import-boundary.ts","../src/evaluator/index.ts"],"sourcesContent":["import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\nfunction isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If we have a resolved path, it's not external\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// If it starts with . or /, it's a local import (even if unresolved)\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package\n\treturn true;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(pathToMatch, allowRules, importInfo.sourceFile, rootDir);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(pathToMatch, denyRules, importInfo.sourceFile, rootDir);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern, { matchBase: true });\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\t// For external packages with glob patterns, match against the package name\n\t\t// and also allow subpaths of matching packages\n\t\tconst packageName = getPackageName(pathToMatch);\n\t\tif (minimatch(packageName, pattern, { matchBase: true })) {\n\t\t\treturn true;\n\t\t}\n\t\t// Also try matching the full path for more specific patterns\n\t\treturn minimatch(pathToMatch, pattern, { matchBase: true });\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n"],"mappings":"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","names":["path","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/project.ts","../src/core/import-collector.ts","../src/rules/loader.ts","../src/rules/schema.ts","../src/rules/resolver.ts","../src/evaluator/import-boundary.ts","../src/evaluator/index.ts"],"sourcesContent":["import { Project } from \"ts-morph\";\nimport type { ProjectOptions } from \"./types.js\";\n\nexport function createProject(options: ProjectOptions): Project {\n\tconst project = new Project({\n\t\ttsConfigFilePath: options.tsConfigFilePath,\n\t\tskipAddingFilesFromTsConfig: true,\n\t});\n\n\tproject.addSourceFilesAtPaths([\n\t\t`${options.rootDir}/**/*.ts`,\n\t\t`${options.rootDir}/**/*.tsx`,\n\t\t`!${options.rootDir}/**/node_modules/**`,\n\t\t`!${options.rootDir}/**/dist/**`,\n\t]);\n\n\treturn project;\n}\n\nexport function checkProject(options: ProjectOptions): Project {\n\treturn createProject(options);\n}\n","import type { ImportDeclaration, Project, SourceFile } from \"ts-morph\";\nimport type { ImportInfo } from \"./types.js\";\n\nexport function collectImports(project: Project, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\n\tfor (const sourceFile of project.getSourceFiles()) {\n\t\tconst fileImports = collectImportsFromFile(sourceFile, rootDir);\n\t\timports.push(...fileImports);\n\t}\n\n\treturn imports;\n}\n\nfunction collectImportsFromFile(sourceFile: SourceFile, rootDir: string): ImportInfo[] {\n\tconst imports: ImportInfo[] = [];\n\tconst filePath = sourceFile.getFilePath();\n\n\tfor (const importDecl of sourceFile.getImportDeclarations()) {\n\t\tconst importInfo = parseImportDeclaration(importDecl, filePath, rootDir);\n\t\timports.push(importInfo);\n\t}\n\n\t// Also collect dynamic imports and re-exports\n\tfor (const exportDecl of sourceFile.getExportDeclarations()) {\n\t\tconst moduleSpecifier = exportDecl.getModuleSpecifier();\n\t\tif (moduleSpecifier) {\n\t\t\tconst specifierValue = moduleSpecifier.getLiteralValue();\n\t\t\tconst resolvedPath = resolveModulePath(exportDecl, specifierValue, filePath);\n\t\t\tconst startLine = exportDecl.getStartLineNumber();\n\t\t\tconst startColumn = exportDecl.getStart() - exportDecl.getStartLinePos();\n\n\t\t\timports.push({\n\t\t\t\tsourceFile: filePath,\n\t\t\t\tmoduleSpecifier: specifierValue,\n\t\t\t\tresolvedPath,\n\t\t\t\tisExternal: isExternalImport(specifierValue, resolvedPath),\n\t\t\t\tline: startLine,\n\t\t\t\tcolumn: startColumn,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn imports;\n}\n\nfunction parseImportDeclaration(\n\timportDecl: ImportDeclaration,\n\tfilePath: string,\n\t_rootDir: string,\n): ImportInfo {\n\tconst moduleSpecifier = importDecl.getModuleSpecifierValue();\n\tconst resolvedPath = resolveModulePath(importDecl, moduleSpecifier, filePath);\n\tconst startLine = importDecl.getStartLineNumber();\n\tconst startColumn = importDecl.getStart() - importDecl.getStartLinePos();\n\n\treturn {\n\t\tsourceFile: filePath,\n\t\tmoduleSpecifier,\n\t\tresolvedPath,\n\t\tisExternal: isExternalImport(moduleSpecifier, resolvedPath),\n\t\tline: startLine,\n\t\tcolumn: startColumn,\n\t};\n}\n\nfunction resolveModulePath(\n\tdecl: ImportDeclaration | { getModuleSpecifierSourceFile: () => SourceFile | undefined },\n\tmoduleSpecifier: string,\n\t_sourceFilePath: string,\n): string | null {\n\t// Try to get the resolved source file from ts-morph\n\tconst resolvedSourceFile = decl.getModuleSpecifierSourceFile?.();\n\tif (resolvedSourceFile) {\n\t\treturn resolvedSourceFile.getFilePath();\n\t}\n\n\t// If it starts with . or /, it's a relative/absolute path that couldn't be resolved\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn null;\n\t}\n\n\t// External package - return null for resolved path\n\treturn null;\n}\n\n/** @internal Exported for testing */\nexport function isExternalImport(moduleSpecifier: string, resolvedPath: string | null): boolean {\n\t// If it starts with . or /, it's a local import\n\tif (moduleSpecifier.startsWith(\".\") || moduleSpecifier.startsWith(\"/\")) {\n\t\treturn false;\n\t}\n\n\t// If resolved path contains node_modules, it's an external package\n\tif (resolvedPath?.includes(\"node_modules\")) {\n\t\treturn true;\n\t}\n\n\t// If we have a resolved path that's not in node_modules, it's local\n\tif (resolvedPath !== null) {\n\t\treturn false;\n\t}\n\n\t// Otherwise, it's an external package (unresolved bare specifier)\n\treturn true;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { parseConfig } from \"./schema.js\";\nimport type { RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nconst RULE_FILE_NAME = \"zonefence.yaml\";\n\nexport async function loadRulesForDirectory(rootDir: string): Promise<RulesByDirectory> {\n\tconst rules: RulesByDirectory = {};\n\n\tawait scanDirectory(rootDir, rootDir, rules);\n\n\treturn rules;\n}\n\nasync function scanDirectory(\n\tcurrentDir: string,\n\trootDir: string,\n\trules: RulesByDirectory,\n): Promise<void> {\n\tconst ruleFilePath = path.join(currentDir, RULE_FILE_NAME);\n\n\tif (fs.existsSync(ruleFilePath)) {\n\t\tconst config = await loadRuleFile(ruleFilePath);\n\t\trules[currentDir] = {\n\t\t\tconfig,\n\t\t\truleFilePath,\n\t\t};\n\t}\n\n\t// Scan subdirectories\n\tconst entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (entry.isDirectory() && !shouldSkipDirectory(entry.name)) {\n\t\t\tconst subDir = path.join(currentDir, entry.name);\n\t\t\tawait scanDirectory(subDir, rootDir, rules);\n\t\t}\n\t}\n}\n\nasync function loadRuleFile(filePath: string): Promise<ZoneFenceConfig> {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n\nfunction shouldSkipDirectory(name: string): boolean {\n\tconst skipDirs = [\"node_modules\", \".git\", \"dist\", \"build\", \"coverage\"];\n\treturn skipDirs.includes(name) || name.startsWith(\".\");\n}\n\nexport function loadRules(filePath: string): ZoneFenceConfig {\n\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\tconst parsed = parseYaml(content);\n\treturn parseConfig(parsed);\n}\n","import { z } from \"zod\";\n\nconst importRuleSchema = z.union([\n\tz.string().transform((from) => ({ from })),\n\tz.object({\n\t\tfrom: z.string(),\n\t\tmessage: z.string().optional(),\n\t}),\n]);\n\nexport const zoneFenceConfigSchema = z.object({\n\tversion: z.number().int().positive(),\n\tdescription: z.string().optional(),\n\tscope: z\n\t\t.object({\n\t\t\tapply: z.enum([\"self\", \"descendants\"]).optional().default(\"descendants\"),\n\t\t\texclude: z.array(z.string()).optional().default([]),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\timports: z\n\t\t.object({\n\t\t\tallow: z.array(importRuleSchema).optional().default([]),\n\t\t\tdeny: z.array(importRuleSchema).optional().default([]),\n\t\t\tmode: z.enum([\"allow-first\", \"deny-first\"]).optional().default(\"allow-first\"),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n});\n\nexport type ParsedZoneFenceConfig = z.infer<typeof zoneFenceConfigSchema>;\n\nexport function parseConfig(data: unknown): ParsedZoneFenceConfig {\n\treturn zoneFenceConfigSchema.parse(data);\n}\n\nexport function validateConfig(\n\tdata: unknown,\n): { success: true; data: ParsedZoneFenceConfig } | { success: false; error: z.ZodError } {\n\tconst result = zoneFenceConfigSchema.safeParse(data);\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\treturn { success: false, error: result.error };\n}\n","import path from \"node:path\";\nimport type { ImportRule, ResolvedRule, RulesByDirectory, ZoneFenceConfig } from \"./types.js\";\n\nexport function resolveRules(rulesByDirectory: RulesByDirectory): ResolvedRule[] {\n\tconst resolvedRules: ResolvedRule[] = [];\n\tconst directories = Object.keys(rulesByDirectory).sort((a, b) => a.length - b.length);\n\n\tfor (const directory of directories) {\n\t\tconst { config, ruleFilePath } = rulesByDirectory[directory];\n\n\t\t// Find parent rules\n\t\tconst parentRules = findParentRules(directory, directories, rulesByDirectory);\n\n\t\t// Merge with parent rules\n\t\tconst mergedConfig = mergeConfigs(parentRules, config);\n\n\t\t// Collect exclude patterns\n\t\tconst excludePatterns = collectExcludePatterns(mergedConfig);\n\n\t\tresolvedRules.push({\n\t\t\tdirectory,\n\t\t\truleFilePath,\n\t\t\tconfig: mergedConfig,\n\t\t\texcludePatterns,\n\t\t});\n\t}\n\n\treturn resolvedRules;\n}\n\nfunction findParentRules(\n\tdirectory: string,\n\tallDirectories: string[],\n\trulesByDirectory: RulesByDirectory,\n): ZoneFenceConfig[] {\n\tconst parents: ZoneFenceConfig[] = [];\n\n\tfor (const potentialParent of allDirectories) {\n\t\tif (potentialParent === directory) continue;\n\n\t\t// Check if potentialParent is an ancestor of directory\n\t\tconst relative = path.relative(potentialParent, directory);\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tconst parentConfig = rulesByDirectory[potentialParent].config;\n\n\t\t\t// Only include if scope.apply is \"descendants\"\n\t\t\tconst scopeApply = parentConfig.scope?.apply ?? \"descendants\";\n\t\t\tif (scopeApply === \"descendants\") {\n\t\t\t\tparents.push(parentConfig);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parents;\n}\n\nfunction mergeConfigs(parents: ZoneFenceConfig[], child: ZoneFenceConfig): ZoneFenceConfig {\n\tif (parents.length === 0) {\n\t\treturn child;\n\t}\n\n\t// Start with the first parent and merge subsequent ones\n\tlet merged: ZoneFenceConfig = { version: child.version };\n\n\tfor (const parent of parents) {\n\t\tmerged = mergeTwoConfigs(merged, parent);\n\t}\n\n\t// Finally merge with child (child takes precedence)\n\tmerged = mergeTwoConfigs(merged, child);\n\n\treturn merged;\n}\n\nfunction mergeTwoConfigs(base: ZoneFenceConfig, override: ZoneFenceConfig): ZoneFenceConfig {\n\tconst merged: ZoneFenceConfig = {\n\t\tversion: override.version ?? base.version,\n\t\tdescription: override.description ?? base.description,\n\t};\n\n\t// Merge scope\n\tif (base.scope || override.scope) {\n\t\tmerged.scope = {\n\t\t\tapply: override.scope?.apply ?? base.scope?.apply ?? \"descendants\",\n\t\t\texclude: mergeArrays(base.scope?.exclude, override.scope?.exclude),\n\t\t};\n\t}\n\n\t// Merge imports\n\tif (base.imports || override.imports) {\n\t\tmerged.imports = {\n\t\t\tallow: mergeImportRules(base.imports?.allow, override.imports?.allow),\n\t\t\tdeny: mergeImportRules(base.imports?.deny, override.imports?.deny),\n\t\t\tmode: override.imports?.mode ?? base.imports?.mode ?? \"allow-first\",\n\t\t};\n\t}\n\n\treturn merged;\n}\n\nfunction mergeArrays<T>(base?: T[], override?: T[]): T[] {\n\tconst result: T[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction mergeImportRules(base?: ImportRule[], override?: ImportRule[]): ImportRule[] {\n\tconst result: ImportRule[] = [];\n\tif (base) result.push(...base);\n\tif (override) result.push(...override);\n\treturn result;\n}\n\nfunction collectExcludePatterns(config: ZoneFenceConfig): string[] {\n\treturn config.scope?.exclude ?? [];\n}\n","import path from \"node:path\";\nimport { minimatch } from \"minimatch\";\nimport type { ImportInfo } from \"../core/types.js\";\nimport type { ImportRule, ResolvedRule } from \"../rules/types.js\";\nimport type { Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n): Violation | null {\n\t// Find the applicable rule for this file\n\tconst applicableRule = findApplicableRule(importInfo.sourceFile, rules);\n\n\tif (!applicableRule) {\n\t\t// No rules apply to this file, allow the import\n\t\treturn null;\n\t}\n\n\t// Check if file is excluded\n\tif (isExcluded(importInfo.sourceFile, applicableRule, rootDir)) {\n\t\treturn null;\n\t}\n\n\tconst { config, ruleFilePath } = applicableRule;\n\tconst imports = config.imports;\n\n\tif (!imports) {\n\t\treturn null;\n\t}\n\n\tconst mode = imports.mode ?? \"allow-first\";\n\tconst allowRules = imports.allow ?? [];\n\tconst denyRules = imports.deny ?? [];\n\n\t// Get the path to match against (resolved path or module specifier)\n\tconst pathToMatch = getPathToMatch(importInfo, rootDir);\n\n\tconst isExternal = importInfo.isExternal;\n\n\tif (mode === \"allow-first\") {\n\t\t// Check deny rules first, then allow rules\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// If there are allow rules, import must match at least one\n\t\tif (allowRules.length > 0) {\n\t\t\tconst allowMatch = findMatchingRule(\n\t\t\t\tpathToMatch,\n\t\t\t\tallowRules,\n\t\t\t\timportInfo.sourceFile,\n\t\t\t\trootDir,\n\t\t\t\tisExternal,\n\t\t\t);\n\t\t\tif (!allowMatch) {\n\t\t\t\treturn createViolation(\n\t\t\t\t\timportInfo,\n\t\t\t\t\t{\n\t\t\t\t\t\tfrom: pathToMatch,\n\t\t\t\t\t\tmessage: `Import from \"${importInfo.moduleSpecifier}\" is not in the allow list`,\n\t\t\t\t\t},\n\t\t\t\t\truleFilePath,\n\t\t\t\t\tconfig.description,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// deny-first: Check allow rules first, then deny rules\n\t\tconst allowMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (allowMatch) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst denyMatch = findMatchingRule(\n\t\t\tpathToMatch,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t);\n\t\tif (denyMatch) {\n\t\t\treturn createViolation(importInfo, denyMatch, ruleFilePath, config.description);\n\t\t}\n\n\t\t// In deny-first mode, if no rules match, allow by default\n\t}\n\n\treturn null;\n}\n\nfunction findApplicableRule(filePath: string, rules: ResolvedRule[]): ResolvedRule | null {\n\t// Find the most specific rule (deepest directory) that applies to this file\n\tlet mostSpecific: ResolvedRule | null = null;\n\n\tfor (const rule of rules) {\n\t\tconst relative = path.relative(rule.directory, filePath);\n\t\t// Check if file is within this directory\n\t\tif (!relative.startsWith(\"..\") && !path.isAbsolute(relative)) {\n\t\t\tif (!mostSpecific || rule.directory.length > mostSpecific.directory.length) {\n\t\t\t\tmostSpecific = rule;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mostSpecific;\n}\n\nfunction isExcluded(filePath: string, rule: ResolvedRule, rootDir: string): boolean {\n\tconst relativePath = path.relative(rootDir, filePath);\n\n\tfor (const pattern of rule.excludePatterns) {\n\t\tif (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction getPathToMatch(importInfo: ImportInfo, rootDir: string): string {\n\t// For external imports, use the module specifier\n\tif (importInfo.isExternal) {\n\t\treturn importInfo.moduleSpecifier;\n\t}\n\n\t// For resolved local imports, use the relative path from root\n\tif (importInfo.resolvedPath) {\n\t\treturn path.relative(rootDir, importInfo.resolvedPath);\n\t}\n\n\t// For unresolved local imports, use the module specifier\n\treturn importInfo.moduleSpecifier;\n}\n\nfunction findMatchingRule(\n\tpathToMatch: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Extract the package name from an import specifier.\n * For scoped packages like @babel/core/lib, returns @babel/core\n * For regular packages like lodash/get, returns lodash\n */\nfunction getPackageName(moduleSpecifier: string): string {\n\tif (moduleSpecifier.startsWith(\"@\")) {\n\t\t// Scoped package: @scope/package or @scope/package/subpath\n\t\tconst parts = moduleSpecifier.split(\"/\");\n\t\tif (parts.length >= 2) {\n\t\t\treturn `${parts[0]}/${parts[1]}`;\n\t\t}\n\t\treturn moduleSpecifier;\n\t}\n\t// Regular package: package or package/subpath\n\treturn moduleSpecifier.split(\"/\")[0];\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): boolean {\n\t// Handle relative patterns (starting with ./)\n\tif (pattern.startsWith(\"./\") || pattern.startsWith(\"../\")) {\n\t\t// Resolve pattern relative to source file's directory\n\t\tconst sourceDir = path.dirname(sourceFile);\n\t\tconst resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));\n\t\treturn minimatch(pathToMatch, resolvedPattern);\n\t}\n\n\t// Handle glob patterns\n\tif (pattern.includes(\"*\")) {\n\t\tif (isExternal) {\n\t\t\t// For external packages, also match against the package name\n\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\tif (minimatch(packageName, pattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// For internal paths (and external full path match), use direct minimatch\n\t\treturn minimatch(pathToMatch, pattern);\n\t}\n\n\t// For non-glob patterns, extract package names and compare\n\tconst pathPackageName = getPackageName(pathToMatch);\n\tconst patternPackageName = getPackageName(pattern);\n\n\t// Exact package match or subpath of the same package\n\tif (pathPackageName === patternPackageName) {\n\t\t// If pattern is the full package name, allow any subpath\n\t\tif (pattern === patternPackageName) {\n\t\t\treturn true;\n\t\t}\n\t\t// If pattern includes subpath, require exact match or subpath\n\t\tif (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nfunction createViolation(\n\timportInfo: ImportInfo,\n\trule: ImportRule,\n\truleFilePath: string,\n\tdesignIntent?: string,\n): Violation {\n\treturn {\n\t\tsourceFile: importInfo.sourceFile,\n\t\tmoduleSpecifier: importInfo.moduleSpecifier,\n\t\tline: importInfo.line,\n\t\tcolumn: importInfo.column,\n\t\trule: \"import-boundary\",\n\t\tmessage: rule.message ?? `Import from \"${importInfo.moduleSpecifier}\" is not allowed`,\n\t\truleFilePath,\n\t\tdesignIntent,\n\t};\n}\n","import type { ImportInfo } from \"../core/types.js\";\nimport type { ResolvedRule } from \"../rules/types.js\";\nimport { evaluateImportBoundary } from \"./import-boundary.js\";\nimport type { EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n): EvaluationResult {\n\tconst violations: Violation[] = [];\n\tconst checkedFiles = new Set<string>();\n\n\tfor (const importInfo of imports) {\n\t\tcheckedFiles.add(importInfo.sourceFile);\n\n\t\tconst violation = evaluateImportBoundary(importInfo, rules, rootDir);\n\t\tif (violation) {\n\t\t\tviolations.push(violation);\n\t\t}\n\t}\n\n\treturn {\n\t\tviolations,\n\t\tfilesChecked: checkedFiles.size,\n\t\timportsChecked: imports.length,\n\t};\n}\n\nexport { evaluateImportBoundary } from \"./import-boundary.js\";\nexport type { EvaluationResult, Violation } from \"./types.js\";\n"],"mappings":"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","names":["path","path"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zonefence",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"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",
|