zonefence 0.0.6 → 0.0.7

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.
@@ -121,7 +121,7 @@ function createProject(options) {
121
121
  // src/evaluator/import-boundary.ts
122
122
  var import_node_path = __toESM(require("path"), 1);
123
123
  var import_minimatch = require("minimatch");
124
- function evaluateImportBoundary(importInfo, rules, rootDir) {
124
+ function evaluateImportBoundary(importInfo, rules, rootDir, options = {}) {
125
125
  const applicableRule = findApplicableRule(importInfo.sourceFile, rules);
126
126
  if (!applicableRule) {
127
127
  return null;
@@ -140,6 +140,7 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
140
140
  const pathToMatch = getPathToMatch(importInfo, rootDir);
141
141
  const isExternal = importInfo.isExternal;
142
142
  const moduleSpecifier = importInfo.moduleSpecifier;
143
+ const pathsMapping = options.pathsMapping;
143
144
  if (mode === "allow-first") {
144
145
  const denyMatch = findMatchingRule(
145
146
  pathToMatch,
@@ -147,7 +148,8 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
147
148
  denyRules,
148
149
  importInfo.sourceFile,
149
150
  rootDir,
150
- isExternal
151
+ isExternal,
152
+ pathsMapping
151
153
  );
152
154
  if (denyMatch) {
153
155
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
@@ -159,7 +161,8 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
159
161
  allowRules,
160
162
  importInfo.sourceFile,
161
163
  rootDir,
162
- isExternal
164
+ isExternal,
165
+ pathsMapping
163
166
  );
164
167
  if (!allowMatch) {
165
168
  return createViolation(
@@ -180,7 +183,8 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
180
183
  allowRules,
181
184
  importInfo.sourceFile,
182
185
  rootDir,
183
- isExternal
186
+ isExternal,
187
+ pathsMapping
184
188
  );
185
189
  if (allowMatch) {
186
190
  return null;
@@ -191,7 +195,8 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
191
195
  denyRules,
192
196
  importInfo.sourceFile,
193
197
  rootDir,
194
- isExternal
198
+ isExternal,
199
+ pathsMapping
195
200
  );
196
201
  if (denyMatch) {
197
202
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
@@ -229,12 +234,12 @@ function getPathToMatch(importInfo, rootDir) {
229
234
  }
230
235
  return importInfo.moduleSpecifier;
231
236
  }
232
- function findMatchingRule(pathToMatch, moduleSpecifier, rules, sourceFile, rootDir, isExternal) {
237
+ function findMatchingRule(pathToMatch, moduleSpecifier, rules, sourceFile, rootDir, isExternal, pathsMapping) {
233
238
  for (const rule of rules) {
234
- if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
239
+ if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal, pathsMapping)) {
235
240
  return rule;
236
241
  }
237
- if (pathToMatch !== moduleSpecifier && matchesPattern(moduleSpecifier, rule.from, sourceFile, rootDir, isExternal)) {
242
+ if (pathToMatch !== moduleSpecifier && matchesPattern(moduleSpecifier, rule.from, sourceFile, rootDir, isExternal, pathsMapping)) {
238
243
  return rule;
239
244
  }
240
245
  }
@@ -250,29 +255,52 @@ function getPackageName(moduleSpecifier) {
250
255
  }
251
256
  return moduleSpecifier.split("/")[0];
252
257
  }
253
- function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
258
+ function resolvePatternWithPaths(pattern, pathsMapping) {
259
+ if (!pathsMapping) {
260
+ return [pattern];
261
+ }
262
+ const resolvedPatterns = [pattern];
263
+ for (const [alias, targets] of Object.entries(pathsMapping)) {
264
+ const aliasBase = alias.replace(/\*$/, "");
265
+ if (pattern.startsWith(aliasBase)) {
266
+ const remainder = pattern.slice(aliasBase.length);
267
+ for (const target of targets) {
268
+ const targetBase = target.replace(/^\.\//, "").replace(/\*$/, "");
269
+ resolvedPatterns.push(targetBase + remainder);
270
+ }
271
+ }
272
+ }
273
+ return resolvedPatterns;
274
+ }
275
+ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal, pathsMapping) {
254
276
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
255
277
  const sourceDir = import_node_path.default.dirname(sourceFile);
256
278
  const resolvedPattern = import_node_path.default.relative(rootDir, import_node_path.default.resolve(sourceDir, pattern));
257
279
  return (0, import_minimatch.minimatch)(pathToMatch, resolvedPattern);
258
280
  }
259
- if (pattern.includes("*")) {
260
- if (isExternal) {
261
- const packageName = getPackageName(pathToMatch);
262
- if ((0, import_minimatch.minimatch)(packageName, pattern)) {
281
+ const patternsToTry = resolvePatternWithPaths(pattern, pathsMapping);
282
+ for (const currentPattern of patternsToTry) {
283
+ if (currentPattern.includes("*")) {
284
+ if (isExternal) {
285
+ const packageName = getPackageName(pathToMatch);
286
+ if ((0, import_minimatch.minimatch)(packageName, currentPattern)) {
287
+ return true;
288
+ }
289
+ }
290
+ if ((0, import_minimatch.minimatch)(pathToMatch, currentPattern)) {
263
291
  return true;
264
292
  }
293
+ continue;
265
294
  }
266
- return (0, import_minimatch.minimatch)(pathToMatch, pattern);
267
- }
268
- const pathPackageName = getPackageName(pathToMatch);
269
- const patternPackageName = getPackageName(pattern);
270
- if (pathPackageName === patternPackageName) {
271
- if (pattern === patternPackageName) {
272
- return true;
273
- }
274
- if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
275
- return true;
295
+ const pathPackageName = getPackageName(pathToMatch);
296
+ const patternPackageName = getPackageName(currentPattern);
297
+ if (pathPackageName === patternPackageName) {
298
+ if (currentPattern === patternPackageName) {
299
+ return true;
300
+ }
301
+ if (pathToMatch === currentPattern || pathToMatch.startsWith(`${currentPattern}/`)) {
302
+ return true;
303
+ }
276
304
  }
277
305
  }
278
306
  return false;
@@ -291,12 +319,12 @@ function createViolation(importInfo, rule, ruleFilePath, designIntent) {
291
319
  }
292
320
 
293
321
  // src/evaluator/index.ts
294
- function evaluate(imports, rules, rootDir) {
322
+ function evaluate(imports, rules, rootDir, options = {}) {
295
323
  const violations = [];
296
324
  const checkedFiles = /* @__PURE__ */ new Set();
297
325
  for (const importInfo of imports) {
298
326
  checkedFiles.add(importInfo.sourceFile);
299
- const violation = evaluateImportBoundary(importInfo, rules, rootDir);
327
+ const violation = evaluateImportBoundary(importInfo, rules, rootDir, options);
300
328
  if (violation) {
301
329
  violations.push(violation);
302
330
  }
@@ -527,6 +555,10 @@ function findTsConfig(startDir) {
527
555
  }
528
556
  return void 0;
529
557
  }
558
+ function getPathsMapping(project) {
559
+ const compilerOptions = project.getCompilerOptions();
560
+ return compilerOptions?.paths;
561
+ }
530
562
  async function checkCommand(targetPath, options) {
531
563
  const absolutePath = import_node_path5.default.resolve(targetPath);
532
564
  console.log(`Checking import boundaries in: ${absolutePath}
@@ -544,7 +576,8 @@ async function checkCommand(targetPath, options) {
544
576
  }
545
577
  const rulesByDir = await loadRulesForDirectory(absolutePath);
546
578
  const resolvedRules = resolveRules(rulesByDir);
547
- const results = evaluate(imports, resolvedRules, absolutePath);
579
+ const pathsMapping = getPathsMapping(project);
580
+ const results = evaluate(imports, resolvedRules, absolutePath, { pathsMapping });
548
581
  const exitCode = reportToConsole(results, {
549
582
  color: options.color !== false
550
583
  });
@@ -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 fs from \"node:fs\";\nimport 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\n/**\n * Find tsconfig.json by searching upward from the start directory\n */\nfunction findTsConfig(startDir: string): string | undefined {\n\tlet dir = startDir;\n\twhile (dir !== path.dirname(dir)) {\n\t\tconst configPath = path.join(dir, \"tsconfig.json\");\n\t\tif (fs.existsSync(configPath)) {\n\t\t\treturn configPath;\n\t\t}\n\t\tdir = path.dirname(dir);\n\t}\n\treturn undefined;\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\t// Use provided config or auto-detect tsconfig.json\n\t\tconst tsConfigFilePath = options.config ?? findTsConfig(absolutePath);\n\n\t\tconst project = createProject({\n\t\t\ttsConfigFilePath,\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\tconst moduleSpecifier = importInfo.moduleSpecifier;\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\tmoduleSpecifier,\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\tmoduleSpecifier,\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\tmoduleSpecifier,\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\tmoduleSpecifier,\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\tmoduleSpecifier: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\t// First, try matching against the resolved path\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {\n\t\t\treturn rule;\n\t\t}\n\t\t// Also try matching against the original module specifier\n\t\t// This allows patterns like \"@/api/**\" or \"@image-router/*\" to work\n\t\tif (\n\t\t\tpathToMatch !== moduleSpecifier &&\n\t\t\tmatchesPattern(moduleSpecifier, rule.from, sourceFile, rootDir, isExternal)\n\t\t) {\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_fs","import_node_path","path","import_node_path","chalk","path","violations","import_node_path","path","fs","parseYaml","import_node_path","path","path","fs"]}
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 fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { Project } from \"ts-morph\";\nimport { collectImports } from \"../../core/import-collector.js\";\nimport { createProject } from \"../../core/project.js\";\nimport { evaluate } from \"../../evaluator/index.js\";\nimport type { PathsMapping } from \"../../evaluator/types.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\n/**\n * Find tsconfig.json by searching upward from the start directory\n */\nfunction findTsConfig(startDir: string): string | undefined {\n\tlet dir = startDir;\n\twhile (dir !== path.dirname(dir)) {\n\t\tconst configPath = path.join(dir, \"tsconfig.json\");\n\t\tif (fs.existsSync(configPath)) {\n\t\t\treturn configPath;\n\t\t}\n\t\tdir = path.dirname(dir);\n\t}\n\treturn undefined;\n}\n\n/**\n * Extract paths mapping from ts-morph project\n */\nfunction getPathsMapping(project: Project): PathsMapping | undefined {\n\tconst compilerOptions = project.getCompilerOptions();\n\treturn compilerOptions?.paths as PathsMapping | undefined;\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\t// Use provided config or auto-detect tsconfig.json\n\t\tconst tsConfigFilePath = options.config ?? findTsConfig(absolutePath);\n\n\t\tconst project = createProject({\n\t\t\ttsConfigFilePath,\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\t// Get paths mapping from tsconfig for pattern resolution\n\t\tconst pathsMapping = getPathsMapping(project);\n\n\t\tconst results = evaluate(imports, resolvedRules, absolutePath, { pathsMapping });\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 { EvaluateOptions, PathsMapping, Violation } from \"./types.js\";\n\nexport function evaluateImportBoundary(\n\timportInfo: ImportInfo,\n\trules: ResolvedRule[],\n\trootDir: string,\n\toptions: EvaluateOptions = {},\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\tconst moduleSpecifier = importInfo.moduleSpecifier;\n\tconst pathsMapping = options.pathsMapping;\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\tmoduleSpecifier,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t\tpathsMapping,\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\tmoduleSpecifier,\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\tpathsMapping,\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\tmoduleSpecifier,\n\t\t\tallowRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t\tpathsMapping,\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\tmoduleSpecifier,\n\t\t\tdenyRules,\n\t\t\timportInfo.sourceFile,\n\t\t\trootDir,\n\t\t\tisExternal,\n\t\t\tpathsMapping,\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\tmoduleSpecifier: string,\n\trules: ImportRule[],\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n\tpathsMapping?: PathsMapping,\n): ImportRule | null {\n\tfor (const rule of rules) {\n\t\t// First, try matching against the resolved path\n\t\tif (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal, pathsMapping)) {\n\t\t\treturn rule;\n\t\t}\n\t\t// Also try matching against the original module specifier\n\t\t// This allows patterns like \"@/api/**\" or \"@image-router/*\" to work\n\t\tif (\n\t\t\tpathToMatch !== moduleSpecifier &&\n\t\t\tmatchesPattern(moduleSpecifier, rule.from, sourceFile, rootDir, isExternal, pathsMapping)\n\t\t) {\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\n/**\n * Resolve a pattern using tsconfig paths mapping\n * e.g., \"@/api/**\" with paths {\"@/*\": [\"./src/*\"]} -> \"src/api/**\"\n */\nfunction resolvePatternWithPaths(pattern: string, pathsMapping?: PathsMapping): string[] {\n\tif (!pathsMapping) {\n\t\treturn [pattern];\n\t}\n\n\tconst resolvedPatterns: string[] = [pattern];\n\n\tfor (const [alias, targets] of Object.entries(pathsMapping)) {\n\t\t// Convert alias pattern to regex (e.g., \"@/*\" -> \"^@/(.*)$\")\n\t\tconst aliasBase = alias.replace(/\\*$/, \"\");\n\t\tif (pattern.startsWith(aliasBase)) {\n\t\t\tconst remainder = pattern.slice(aliasBase.length);\n\t\t\tfor (const target of targets) {\n\t\t\t\t// Convert target pattern (e.g., \"./src/*\" -> \"src/\")\n\t\t\t\tconst targetBase = target.replace(/^\\.\\//, \"\").replace(/\\*$/, \"\");\n\t\t\t\tresolvedPatterns.push(targetBase + remainder);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn resolvedPatterns;\n}\n\nfunction matchesPattern(\n\tpathToMatch: string,\n\tpattern: string,\n\tsourceFile: string,\n\trootDir: string,\n\tisExternal: boolean,\n\tpathsMapping?: PathsMapping,\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// Get all possible patterns (original + resolved via paths)\n\tconst patternsToTry = resolvePatternWithPaths(pattern, pathsMapping);\n\n\tfor (const currentPattern of patternsToTry) {\n\t\t// Handle glob patterns\n\t\tif (currentPattern.includes(\"*\")) {\n\t\t\tif (isExternal) {\n\t\t\t\t// For external packages, also match against the package name\n\t\t\t\t// e.g., pattern \"lodash/*\" should match \"lodash/get\"\n\t\t\t\tconst packageName = getPackageName(pathToMatch);\n\t\t\t\tif (minimatch(packageName, currentPattern)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// For internal paths (and external full path match), use direct minimatch\n\t\t\tif (minimatch(pathToMatch, currentPattern)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// For non-glob patterns, extract package names and compare\n\t\tconst pathPackageName = getPackageName(pathToMatch);\n\t\tconst patternPackageName = getPackageName(currentPattern);\n\n\t\t// Exact package match or subpath of the same package\n\t\tif (pathPackageName === patternPackageName) {\n\t\t\t// If pattern is the full package name, allow any subpath\n\t\t\tif (currentPattern === patternPackageName) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t// If pattern includes subpath, require exact match or subpath\n\t\t\tif (pathToMatch === currentPattern || pathToMatch.startsWith(`${currentPattern}/`)) {\n\t\t\t\treturn true;\n\t\t\t}\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 { EvaluateOptions, EvaluationResult, Violation } from \"./types.js\";\n\nexport function evaluate(\n\timports: ImportInfo[],\n\trules: ResolvedRule[],\n\trootDir: string,\n\toptions: EvaluateOptions = {},\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, options);\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_fs","import_node_path","path","import_node_path","chalk","path","violations","import_node_path","path","fs","parseYaml","import_node_path","path","path","fs"]}
package/dist/cli/index.js CHANGED
@@ -98,7 +98,7 @@ function createProject(options) {
98
98
  // src/evaluator/import-boundary.ts
99
99
  import path from "path";
100
100
  import { minimatch } from "minimatch";
101
- function evaluateImportBoundary(importInfo, rules, rootDir) {
101
+ function evaluateImportBoundary(importInfo, rules, rootDir, options = {}) {
102
102
  const applicableRule = findApplicableRule(importInfo.sourceFile, rules);
103
103
  if (!applicableRule) {
104
104
  return null;
@@ -117,6 +117,7 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
117
117
  const pathToMatch = getPathToMatch(importInfo, rootDir);
118
118
  const isExternal = importInfo.isExternal;
119
119
  const moduleSpecifier = importInfo.moduleSpecifier;
120
+ const pathsMapping = options.pathsMapping;
120
121
  if (mode === "allow-first") {
121
122
  const denyMatch = findMatchingRule(
122
123
  pathToMatch,
@@ -124,7 +125,8 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
124
125
  denyRules,
125
126
  importInfo.sourceFile,
126
127
  rootDir,
127
- isExternal
128
+ isExternal,
129
+ pathsMapping
128
130
  );
129
131
  if (denyMatch) {
130
132
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
@@ -136,7 +138,8 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
136
138
  allowRules,
137
139
  importInfo.sourceFile,
138
140
  rootDir,
139
- isExternal
141
+ isExternal,
142
+ pathsMapping
140
143
  );
141
144
  if (!allowMatch) {
142
145
  return createViolation(
@@ -157,7 +160,8 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
157
160
  allowRules,
158
161
  importInfo.sourceFile,
159
162
  rootDir,
160
- isExternal
163
+ isExternal,
164
+ pathsMapping
161
165
  );
162
166
  if (allowMatch) {
163
167
  return null;
@@ -168,7 +172,8 @@ function evaluateImportBoundary(importInfo, rules, rootDir) {
168
172
  denyRules,
169
173
  importInfo.sourceFile,
170
174
  rootDir,
171
- isExternal
175
+ isExternal,
176
+ pathsMapping
172
177
  );
173
178
  if (denyMatch) {
174
179
  return createViolation(importInfo, denyMatch, ruleFilePath, config.description);
@@ -206,12 +211,12 @@ function getPathToMatch(importInfo, rootDir) {
206
211
  }
207
212
  return importInfo.moduleSpecifier;
208
213
  }
209
- function findMatchingRule(pathToMatch, moduleSpecifier, rules, sourceFile, rootDir, isExternal) {
214
+ function findMatchingRule(pathToMatch, moduleSpecifier, rules, sourceFile, rootDir, isExternal, pathsMapping) {
210
215
  for (const rule of rules) {
211
- if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal)) {
216
+ if (matchesPattern(pathToMatch, rule.from, sourceFile, rootDir, isExternal, pathsMapping)) {
212
217
  return rule;
213
218
  }
214
- if (pathToMatch !== moduleSpecifier && matchesPattern(moduleSpecifier, rule.from, sourceFile, rootDir, isExternal)) {
219
+ if (pathToMatch !== moduleSpecifier && matchesPattern(moduleSpecifier, rule.from, sourceFile, rootDir, isExternal, pathsMapping)) {
215
220
  return rule;
216
221
  }
217
222
  }
@@ -227,29 +232,52 @@ function getPackageName(moduleSpecifier) {
227
232
  }
228
233
  return moduleSpecifier.split("/")[0];
229
234
  }
230
- function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal) {
235
+ function resolvePatternWithPaths(pattern, pathsMapping) {
236
+ if (!pathsMapping) {
237
+ return [pattern];
238
+ }
239
+ const resolvedPatterns = [pattern];
240
+ for (const [alias, targets] of Object.entries(pathsMapping)) {
241
+ const aliasBase = alias.replace(/\*$/, "");
242
+ if (pattern.startsWith(aliasBase)) {
243
+ const remainder = pattern.slice(aliasBase.length);
244
+ for (const target of targets) {
245
+ const targetBase = target.replace(/^\.\//, "").replace(/\*$/, "");
246
+ resolvedPatterns.push(targetBase + remainder);
247
+ }
248
+ }
249
+ }
250
+ return resolvedPatterns;
251
+ }
252
+ function matchesPattern(pathToMatch, pattern, sourceFile, rootDir, isExternal, pathsMapping) {
231
253
  if (pattern.startsWith("./") || pattern.startsWith("../")) {
232
254
  const sourceDir = path.dirname(sourceFile);
233
255
  const resolvedPattern = path.relative(rootDir, path.resolve(sourceDir, pattern));
234
256
  return minimatch(pathToMatch, resolvedPattern);
235
257
  }
236
- if (pattern.includes("*")) {
237
- if (isExternal) {
238
- const packageName = getPackageName(pathToMatch);
239
- if (minimatch(packageName, pattern)) {
258
+ const patternsToTry = resolvePatternWithPaths(pattern, pathsMapping);
259
+ for (const currentPattern of patternsToTry) {
260
+ if (currentPattern.includes("*")) {
261
+ if (isExternal) {
262
+ const packageName = getPackageName(pathToMatch);
263
+ if (minimatch(packageName, currentPattern)) {
264
+ return true;
265
+ }
266
+ }
267
+ if (minimatch(pathToMatch, currentPattern)) {
240
268
  return true;
241
269
  }
270
+ continue;
242
271
  }
243
- return minimatch(pathToMatch, pattern);
244
- }
245
- const pathPackageName = getPackageName(pathToMatch);
246
- const patternPackageName = getPackageName(pattern);
247
- if (pathPackageName === patternPackageName) {
248
- if (pattern === patternPackageName) {
249
- return true;
250
- }
251
- if (pathToMatch === pattern || pathToMatch.startsWith(`${pattern}/`)) {
252
- return true;
272
+ const pathPackageName = getPackageName(pathToMatch);
273
+ const patternPackageName = getPackageName(currentPattern);
274
+ if (pathPackageName === patternPackageName) {
275
+ if (currentPattern === patternPackageName) {
276
+ return true;
277
+ }
278
+ if (pathToMatch === currentPattern || pathToMatch.startsWith(`${currentPattern}/`)) {
279
+ return true;
280
+ }
253
281
  }
254
282
  }
255
283
  return false;
@@ -268,12 +296,12 @@ function createViolation(importInfo, rule, ruleFilePath, designIntent) {
268
296
  }
269
297
 
270
298
  // src/evaluator/index.ts
271
- function evaluate(imports, rules, rootDir) {
299
+ function evaluate(imports, rules, rootDir, options = {}) {
272
300
  const violations = [];
273
301
  const checkedFiles = /* @__PURE__ */ new Set();
274
302
  for (const importInfo of imports) {
275
303
  checkedFiles.add(importInfo.sourceFile);
276
- const violation = evaluateImportBoundary(importInfo, rules, rootDir);
304
+ const violation = evaluateImportBoundary(importInfo, rules, rootDir, options);
277
305
  if (violation) {
278
306
  violations.push(violation);
279
307
  }
@@ -504,6 +532,10 @@ function findTsConfig(startDir) {
504
532
  }
505
533
  return void 0;
506
534
  }
535
+ function getPathsMapping(project) {
536
+ const compilerOptions = project.getCompilerOptions();
537
+ return compilerOptions?.paths;
538
+ }
507
539
  async function checkCommand(targetPath, options) {
508
540
  const absolutePath = path5.resolve(targetPath);
509
541
  console.log(`Checking import boundaries in: ${absolutePath}
@@ -521,7 +553,8 @@ async function checkCommand(targetPath, options) {
521
553
  }
522
554
  const rulesByDir = await loadRulesForDirectory(absolutePath);
523
555
  const resolvedRules = resolveRules(rulesByDir);
524
- const results = evaluate(imports, resolvedRules, absolutePath);
556
+ const pathsMapping = getPathsMapping(project);
557
+ const results = evaluate(imports, resolvedRules, absolutePath, { pathsMapping });
525
558
  const exitCode = reportToConsole(results, {
526
559
  color: options.color !== false
527
560
  });