@ankhorage/doctor 0.11.7 → 0.11.8
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/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/analysis.d.ts.map +1 -1
- package/dist/analysis.js +62 -181
- package/dist/analysis.js.map +1 -1
- package/dist/cliLayoutAnalysis.d.ts.map +1 -1
- package/dist/cliLayoutAnalysis.js +26 -26
- package/dist/cliLayoutAnalysis.js.map +1 -1
- package/dist/sourceArchitectureAnalysis.d.ts.map +1 -1
- package/dist/sourceArchitectureAnalysis.js +35 -70
- package/dist/sourceArchitectureAnalysis.js.map +1 -1
- package/dist/utils/getArchitecturePolicyRule.d.ts +281 -0
- package/dist/utils/getArchitecturePolicyRule.d.ts.map +1 -0
- package/dist/utils/getArchitecturePolicyRule.js +12 -0
- package/dist/utils/getArchitecturePolicyRule.js.map +1 -0
- package/package.json +2 -2
- package/src/analysis.ts +83 -218
- package/src/cliLayoutAnalysis.ts +33 -26
- package/src/sourceArchitectureAnalysis.ts +59 -105
- package/src/utils/getArchitecturePolicyRule.ts +15 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cliLayoutAnalysis.js","sourceRoot":"","sources":["../src/cliLayoutAnalysis.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport {\n analyzeDoctorTarget,\n type DoctorAnalysisRequest,\n type DoctorAnalysisResult,\n} from './analysis.js';\nimport type { DoctorDiagnostic, DoctorPolicyProfile } from './diagnostics.js';\nimport { analyzeAppManifestTarget } from './manifestAnalysis.js';\nimport { analyzeSourceArchitecture } from './sourceArchitectureAnalysis.js';\n\nconst LEGACY_ROOT_CLI_SOURCE = path.join('src', 'cli.ts');\nconst ACTIVE_SOURCE_ROOTS = ['src', 'app', 'apps', 'packages', 'scripts'] as const;\nconst ACTIVE_SOURCE_EXTENSIONS = new Set([\n '.cjs',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.mts',\n '.ts',\n '.tsx',\n]);\nconst IGNORED_SOURCE_DIRECTORIES = new Set([\n '.expo',\n '.git',\n '.next',\n '__fixtures__',\n '__tests__',\n 'build',\n 'coverage',\n 'dist',\n 'docs',\n 'fixtures',\n 'node_modules',\n 'paradox',\n 'test',\n 'tests',\n]);\nconst COMPATIBILITY_PACKAGE_PREFIX = '@ankh/';\nconst STUDIO_PACKAGE_NAME = '@ankhorage/studio';\nconst STUDIO_DND_PACKAGE_NAME = '@ankhorage/react-native-reanimated-dnd-web';\nconst STUDIO_RUNTIME_PACKAGE_NAME = '@ankhorage/runtime';\nconst DEPENDENCY_FIELD_NAMES = [\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n 'peerDependencies',\n] as const;\n\ninterface ActiveSourceImport {\n readonly filePath: string;\n readonly specifier: string;\n}\n\ninterface DependencyEntry {\n readonly fieldName: (typeof DEPENDENCY_FIELD_NAMES)[number];\n readonly packageName: string;\n readonly version: string;\n}\n\nexport async function analyzeDoctorTargetWithCliLayout(\n request: DoctorAnalysisRequest,\n): Promise<DoctorAnalysisResult> {\n const manifestResult = await analyzeAppManifestTarget(request);\n if (manifestResult !== null) {\n return manifestResult;\n }\n\n const result = await analyzeDoctorTarget(request);\n if (!result.hasPackageJson) {\n return result;\n }\n\n const packageJsonPath = path.join(result.targetPath, 'package.json');\n const packageJson = await readPackageJson(packageJsonPath);\n if (packageJson === null) {\n return result;\n }\n\n const architectureDiagnostics = await analyzeTargetArchitecture({\n packageJson,\n packageJsonPath,\n profile: result.profile,\n targetPath: result.targetPath,\n });\n if (architectureDiagnostics.length === 0) {\n return result;\n }\n\n const diagnostics = [...result.diagnostics, ...architectureDiagnostics];\n\n return {\n ...result,\n diagnostics,\n fixPlan: result.fixPlan === null ? null : { ...result.fixPlan, diagnostics },\n };\n}\n\nasync function analyzeTargetArchitecture(request: {\n readonly packageJson: Record<string, unknown>;\n readonly packageJsonPath: string;\n readonly profile: DoctorPolicyProfile;\n readonly targetPath: string;\n}): Promise<DoctorDiagnostic[]> {\n const diagnostics: DoctorDiagnostic[] = [];\n const legacyRootCliPath = path.join(request.targetPath, LEGACY_ROOT_CLI_SOURCE);\n const exportsField = isRecord(request.packageJson.exports) ? request.packageJson.exports : null;\n const cliCapable = hasAnkhProvider(request.packageJson) || exportsField?.['./cli'] !== undefined;\n\n if (await pathExists(legacyRootCliPath)) {\n diagnostics.push({\n code: 'field-invalid',\n message: 'Package CLI code must live under src/cli/; root src/cli.ts is not allowed.',\n path: legacyRootCliPath,\n profile: request.profile,\n ruleId: 'package.cli.root-file.disallowed',\n severity: 'error',\n });\n }\n\n if (cliCapable && exportsField?.['./cli'] === undefined) {\n diagnostics.push({\n code: 'field-missing',\n message:\n 'CLI-capable packages must export \"./cli\" from package.json and point it at the metadata-declared provider build output.',\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'package.cli.export.required',\n severity: 'error',\n });\n }\n\n const dependencyEntries = collectDependencyEntries(request.packageJson);\n diagnostics.push(\n ...validateDependencyArchitecture({\n dependencyEntries,\n packageJsonPath: request.packageJsonPath,\n profile: request.profile,\n }),\n );\n\n const activeSourceImports = await collectActiveSourceImports(request.targetPath);\n diagnostics.push(\n ...validateActiveSourceImports({\n activeSourceImports,\n profile: request.profile,\n }),\n );\n diagnostics.push(\n ...(await analyzeSourceArchitecture({\n activeSourceImports,\n profile: request.profile,\n targetPath: request.targetPath,\n })),\n );\n\n if (request.packageJson.name === STUDIO_PACKAGE_NAME) {\n diagnostics.push(\n ...validateStudioOwnership({\n activeSourceImports,\n dependencyEntries,\n packageJsonPath: request.packageJsonPath,\n profile: request.profile,\n }),\n );\n }\n\n return diagnostics;\n}\n\nfunction validateDependencyArchitecture(request: {\n readonly dependencyEntries: readonly DependencyEntry[];\n readonly packageJsonPath: string;\n readonly profile: DoctorPolicyProfile;\n}): DoctorDiagnostic[] {\n const diagnostics: DoctorDiagnostic[] = [];\n\n for (const dependency of request.dependencyEntries) {\n if (dependency.packageName.startsWith(COMPATIBILITY_PACKAGE_PREFIX)) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Dependency \"${dependency.packageName}\" in package.json.${dependency.fieldName} is an old ankhorage4 workspace alias. Depend on \"${toOwningPackageSpecifier(dependency.packageName)}\" directly.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'package.dependencies.ankh-workspace-alias.disallowed',\n severity: 'error',\n });\n }\n\n if (\n request.profile === 'public-package' &&\n /^(?:file:|link:|workspace:|github:|git(?:\\+[^:]+)?:)/u.test(dependency.version)\n ) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Published dependency \"${dependency.packageName}\" must resolve from a registry version, not \"${dependency.version}\". Standalone packages cannot rely on local/workspace/Git package state.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'package.dependencies.local-protocol.disallowed',\n severity: 'error',\n });\n }\n\n if (\n dependency.packageName.includes('ankhorage4') ||\n dependency.version.includes('ankhorage4')\n ) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Dependency \"${dependency.packageName}\" references ankhorage4 as active source. Depend on the extracted owning @ankhorage/* package instead.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'package.dependencies.ankhorage4-source.disallowed',\n severity: 'error',\n });\n }\n }\n\n return diagnostics;\n}\n\nfunction validateActiveSourceImports(request: {\n readonly activeSourceImports: readonly ActiveSourceImport[];\n readonly profile: DoctorPolicyProfile;\n}): DoctorDiagnostic[] {\n const diagnostics: DoctorDiagnostic[] = [];\n\n for (const sourceImport of request.activeSourceImports) {\n if (sourceImport.specifier.startsWith(COMPATIBILITY_PACKAGE_PREFIX)) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Import \"${sourceImport.specifier}\" is an old ankhorage4 compatibility boundary. Import \"${toOwningPackageSpecifier(sourceImport.specifier)}\" directly from the owning package.`,\n path: sourceImport.filePath,\n profile: request.profile,\n ruleId: 'package.imports.ankh-workspace-alias.disallowed',\n severity: 'error',\n });\n }\n\n if (sourceImport.specifier.includes('ankhorage4')) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Import \"${sourceImport.specifier}\" references ankhorage4 as active source. Import the extracted owning @ankhorage/* package instead.`,\n path: sourceImport.filePath,\n profile: request.profile,\n ruleId: 'package.imports.ankhorage4-source.disallowed',\n severity: 'error',\n });\n }\n }\n\n return diagnostics;\n}\n\nfunction validateStudioOwnership(request: {\n readonly activeSourceImports: readonly ActiveSourceImport[];\n readonly dependencyEntries: readonly DependencyEntry[];\n readonly packageJsonPath: string;\n readonly profile: DoctorPolicyProfile;\n}): DoctorDiagnostic[] {\n const diagnostics: DoctorDiagnostic[] = [];\n const directDependencies = new Set(\n request.dependencyEntries\n .filter((dependency) => dependency.fieldName === 'dependencies')\n .map((dependency) => dependency.packageName),\n );\n const importedSpecifiers = request.activeSourceImports.map(\n (sourceImport) => sourceImport.specifier,\n );\n\n if (!directDependencies.has(STUDIO_DND_PACKAGE_NAME)) {\n diagnostics.push({\n code: 'missing-dependency',\n message: `@ankhorage/studio must depend directly on \"${STUDIO_DND_PACKAGE_NAME}\" for Studio drag-and-drop ownership.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'studio.dependencies.dnd.required',\n severity: 'error',\n });\n }\n\n if (!directDependencies.has(STUDIO_RUNTIME_PACKAGE_NAME)) {\n diagnostics.push({\n code: 'missing-dependency',\n message: `@ankhorage/studio must depend directly on \"${STUDIO_RUNTIME_PACKAGE_NAME}\" for runtime ownership.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'studio.dependencies.runtime.required',\n severity: 'error',\n });\n }\n\n if (\n !importedSpecifiers.some((specifier) => isPackageImport(specifier, STUDIO_DND_PACKAGE_NAME))\n ) {\n diagnostics.push({\n code: 'field-missing',\n message: `@ankhorage/studio must import \"${STUDIO_DND_PACKAGE_NAME}\" directly from its Studio DnD boundary.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'studio.imports.dnd.required',\n severity: 'error',\n });\n }\n\n if (\n !importedSpecifiers.some((specifier) => isPackageImport(specifier, STUDIO_RUNTIME_PACKAGE_NAME))\n ) {\n diagnostics.push({\n code: 'field-missing',\n message: `@ankhorage/studio must import \"${STUDIO_RUNTIME_PACKAGE_NAME}\" directly from the owning runtime package.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'studio.imports.runtime.required',\n severity: 'error',\n });\n }\n\n return diagnostics;\n}\n\nasync function collectActiveSourceImports(targetPath: string): Promise<ActiveSourceImport[]> {\n const imports: ActiveSourceImport[] = [];\n\n for (const sourceRoot of ACTIVE_SOURCE_ROOTS) {\n const sourceRootPath = path.join(targetPath, sourceRoot);\n if (!(await pathExists(sourceRootPath))) {\n continue;\n }\n\n for (const filePath of await listActiveSourceFiles(sourceRootPath)) {\n const source = await fs.readFile(filePath, 'utf8');\n for (const specifier of extractImportSpecifiers(source)) {\n imports.push({ filePath, specifier });\n }\n }\n }\n\n return imports;\n}\n\nasync function listActiveSourceFiles(rootPath: string): Promise<string[]> {\n const files: string[] = [];\n await visit(rootPath);\n return files.sort((left, right) => left.localeCompare(right));\n\n async function visit(currentPath: string): Promise<void> {\n const entries = await fs.readdir(currentPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!IGNORED_SOURCE_DIRECTORIES.has(entry.name)) {\n await visit(path.join(currentPath, entry.name));\n }\n continue;\n }\n\n if (!entry.isFile()) {\n continue;\n }\n\n const filePath = path.join(currentPath, entry.name);\n if (isActiveSourceFile(filePath)) {\n files.push(filePath);\n }\n }\n }\n}\n\nfunction isActiveSourceFile(filePath: string): boolean {\n if (!ACTIVE_SOURCE_EXTENSIONS.has(path.extname(filePath))) {\n return false;\n }\n\n return !/(?:^|\\.)(?:spec|test)\\.[cm]?[jt]sx?$/.test(path.basename(filePath));\n}\n\nfunction extractImportSpecifiers(source: string): string[] {\n const specifiers = new Set<string>();\n const patterns = [\n /\\b(?:import|export)\\s+(?:type\\s+)?(?:[^;]*?\\sfrom\\s*)?['\"]([^'\"]+)['\"]/g,\n /\\b(?:import|require)\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g,\n ];\n\n for (const pattern of patterns) {\n for (const match of source.matchAll(pattern)) {\n const [, specifier] = match;\n if (specifier !== undefined) {\n specifiers.add(specifier);\n }\n }\n }\n\n return [...specifiers].sort((left, right) => left.localeCompare(right));\n}\n\nfunction collectDependencyEntries(packageJson: Record<string, unknown>): DependencyEntry[] {\n const entries: DependencyEntry[] = [];\n\n for (const fieldName of DEPENDENCY_FIELD_NAMES) {\n const dependencies = isRecord(packageJson[fieldName]) ? packageJson[fieldName] : null;\n if (dependencies === null) {\n continue;\n }\n\n for (const [packageName, version] of Object.entries(dependencies)) {\n if (isNonEmptyString(version)) {\n entries.push({ fieldName, packageName, version });\n }\n }\n }\n\n return entries.sort((left, right) => {\n const fieldComparison = left.fieldName.localeCompare(right.fieldName);\n return fieldComparison !== 0\n ? fieldComparison\n : left.packageName.localeCompare(right.packageName);\n });\n}\n\nfunction hasAnkhProvider(packageJson: Record<string, unknown>): boolean {\n const ankh = isRecord(packageJson.ankh) ? packageJson.ankh : null;\n return isNonEmptyString(ankh?.provider);\n}\n\nfunction isPackageImport(specifier: string, packageName: string): boolean {\n return specifier === packageName || specifier.startsWith(`${packageName}/`);\n}\n\nfunction toOwningPackageSpecifier(specifier: string): string {\n return `@ankhorage/${specifier.slice(COMPATIBILITY_PACKAGE_PREFIX.length)}`;\n}\n\nasync function readPackageJson(packageJsonPath: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed: unknown = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nasync function pathExists(targetPath: string): Promise<boolean> {\n try {\n await fs.access(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cliLayoutAnalysis.js","sourceRoot":"","sources":["../src/cliLayoutAnalysis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE,OAAO,EACL,mBAAmB,GAGpB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,MAAM,UAAU,GAAG,mBAAmB,CAAC,GAAG,CAAC;AAC3C,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,YAAY,CAAC;AAC3D,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAU,CAAC;AACnF,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC;IACvC,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;CACP,CAAC,CAAC;AACH,MAAM,0BAA0B,GAAG,IAAI,GAAG,CAAC;IACzC,OAAO;IACP,MAAM;IACN,OAAO;IACP,cAAc;IACd,WAAW;IACX,OAAO;IACP,UAAU;IACV,MAAM;IACN,MAAM;IACN,UAAU;IACV,cAAc;IACd,SAAS;IACT,MAAM;IACN,OAAO;CACR,CAAC,CAAC;AACH,MAAM,mBAAmB,GAAG,mBAAmB,CAAC;AAChD,MAAM,uBAAuB,GAAG,4CAA4C,CAAC;AAC7E,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AACzD,MAAM,sBAAsB,GAAG;IAC7B,cAAc;IACd,iBAAiB;IACjB,sBAAsB;IACtB,kBAAkB;CACV,CAAC;AAaX,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,OAA8B;IAE9B,MAAM,cAAc,GAAG,MAAM,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAC/D,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAC5B,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IACrE,MAAM,WAAW,GAAG,MAAM,eAAe,CAAC,eAAe,CAAC,CAAC;IAC3D,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,uBAAuB,GAAG,MAAM,yBAAyB,CAAC;QAC9D,WAAW;QACX,eAAe;QACf,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC,CAAC;IACH,IAAI,uBAAuB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,uBAAuB,CAAC,CAAC;IAExE,OAAO;QACL,GAAG,MAAM;QACT,WAAW;QACX,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE;KAC7E,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,yBAAyB,CAAC,OAKxC;IACC,MAAM,WAAW,GAAuB,EAAE,CAAC;IAC3C,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC;IACnF,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAChG,MAAM,UAAU,GACd,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,YAAY,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,SAAS,CAAC;IAEjG,IAAI,MAAM,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACxC,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,4EAA4E;YACrF,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,GAAG,sBAAsB,CAAC,UAAU,CAAC,gBAAgB,CAAC;SACvD,CAAC,CAAC;IACL,CAAC;IAED,IAAI,UAAU,IAAI,YAAY,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,SAAS,EAAE,CAAC;QACzE,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,eAAe;YACrB,OAAO,EACL,yHAAyH;YAC3H,IAAI,EAAE,OAAO,CAAC,eAAe;YAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,GAAG,sBAAsB,CAAC,UAAU,CAAC,YAAY,CAAC;SACnD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,iBAAiB,GAAG,wBAAwB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACxE,WAAW,CAAC,IAAI,CACd,GAAG,8BAA8B,CAAC;QAChC,iBAAiB;QACjB,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CACH,CAAC;IAEF,MAAM,mBAAmB,GAAG,MAAM,0BAA0B,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACjF,WAAW,CAAC,IAAI,CACd,GAAG,2BAA2B,CAAC;QAC7B,mBAAmB;QACnB,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CACH,CAAC;IACF,WAAW,CAAC,IAAI,CACd,GAAG,CAAC,MAAM,yBAAyB,CAAC;QAClC,mBAAmB;QACnB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC,CAAC,CACJ,CAAC;IAEF,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;QACrD,WAAW,CAAC,IAAI,CACd,GAAG,uBAAuB,CAAC;YACzB,mBAAmB;YACnB,iBAAiB;YACjB,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,8BAA8B,CAAC,OAIvC;IACC,MAAM,WAAW,GAAuB,EAAE,CAAC;IAE3C,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;QACnD,IAAI,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,iBAAiB,CAAC,0BAA0B,CAAC,EAAE,CAAC;YACpF,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,eAAe,UAAU,CAAC,WAAW,qBAAqB,UAAU,CAAC,SAAS,qDAAqD,wBAAwB,CAAC,UAAU,CAAC,WAAW,CAAC,aAAa;gBACzM,IAAI,EAAE,OAAO,CAAC,eAAe;gBAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,KAAK,CAAC,uBAAuB,CAAC;aAC3E,CAAC,CAAC;QACL,CAAC;QAED,IACE,OAAO,CAAC,OAAO,KAAK,gBAAgB;YACpC,iBAAiB,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CACtD,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CACtC,EACD,CAAC;YACD,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,yBAAyB,UAAU,CAAC,WAAW,gDAAgD,UAAU,CAAC,OAAO,0EAA0E;gBACpM,IAAI,EAAE,OAAO,CAAC,eAAe;gBAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,KAAK,CAAC,uBAAuB,CAAC;aAC3E,CAAC,CAAC;QACL,CAAC;QAED,IACE,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,iBAAiB,CAAC,kBAAkB,CAAC;YACrE,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,EACjE,CAAC;YACD,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,eAAe,UAAU,CAAC,WAAW,wGAAwG;gBACtJ,IAAI,EAAE,OAAO,CAAC,eAAe;gBAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,KAAK,CAAC,sBAAsB,CAAC;aAC1E,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,2BAA2B,CAAC,OAGpC;IACC,MAAM,WAAW,GAAuB,EAAE,CAAC;IAE3C,KAAK,MAAM,YAAY,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;QACvD,IAAI,YAAY,CAAC,SAAS,CAAC,UAAU,CAAC,iBAAiB,CAAC,0BAA0B,CAAC,EAAE,CAAC;YACpF,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,WAAW,YAAY,CAAC,SAAS,0DAA0D,wBAAwB,CAAC,YAAY,CAAC,SAAS,CAAC,qCAAqC;gBACzL,IAAI,EAAE,YAAY,CAAC,QAAQ;gBAC3B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,KAAK,CAAC,mBAAmB,CAAC;aACvE,CAAC,CAAC;QACL,CAAC;QAED,IAAI,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC1E,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,WAAW,YAAY,CAAC,SAAS,qGAAqG;gBAC/I,IAAI,EAAE,YAAY,CAAC,QAAQ;gBAC3B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,KAAK,CAAC,kBAAkB,CAAC;aACtE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,uBAAuB,CAAC,OAKhC;IACC,MAAM,WAAW,GAAuB,EAAE,CAAC;IAC3C,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAChC,OAAO,CAAC,iBAAiB;SACtB,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,KAAK,cAAc,CAAC;SAC/D,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAC/C,CAAC;IACF,MAAM,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG,CACxD,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,CACzC,CAAC;IAEF,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,uBAAuB,CAAC,EAAE,CAAC;QACrD,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,8CAA8C,uBAAuB,uCAAuC;YACrH,IAAI,EAAE,OAAO,CAAC,eAAe;YAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,MAAM,EAAE,kCAAkC;YAC1C,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,2BAA2B,CAAC,EAAE,CAAC;QACzD,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,8CAA8C,2BAA2B,0BAA0B;YAC5G,IAAI,EAAE,OAAO,CAAC,eAAe;YAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,MAAM,EAAE,sCAAsC;YAC9C,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;IACL,CAAC;IAED,IACE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,eAAe,CAAC,SAAS,EAAE,uBAAuB,CAAC,CAAC,EAC5F,CAAC;QACD,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,kCAAkC,uBAAuB,0CAA0C;YAC5G,IAAI,EAAE,OAAO,CAAC,eAAe;YAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,MAAM,EAAE,6BAA6B;YACrC,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;IACL,CAAC;IAED,IACE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,eAAe,CAAC,SAAS,EAAE,2BAA2B,CAAC,CAAC,EAChG,CAAC;QACD,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,kCAAkC,2BAA2B,6CAA6C;YACnH,IAAI,EAAE,OAAO,CAAC,eAAe;YAC7B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,MAAM,EAAE,iCAAiC;YACzC,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,KAAK,UAAU,0BAA0B,CAAC,UAAkB;IAC1D,MAAM,OAAO,GAAyB,EAAE,CAAC;IAEzC,KAAK,MAAM,UAAU,IAAI,mBAAmB,EAAE,CAAC;QAC7C,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACzD,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC;YACxC,SAAS;QACX,CAAC;QAED,KAAK,MAAM,QAAQ,IAAI,MAAM,qBAAqB,CAAC,cAAc,CAAC,EAAE,CAAC;YACnE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACnD,KAAK,MAAM,SAAS,IAAI,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxD,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,QAAgB;IACnD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;IACtB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IAE9D,KAAK,UAAU,KAAK,CAAC,WAAmB;QACtC,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,CAAC,0BAA0B,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBAChD,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBAClD,CAAC;gBACD,SAAS;YACX,CAAC;YAED,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBACpB,SAAS;YACX,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACpD,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAgB;IAC1C,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CAAC,sCAAsC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAc;IAC7C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,MAAM,QAAQ,GAAG;QACf,yEAAyE;QACzE,oDAAoD;KACrD,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7C,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC;YAC5B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,wBAAwB,CAAC,WAAoC;IACpE,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,KAAK,MAAM,SAAS,IAAI,sBAAsB,EAAE,CAAC;QAC/C,MAAM,YAAY,GAAG,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACtF,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;YAC1B,SAAS;QACX,CAAC;QAED,KAAK,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YAClE,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAClC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACtE,OAAO,eAAe,KAAK,CAAC;YAC1B,CAAC,CAAC,eAAe;YACjB,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,WAAoC;IAC3D,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAClE,OAAO,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB,EAAE,WAAmB;IAC7D,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,gFAAgF;AAChF,SAAS,sBAAsB,CAC7B,MAAkC;IAElC,MAAM,IAAI,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAC/C,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;AACtD,CAAC;AAED,SAAS,wBAAwB,CAAC,SAAiB;IACjD,OAAO,cAAc,SAAS,CAAC,KAAK,CAAC,iBAAiB,CAAC,0BAA0B,CAAC,MAAM,CAAC,EAAE,CAAC;AAC9F,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,eAAuB;IACpD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/E,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,UAAkB;IAC1C,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { ARCHITECTURE_POLICY } from '@ankhorage/policy/architecture';\n\nimport {\n analyzeDoctorTarget,\n type DoctorAnalysisRequest,\n type DoctorAnalysisResult,\n} from './analysis.js';\nimport type { DoctorDiagnostic, DoctorPolicyProfile } from './diagnostics.js';\nimport { analyzeAppManifestTarget } from './manifestAnalysis.js';\nimport { analyzeSourceArchitecture } from './sourceArchitectureAnalysis.js';\nimport { getArchitecturePolicyRule } from './utils/getArchitecturePolicyRule.js';\n\nconst CLI_POLICY = ARCHITECTURE_POLICY.cli;\nconst DEPENDENCY_POLICY = ARCHITECTURE_POLICY.dependencies;\nconst ACTIVE_SOURCE_ROOTS = ['src', 'app', 'apps', 'packages', 'scripts'] as const;\nconst ACTIVE_SOURCE_EXTENSIONS = new Set([\n '.cjs',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.mts',\n '.ts',\n '.tsx',\n]);\nconst IGNORED_SOURCE_DIRECTORIES = new Set([\n '.expo',\n '.git',\n '.next',\n '__fixtures__',\n '__tests__',\n 'build',\n 'coverage',\n 'dist',\n 'docs',\n 'fixtures',\n 'node_modules',\n 'paradox',\n 'test',\n 'tests',\n]);\nconst STUDIO_PACKAGE_NAME = '@ankhorage/studio';\nconst STUDIO_DND_PACKAGE_NAME = '@ankhorage/react-native-reanimated-dnd-web';\nconst STUDIO_RUNTIME_PACKAGE_NAME = '@ankhorage/runtime';\nconst DEPENDENCY_FIELD_NAMES = [\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n 'peerDependencies',\n] as const;\n\ninterface ActiveSourceImport {\n readonly filePath: string;\n readonly specifier: string;\n}\n\ninterface DependencyEntry {\n readonly fieldName: (typeof DEPENDENCY_FIELD_NAMES)[number];\n readonly packageName: string;\n readonly version: string;\n}\n\nexport async function analyzeDoctorTargetWithCliLayout(\n request: DoctorAnalysisRequest,\n): Promise<DoctorAnalysisResult> {\n const manifestResult = await analyzeAppManifestTarget(request);\n if (manifestResult !== null) {\n return manifestResult;\n }\n\n const result = await analyzeDoctorTarget(request);\n if (!result.hasPackageJson) {\n return result;\n }\n\n const packageJsonPath = path.join(result.targetPath, 'package.json');\n const packageJson = await readPackageJson(packageJsonPath);\n if (packageJson === null) {\n return result;\n }\n\n const architectureDiagnostics = await analyzeTargetArchitecture({\n packageJson,\n packageJsonPath,\n profile: result.profile,\n targetPath: result.targetPath,\n });\n if (architectureDiagnostics.length === 0) {\n return result;\n }\n\n const diagnostics = [...result.diagnostics, ...architectureDiagnostics];\n\n return {\n ...result,\n diagnostics,\n fixPlan: result.fixPlan === null ? null : { ...result.fixPlan, diagnostics },\n };\n}\n\nasync function analyzeTargetArchitecture(request: {\n readonly packageJson: Record<string, unknown>;\n readonly packageJsonPath: string;\n readonly profile: DoctorPolicyProfile;\n readonly targetPath: string;\n}): Promise<DoctorDiagnostic[]> {\n const diagnostics: DoctorDiagnostic[] = [];\n const legacyRootCliPath = path.join(request.targetPath, CLI_POLICY.legacyRootFile);\n const exportsField = isRecord(request.packageJson.exports) ? request.packageJson.exports : null;\n const cliCapable =\n hasAnkhProvider(request.packageJson) || exportsField?.[CLI_POLICY.packageExport] !== undefined;\n\n if (await pathExists(legacyRootCliPath)) {\n diagnostics.push({\n code: 'field-invalid',\n message: 'Package CLI code must live under src/cli/; root src/cli.ts is not allowed.',\n path: legacyRootCliPath,\n profile: request.profile,\n ...architectureRuleFields(CLI_POLICY.legacyRootRuleId),\n });\n }\n\n if (cliCapable && exportsField?.[CLI_POLICY.packageExport] === undefined) {\n diagnostics.push({\n code: 'field-missing',\n message:\n 'CLI-capable packages must export \"./cli\" from package.json and point it at the metadata-declared provider build output.',\n path: request.packageJsonPath,\n profile: request.profile,\n ...architectureRuleFields(CLI_POLICY.exportRuleId),\n });\n }\n\n const dependencyEntries = collectDependencyEntries(request.packageJson);\n diagnostics.push(\n ...validateDependencyArchitecture({\n dependencyEntries,\n packageJsonPath: request.packageJsonPath,\n profile: request.profile,\n }),\n );\n\n const activeSourceImports = await collectActiveSourceImports(request.targetPath);\n diagnostics.push(\n ...validateActiveSourceImports({\n activeSourceImports,\n profile: request.profile,\n }),\n );\n diagnostics.push(\n ...(await analyzeSourceArchitecture({\n activeSourceImports,\n profile: request.profile,\n targetPath: request.targetPath,\n })),\n );\n\n if (request.packageJson.name === STUDIO_PACKAGE_NAME) {\n diagnostics.push(\n ...validateStudioOwnership({\n activeSourceImports,\n dependencyEntries,\n packageJsonPath: request.packageJsonPath,\n profile: request.profile,\n }),\n );\n }\n\n return diagnostics;\n}\n\nfunction validateDependencyArchitecture(request: {\n readonly dependencyEntries: readonly DependencyEntry[];\n readonly packageJsonPath: string;\n readonly profile: DoctorPolicyProfile;\n}): DoctorDiagnostic[] {\n const diagnostics: DoctorDiagnostic[] = [];\n\n for (const dependency of request.dependencyEntries) {\n if (dependency.packageName.startsWith(DEPENDENCY_POLICY.compatibilityPackagePrefix)) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Dependency \"${dependency.packageName}\" in package.json.${dependency.fieldName} is an old ankhorage4 workspace alias. Depend on \"${toOwningPackageSpecifier(dependency.packageName)}\" directly.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ...architectureRuleFields(DEPENDENCY_POLICY.rules.compatibilityDependency),\n });\n }\n\n if (\n request.profile === 'public-package' &&\n DEPENDENCY_POLICY.localProtocolPrefixes.some((prefix) =>\n dependency.version.startsWith(prefix),\n )\n ) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Published dependency \"${dependency.packageName}\" must resolve from a registry version, not \"${dependency.version}\". Standalone packages cannot rely on local/workspace/Git package state.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ...architectureRuleFields(DEPENDENCY_POLICY.rules.localProtocolDependency),\n });\n }\n\n if (\n dependency.packageName.includes(DEPENDENCY_POLICY.legacySourceMarker) ||\n dependency.version.includes(DEPENDENCY_POLICY.legacySourceMarker)\n ) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Dependency \"${dependency.packageName}\" references ankhorage4 as active source. Depend on the extracted owning @ankhorage/* package instead.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ...architectureRuleFields(DEPENDENCY_POLICY.rules.legacySourceDependency),\n });\n }\n }\n\n return diagnostics;\n}\n\nfunction validateActiveSourceImports(request: {\n readonly activeSourceImports: readonly ActiveSourceImport[];\n readonly profile: DoctorPolicyProfile;\n}): DoctorDiagnostic[] {\n const diagnostics: DoctorDiagnostic[] = [];\n\n for (const sourceImport of request.activeSourceImports) {\n if (sourceImport.specifier.startsWith(DEPENDENCY_POLICY.compatibilityPackagePrefix)) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Import \"${sourceImport.specifier}\" is an old ankhorage4 compatibility boundary. Import \"${toOwningPackageSpecifier(sourceImport.specifier)}\" directly from the owning package.`,\n path: sourceImport.filePath,\n profile: request.profile,\n ...architectureRuleFields(DEPENDENCY_POLICY.rules.compatibilityImport),\n });\n }\n\n if (sourceImport.specifier.includes(DEPENDENCY_POLICY.legacySourceMarker)) {\n diagnostics.push({\n code: 'field-invalid',\n message: `Import \"${sourceImport.specifier}\" references ankhorage4 as active source. Import the extracted owning @ankhorage/* package instead.`,\n path: sourceImport.filePath,\n profile: request.profile,\n ...architectureRuleFields(DEPENDENCY_POLICY.rules.legacySourceImport),\n });\n }\n }\n\n return diagnostics;\n}\n\nfunction validateStudioOwnership(request: {\n readonly activeSourceImports: readonly ActiveSourceImport[];\n readonly dependencyEntries: readonly DependencyEntry[];\n readonly packageJsonPath: string;\n readonly profile: DoctorPolicyProfile;\n}): DoctorDiagnostic[] {\n const diagnostics: DoctorDiagnostic[] = [];\n const directDependencies = new Set(\n request.dependencyEntries\n .filter((dependency) => dependency.fieldName === 'dependencies')\n .map((dependency) => dependency.packageName),\n );\n const importedSpecifiers = request.activeSourceImports.map(\n (sourceImport) => sourceImport.specifier,\n );\n\n if (!directDependencies.has(STUDIO_DND_PACKAGE_NAME)) {\n diagnostics.push({\n code: 'missing-dependency',\n message: `@ankhorage/studio must depend directly on \"${STUDIO_DND_PACKAGE_NAME}\" for Studio drag-and-drop ownership.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'studio.dependencies.dnd.required',\n severity: 'error',\n });\n }\n\n if (!directDependencies.has(STUDIO_RUNTIME_PACKAGE_NAME)) {\n diagnostics.push({\n code: 'missing-dependency',\n message: `@ankhorage/studio must depend directly on \"${STUDIO_RUNTIME_PACKAGE_NAME}\" for runtime ownership.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'studio.dependencies.runtime.required',\n severity: 'error',\n });\n }\n\n if (\n !importedSpecifiers.some((specifier) => isPackageImport(specifier, STUDIO_DND_PACKAGE_NAME))\n ) {\n diagnostics.push({\n code: 'field-missing',\n message: `@ankhorage/studio must import \"${STUDIO_DND_PACKAGE_NAME}\" directly from its Studio DnD boundary.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'studio.imports.dnd.required',\n severity: 'error',\n });\n }\n\n if (\n !importedSpecifiers.some((specifier) => isPackageImport(specifier, STUDIO_RUNTIME_PACKAGE_NAME))\n ) {\n diagnostics.push({\n code: 'field-missing',\n message: `@ankhorage/studio must import \"${STUDIO_RUNTIME_PACKAGE_NAME}\" directly from the owning runtime package.`,\n path: request.packageJsonPath,\n profile: request.profile,\n ruleId: 'studio.imports.runtime.required',\n severity: 'error',\n });\n }\n\n return diagnostics;\n}\n\nasync function collectActiveSourceImports(targetPath: string): Promise<ActiveSourceImport[]> {\n const imports: ActiveSourceImport[] = [];\n\n for (const sourceRoot of ACTIVE_SOURCE_ROOTS) {\n const sourceRootPath = path.join(targetPath, sourceRoot);\n if (!(await pathExists(sourceRootPath))) {\n continue;\n }\n\n for (const filePath of await listActiveSourceFiles(sourceRootPath)) {\n const source = await fs.readFile(filePath, 'utf8');\n for (const specifier of extractImportSpecifiers(source)) {\n imports.push({ filePath, specifier });\n }\n }\n }\n\n return imports;\n}\n\nasync function listActiveSourceFiles(rootPath: string): Promise<string[]> {\n const files: string[] = [];\n await visit(rootPath);\n return files.sort((left, right) => left.localeCompare(right));\n\n async function visit(currentPath: string): Promise<void> {\n const entries = await fs.readdir(currentPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!IGNORED_SOURCE_DIRECTORIES.has(entry.name)) {\n await visit(path.join(currentPath, entry.name));\n }\n continue;\n }\n\n if (!entry.isFile()) {\n continue;\n }\n\n const filePath = path.join(currentPath, entry.name);\n if (isActiveSourceFile(filePath)) {\n files.push(filePath);\n }\n }\n }\n}\n\nfunction isActiveSourceFile(filePath: string): boolean {\n if (!ACTIVE_SOURCE_EXTENSIONS.has(path.extname(filePath))) {\n return false;\n }\n\n return !/(?:^|\\.)(?:spec|test)\\.[cm]?[jt]sx?$/.test(path.basename(filePath));\n}\n\nfunction extractImportSpecifiers(source: string): string[] {\n const specifiers = new Set<string>();\n const patterns = [\n /\\b(?:import|export)\\s+(?:type\\s+)?(?:[^;]*?\\sfrom\\s*)?['\"]([^'\"]+)['\"]/g,\n /\\b(?:import|require)\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g,\n ];\n\n for (const pattern of patterns) {\n for (const match of source.matchAll(pattern)) {\n const [, specifier] = match;\n if (specifier !== undefined) {\n specifiers.add(specifier);\n }\n }\n }\n\n return [...specifiers].sort((left, right) => left.localeCompare(right));\n}\n\nfunction collectDependencyEntries(packageJson: Record<string, unknown>): DependencyEntry[] {\n const entries: DependencyEntry[] = [];\n\n for (const fieldName of DEPENDENCY_FIELD_NAMES) {\n const dependencies = isRecord(packageJson[fieldName]) ? packageJson[fieldName] : null;\n if (dependencies === null) {\n continue;\n }\n\n for (const [packageName, version] of Object.entries(dependencies)) {\n if (isNonEmptyString(version)) {\n entries.push({ fieldName, packageName, version });\n }\n }\n }\n\n return entries.sort((left, right) => {\n const fieldComparison = left.fieldName.localeCompare(right.fieldName);\n return fieldComparison !== 0\n ? fieldComparison\n : left.packageName.localeCompare(right.packageName);\n });\n}\n\nfunction hasAnkhProvider(packageJson: Record<string, unknown>): boolean {\n const ankh = isRecord(packageJson.ankh) ? packageJson.ankh : null;\n return isNonEmptyString(ankh?.provider);\n}\n\nfunction isPackageImport(specifier: string, packageName: string): boolean {\n return specifier === packageName || specifier.startsWith(`${packageName}/`);\n}\n\n/*** Resolve Policy-owned rule metadata for generic architecture diagnostics. */\nfunction architectureRuleFields(\n ruleId: DoctorDiagnostic['ruleId'],\n): Pick<DoctorDiagnostic, 'ruleId' | 'severity'> {\n const rule = getArchitecturePolicyRule(ruleId);\n return { ruleId: rule.id, severity: rule.severity };\n}\n\nfunction toOwningPackageSpecifier(specifier: string): string {\n return `@ankhorage/${specifier.slice(DEPENDENCY_POLICY.compatibilityPackagePrefix.length)}`;\n}\n\nasync function readPackageJson(packageJsonPath: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed: unknown = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nasync function pathExists(targetPath: string): Promise<boolean> {\n try {\n await fs.access(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sourceArchitectureAnalysis.d.ts","sourceRoot":"","sources":["../src/sourceArchitectureAnalysis.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sourceArchitectureAnalysis.d.ts","sourceRoot":"","sources":["../src/sourceArchitectureAnalysis.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAG9E,UAAU,YAAY;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,UAAU,8BAA8B;IACtC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,YAAY,EAAE,CAAC;IACtD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAID,iFAAiF;AACjF,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE7B"}
|
|
@@ -1,30 +1,8 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const PORT_ROLE_SEGMENTS = new Set(['ports']);
|
|
7
|
-
const DOMAIN_OUTWARD_SEGMENTS = new Set([
|
|
8
|
-
'adapters',
|
|
9
|
-
'app',
|
|
10
|
-
'application',
|
|
11
|
-
'cli',
|
|
12
|
-
'composition',
|
|
13
|
-
'host',
|
|
14
|
-
'infrastructure',
|
|
15
|
-
'platform',
|
|
16
|
-
]);
|
|
17
|
-
const APPLICATION_OUTWARD_SEGMENTS = new Set([
|
|
18
|
-
'adapters',
|
|
19
|
-
'app',
|
|
20
|
-
'cli',
|
|
21
|
-
'composition',
|
|
22
|
-
'host',
|
|
23
|
-
'infrastructure',
|
|
24
|
-
'platform',
|
|
25
|
-
]);
|
|
26
|
-
const PORT_OUTWARD_SEGMENTS = APPLICATION_OUTWARD_SEGMENTS;
|
|
27
|
-
const INWARD_FEATURE_ROLES = ['application', 'contracts', 'domain', 'planning', 'ports'];
|
|
3
|
+
import { ARCHITECTURE_POLICY } from '@ankhorage/policy/architecture';
|
|
4
|
+
import { getArchitecturePolicyRule } from './utils/getArchitecturePolicyRule.js';
|
|
5
|
+
const SOURCE_POLICY = ARCHITECTURE_POLICY.source;
|
|
28
6
|
/*** Validate folder-role combinations and inward source dependency direction. */
|
|
29
7
|
export async function analyzeSourceArchitecture(input) {
|
|
30
8
|
return [...(await analyzeDirectoryVocabularyAsync(input)), ...analyzeImportDirection(input)];
|
|
@@ -33,10 +11,10 @@ export async function analyzeSourceArchitecture(input) {
|
|
|
33
11
|
async function analyzeDirectoryVocabularyAsync(input) {
|
|
34
12
|
const diagnostics = [];
|
|
35
13
|
const sourceRoot = path.join(input.targetPath, 'src');
|
|
36
|
-
for (const directoryName of
|
|
14
|
+
for (const directoryName of SOURCE_POLICY.catchAllDirectories) {
|
|
37
15
|
const directoryPath = path.join(sourceRoot, directoryName);
|
|
38
16
|
if (await pathExistsAsync(directoryPath)) {
|
|
39
|
-
diagnostics.push(createDiagnostic(input, directoryPath,
|
|
17
|
+
diagnostics.push(createDiagnostic(input, directoryPath, ARCHITECTURE_POLICY.rules.catchAllDirectory, `src/${directoryName}/ is a generic catch-all. Move code to an owning domain, feature, package edge, or utils/ according to the repository profile.`));
|
|
40
18
|
}
|
|
41
19
|
}
|
|
42
20
|
diagnostics.push(...(await analyzeFeatureCombinationsAsync(input, sourceRoot)));
|
|
@@ -56,13 +34,11 @@ async function analyzeFeatureCombinationsAsync(input, sourceRoot) {
|
|
|
56
34
|
const roleNames = new Set((await fs.readdir(featureRoot, { withFileTypes: true }))
|
|
57
35
|
.filter((child) => child.isDirectory())
|
|
58
36
|
.map((child) => child.name));
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
!['adapters', 'application', 'ports', 'planning'].some((role) => roleNames.has(role))) {
|
|
65
|
-
diagnostics.push(createDiagnostic(input, path.join(featureRoot, 'composition'), 'package.architecture.role-combination.invalid', `Feature "${entry.name}" declares composition/ without application, ports, planning, or adapters to wire.`));
|
|
37
|
+
for (const [roleName, requirement] of Object.entries(SOURCE_POLICY.featureCombinations)) {
|
|
38
|
+
if (roleNames.has(roleName) &&
|
|
39
|
+
!requirement.requiresAnyOf.some((requiredRole) => roleNames.has(requiredRole))) {
|
|
40
|
+
diagnostics.push(createDiagnostic(input, path.join(featureRoot, roleName), getArchitecturePolicyRule(requirement.ruleId), `Feature "${entry.name}" declares ${roleName}/ without one of the required inward roles: ${requirement.requiresAnyOf.join(', ')}.`));
|
|
41
|
+
}
|
|
66
42
|
}
|
|
67
43
|
}
|
|
68
44
|
return diagnostics;
|
|
@@ -86,74 +62,63 @@ function createEscapedRepositoryDiagnostic(input, sourceImport, targetPath) {
|
|
|
86
62
|
const relativeTarget = path.relative(input.targetPath, targetPath);
|
|
87
63
|
if (!relativeTarget.startsWith('..') && !path.isAbsolute(relativeTarget))
|
|
88
64
|
return null;
|
|
89
|
-
return createDiagnostic(input, sourceImport.filePath,
|
|
65
|
+
return createDiagnostic(input, sourceImport.filePath, getArchitecturePolicyRule(SOURCE_POLICY.repositoryBoundaryRuleId), `Relative import "${sourceImport.specifier}" escapes the standalone repository root.`);
|
|
90
66
|
}
|
|
91
67
|
/*** Apply inward dependency rules for recognized source architecture roles. */
|
|
92
68
|
function analyzeRecognizedRoleDirection(input, sourceImport, targetPath) {
|
|
93
69
|
const sourceSegments = splitRelativePath(input.targetPath, sourceImport.filePath);
|
|
94
70
|
const targetSegments = splitRelativePath(input.targetPath, targetPath);
|
|
95
|
-
|
|
71
|
+
const deliveryPolicy = SOURCE_POLICY.thinDeliveryAdapter;
|
|
72
|
+
if (includesSegmentSequence(sourceSegments, deliveryPolicy.pathSegments) &&
|
|
73
|
+
targetSegments.includes(deliveryPolicy.concreteAdapterSegment)) {
|
|
96
74
|
return [
|
|
97
|
-
createDiagnostic(input, sourceImport.filePath,
|
|
75
|
+
createDiagnostic(input, sourceImport.filePath, getArchitecturePolicyRule(deliveryPolicy.ruleId), `Thin delivery adapter must not wire concrete adapter implementation through "${sourceImport.specifier}". Import an application operation or composition boundary instead.`),
|
|
98
76
|
];
|
|
99
77
|
}
|
|
100
78
|
const role = resolveSourceRole(sourceSegments);
|
|
101
79
|
if (role === null)
|
|
102
80
|
return [];
|
|
103
|
-
return createOutwardImportDiagnostic(input, sourceImport, targetSegments, role
|
|
104
|
-
}
|
|
105
|
-
/*** Check whether a source file belongs to a thin CLI command delivery boundary. */
|
|
106
|
-
function isThinDeliveryAdapter(sourceSegments) {
|
|
107
|
-
const cliIndex = sourceSegments.indexOf('cli');
|
|
108
|
-
return cliIndex >= 0 && sourceSegments[cliIndex + 1] === 'commands';
|
|
81
|
+
return createOutwardImportDiagnostic(input, sourceImport, targetSegments, role);
|
|
109
82
|
}
|
|
110
83
|
/*** Resolve the dependency rule owned by one recognized inner source role. */
|
|
111
84
|
function resolveSourceRole(sourceSegments) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
forbiddenSegments: APPLICATION_OUTWARD_SEGMENTS,
|
|
122
|
-
label: 'Application/use-case code',
|
|
123
|
-
ruleId: 'package.architecture.application-outward-import.disallowed',
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
if (sourceSegments.some((segment) => PORT_ROLE_SEGMENTS.has(segment))) {
|
|
127
|
-
return {
|
|
128
|
-
forbiddenSegments: PORT_OUTWARD_SEGMENTS,
|
|
129
|
-
label: 'Port contracts',
|
|
130
|
-
ruleId: 'package.architecture.port-outward-import.disallowed',
|
|
131
|
-
};
|
|
85
|
+
for (const role of Object.values(SOURCE_POLICY.roles)) {
|
|
86
|
+
if (sourceSegments.some((segment) => role.segments.some((roleSegment) => roleSegment === segment))) {
|
|
87
|
+
return {
|
|
88
|
+
forbiddenSegments: new Set(role.forbiddenOutwardSegments),
|
|
89
|
+
label: role.label,
|
|
90
|
+
rule: getArchitecturePolicyRule(role.ruleId),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
132
93
|
}
|
|
133
94
|
return null;
|
|
134
95
|
}
|
|
135
96
|
/*** Report one outward dependency when the imported path crosses a forbidden role. */
|
|
136
|
-
function createOutwardImportDiagnostic(input, sourceImport, targetSegments,
|
|
137
|
-
const outwardRole = targetSegments.find((segment) => forbiddenSegments.has(segment));
|
|
97
|
+
function createOutwardImportDiagnostic(input, sourceImport, targetSegments, role) {
|
|
98
|
+
const outwardRole = targetSegments.find((segment) => role.forbiddenSegments.has(segment));
|
|
138
99
|
if (outwardRole === undefined)
|
|
139
100
|
return [];
|
|
140
101
|
return [
|
|
141
|
-
createDiagnostic(input, sourceImport.filePath,
|
|
102
|
+
createDiagnostic(input, sourceImport.filePath, role.rule, `${role.label} must not import outward ${outwardRole}/ implementation through "${sourceImport.specifier}".`),
|
|
142
103
|
];
|
|
143
104
|
}
|
|
105
|
+
/*** Check whether one path segment sequence occurs contiguously in another. */
|
|
106
|
+
function includesSegmentSequence(segments, expected) {
|
|
107
|
+
return segments.some((_, index) => expected.every((segment, offset) => segments[index + offset] === segment));
|
|
108
|
+
}
|
|
144
109
|
/*** Split one repository path into normalized architecture segments. */
|
|
145
110
|
function splitRelativePath(targetPath, filePath) {
|
|
146
111
|
return path.relative(targetPath, filePath).split(path.sep).filter(Boolean);
|
|
147
112
|
}
|
|
148
|
-
/*** Build one deterministic architecture diagnostic. */
|
|
149
|
-
function createDiagnostic(input, diagnosticPath,
|
|
113
|
+
/*** Build one deterministic architecture diagnostic from Policy-owned metadata. */
|
|
114
|
+
function createDiagnostic(input, diagnosticPath, rule, message) {
|
|
150
115
|
return {
|
|
151
116
|
code: 'field-invalid',
|
|
152
117
|
message,
|
|
153
118
|
path: diagnosticPath,
|
|
154
119
|
profile: input.profile,
|
|
155
|
-
ruleId,
|
|
156
|
-
severity:
|
|
120
|
+
ruleId: rule.id,
|
|
121
|
+
severity: rule.severity,
|
|
157
122
|
};
|
|
158
123
|
}
|
|
159
124
|
/*** Check whether a filesystem path exists. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sourceArchitectureAnalysis.js","sourceRoot":"","sources":["../src/sourceArchitectureAnalysis.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport type { DoctorDiagnostic, DoctorPolicyProfile } from './diagnostics.js';\n\ninterface SourceImport {\n readonly filePath: string;\n readonly specifier: string;\n}\n\ninterface AnalyzeSourceArchitectureInput {\n readonly activeSourceImports: readonly SourceImport[];\n readonly profile: DoctorPolicyProfile;\n readonly targetPath: string;\n}\n\nconst CATCH_ALL_DIRECTORIES = ['common', 'helpers', 'shared'] as const;\nconst INNER_ROLE_SEGMENTS = new Set(['domain', 'core']);\nconst APPLICATION_ROLE_SEGMENTS = new Set(['application']);\nconst PORT_ROLE_SEGMENTS = new Set(['ports']);\nconst DOMAIN_OUTWARD_SEGMENTS = new Set([\n 'adapters',\n 'app',\n 'application',\n 'cli',\n 'composition',\n 'host',\n 'infrastructure',\n 'platform',\n]);\nconst APPLICATION_OUTWARD_SEGMENTS = new Set([\n 'adapters',\n 'app',\n 'cli',\n 'composition',\n 'host',\n 'infrastructure',\n 'platform',\n]);\nconst PORT_OUTWARD_SEGMENTS = APPLICATION_OUTWARD_SEGMENTS;\nconst INWARD_FEATURE_ROLES = ['application', 'contracts', 'domain', 'planning', 'ports'] as const;\n\n/*** Validate folder-role combinations and inward source dependency direction. */\nexport async function analyzeSourceArchitecture(\n input: AnalyzeSourceArchitectureInput,\n): Promise<DoctorDiagnostic[]> {\n return [...(await analyzeDirectoryVocabularyAsync(input)), ...analyzeImportDirection(input)];\n}\n\n/*** Validate architectural directory names only when a repository has introduced them. */\nasync function analyzeDirectoryVocabularyAsync(\n input: AnalyzeSourceArchitectureInput,\n): Promise<DoctorDiagnostic[]> {\n const diagnostics: DoctorDiagnostic[] = [];\n const sourceRoot = path.join(input.targetPath, 'src');\n\n for (const directoryName of CATCH_ALL_DIRECTORIES) {\n const directoryPath = path.join(sourceRoot, directoryName);\n if (await pathExistsAsync(directoryPath)) {\n diagnostics.push(\n createDiagnostic(\n input,\n directoryPath,\n 'package.architecture.catch-all-directory.disallowed',\n `src/${directoryName}/ is a generic catch-all. Move code to an owning domain, feature, package edge, or utils/ according to the repository profile.`,\n ),\n );\n }\n }\n\n diagnostics.push(...(await analyzeFeatureCombinationsAsync(input, sourceRoot)));\n return diagnostics;\n}\n\n/*** Validate feature-first role combinations without requiring unused ceremonial layers. */\nasync function analyzeFeatureCombinationsAsync(\n input: AnalyzeSourceArchitectureInput,\n sourceRoot: string,\n): Promise<DoctorDiagnostic[]> {\n const featuresRoot = path.join(sourceRoot, 'features');\n if (!(await pathExistsAsync(featuresRoot))) return [];\n\n const featureEntries = (await fs.readdir(featuresRoot, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .sort((left, right) => left.name.localeCompare(right.name));\n const diagnostics: DoctorDiagnostic[] = [];\n\n for (const entry of featureEntries) {\n const featureRoot = path.join(featuresRoot, entry.name);\n const roleNames = new Set(\n (await fs.readdir(featureRoot, { withFileTypes: true }))\n .filter((child) => child.isDirectory())\n .map((child) => child.name),\n );\n const hasInwardRole = INWARD_FEATURE_ROLES.some((role) => roleNames.has(role));\n\n if (roleNames.has('adapters') && !hasInwardRole) {\n diagnostics.push(\n createDiagnostic(\n input,\n path.join(featureRoot, 'adapters'),\n 'package.architecture.role-combination.invalid',\n `Feature \"${entry.name}\" declares adapters/ without domain, application, ports, contracts, or planning policy to adapt to.`,\n ),\n );\n }\n\n if (\n roleNames.has('composition') &&\n !['adapters', 'application', 'ports', 'planning'].some((role) => roleNames.has(role))\n ) {\n diagnostics.push(\n createDiagnostic(\n input,\n path.join(featureRoot, 'composition'),\n 'package.architecture.role-combination.invalid',\n `Feature \"${entry.name}\" declares composition/ without application, ports, planning, or adapters to wire.`,\n ),\n );\n }\n }\n\n return diagnostics;\n}\n\n/*** Validate that relative source imports point inward across recognized architecture roles. */\nfunction analyzeImportDirection(input: AnalyzeSourceArchitectureInput): DoctorDiagnostic[] {\n return input.activeSourceImports.flatMap((sourceImport) =>\n analyzeOneImportDirection(input, sourceImport),\n );\n}\n\n/*** Validate one relative import against repository and layer boundaries. */\nfunction analyzeOneImportDirection(\n input: AnalyzeSourceArchitectureInput,\n sourceImport: SourceImport,\n): DoctorDiagnostic[] {\n if (!sourceImport.specifier.startsWith('.')) return [];\n\n const targetPath = path.resolve(path.dirname(sourceImport.filePath), sourceImport.specifier);\n const escaped = createEscapedRepositoryDiagnostic(input, sourceImport, targetPath);\n if (escaped !== null) return [escaped];\n\n return analyzeRecognizedRoleDirection(input, sourceImport, targetPath);\n}\n\n/*** Report a relative import that escapes the standalone repository boundary. */\nfunction createEscapedRepositoryDiagnostic(\n input: AnalyzeSourceArchitectureInput,\n sourceImport: SourceImport,\n targetPath: string,\n): DoctorDiagnostic | null {\n const relativeTarget = path.relative(input.targetPath, targetPath);\n if (!relativeTarget.startsWith('..') && !path.isAbsolute(relativeTarget)) return null;\n\n return createDiagnostic(\n input,\n sourceImport.filePath,\n 'package.imports.outside-root.disallowed',\n `Relative import \"${sourceImport.specifier}\" escapes the standalone repository root.`,\n );\n}\n\n/*** Apply inward dependency rules for recognized source architecture roles. */\nfunction analyzeRecognizedRoleDirection(\n input: AnalyzeSourceArchitectureInput,\n sourceImport: SourceImport,\n targetPath: string,\n): DoctorDiagnostic[] {\n const sourceSegments = splitRelativePath(input.targetPath, sourceImport.filePath);\n const targetSegments = splitRelativePath(input.targetPath, targetPath);\n if (isThinDeliveryAdapter(sourceSegments) && targetSegments.includes('adapters')) {\n return [\n createDiagnostic(\n input,\n sourceImport.filePath,\n 'package.architecture.delivery-concrete-adapter-import.disallowed',\n `Thin delivery adapter must not wire concrete adapter implementation through \"${sourceImport.specifier}\". Import an application operation or composition boundary instead.`,\n ),\n ];\n }\n\n const role = resolveSourceRole(sourceSegments);\n if (role === null) return [];\n\n return createOutwardImportDiagnostic(\n input,\n sourceImport,\n targetSegments,\n role.forbiddenSegments,\n role.ruleId,\n role.label,\n );\n}\n\n/*** Check whether a source file belongs to a thin CLI command delivery boundary. */\nfunction isThinDeliveryAdapter(sourceSegments: readonly string[]): boolean {\n const cliIndex = sourceSegments.indexOf('cli');\n return cliIndex >= 0 && sourceSegments[cliIndex + 1] === 'commands';\n}\n\n/*** Resolve the dependency rule owned by one recognized inner source role. */\nfunction resolveSourceRole(sourceSegments: readonly string[]): SourceRoleRule | null {\n if (sourceSegments.some((segment) => INNER_ROLE_SEGMENTS.has(segment))) {\n return {\n forbiddenSegments: DOMAIN_OUTWARD_SEGMENTS,\n label: 'Domain/core policy',\n ruleId: 'package.architecture.domain-outward-import.disallowed',\n };\n }\n if (sourceSegments.some((segment) => APPLICATION_ROLE_SEGMENTS.has(segment))) {\n return {\n forbiddenSegments: APPLICATION_OUTWARD_SEGMENTS,\n label: 'Application/use-case code',\n ruleId: 'package.architecture.application-outward-import.disallowed',\n };\n }\n if (sourceSegments.some((segment) => PORT_ROLE_SEGMENTS.has(segment))) {\n return {\n forbiddenSegments: PORT_OUTWARD_SEGMENTS,\n label: 'Port contracts',\n ruleId: 'package.architecture.port-outward-import.disallowed',\n };\n }\n return null;\n}\n\ninterface SourceRoleRule {\n readonly forbiddenSegments: ReadonlySet<string>;\n readonly label: string;\n readonly ruleId:\n | 'package.architecture.application-outward-import.disallowed'\n | 'package.architecture.domain-outward-import.disallowed'\n | 'package.architecture.port-outward-import.disallowed';\n}\n\n/*** Report one outward dependency when the imported path crosses a forbidden role. */\nfunction createOutwardImportDiagnostic(\n input: AnalyzeSourceArchitectureInput,\n sourceImport: SourceImport,\n targetSegments: readonly string[],\n forbiddenSegments: ReadonlySet<string>,\n ruleId:\n | 'package.architecture.application-outward-import.disallowed'\n | 'package.architecture.domain-outward-import.disallowed'\n | 'package.architecture.port-outward-import.disallowed',\n roleLabel: string,\n): DoctorDiagnostic[] {\n const outwardRole = targetSegments.find((segment) => forbiddenSegments.has(segment));\n if (outwardRole === undefined) return [];\n\n return [\n createDiagnostic(\n input,\n sourceImport.filePath,\n ruleId,\n `${roleLabel} must not import outward ${outwardRole}/ implementation through \"${sourceImport.specifier}\".`,\n ),\n ];\n}\n\n/*** Split one repository path into normalized architecture segments. */\nfunction splitRelativePath(targetPath: string, filePath: string): readonly string[] {\n return path.relative(targetPath, filePath).split(path.sep).filter(Boolean);\n}\n\n/*** Build one deterministic architecture diagnostic. */\nfunction createDiagnostic(\n input: AnalyzeSourceArchitectureInput,\n diagnosticPath: string,\n ruleId: DoctorDiagnostic['ruleId'],\n message: string,\n): DoctorDiagnostic {\n return {\n code: 'field-invalid',\n message,\n path: diagnosticPath,\n profile: input.profile,\n ruleId,\n severity: 'error',\n };\n}\n\n/*** Check whether a filesystem path exists. */\nasync function pathExistsAsync(targetPath: string): Promise<boolean> {\n try {\n await fs.access(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sourceArchitectureAnalysis.js","sourceRoot":"","sources":["../src/sourceArchitectureAnalysis.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { ARCHITECTURE_POLICY } from '@ankhorage/policy/architecture';\n\nimport type { DoctorDiagnostic, DoctorPolicyProfile } from './diagnostics.js';\nimport { getArchitecturePolicyRule } from './utils/getArchitecturePolicyRule.js';\n\ninterface SourceImport {\n readonly filePath: string;\n readonly specifier: string;\n}\n\ninterface AnalyzeSourceArchitectureInput {\n readonly activeSourceImports: readonly SourceImport[];\n readonly profile: DoctorPolicyProfile;\n readonly targetPath: string;\n}\n\nconst SOURCE_POLICY = ARCHITECTURE_POLICY.source;\n\n/*** Validate folder-role combinations and inward source dependency direction. */\nexport async function analyzeSourceArchitecture(\n input: AnalyzeSourceArchitectureInput,\n): Promise<DoctorDiagnostic[]> {\n return [...(await analyzeDirectoryVocabularyAsync(input)), ...analyzeImportDirection(input)];\n}\n\n/*** Validate architectural directory names only when a repository has introduced them. */\nasync function analyzeDirectoryVocabularyAsync(\n input: AnalyzeSourceArchitectureInput,\n): Promise<DoctorDiagnostic[]> {\n const diagnostics: DoctorDiagnostic[] = [];\n const sourceRoot = path.join(input.targetPath, 'src');\n\n for (const directoryName of SOURCE_POLICY.catchAllDirectories) {\n const directoryPath = path.join(sourceRoot, directoryName);\n if (await pathExistsAsync(directoryPath)) {\n diagnostics.push(\n createDiagnostic(\n input,\n directoryPath,\n ARCHITECTURE_POLICY.rules.catchAllDirectory,\n `src/${directoryName}/ is a generic catch-all. Move code to an owning domain, feature, package edge, or utils/ according to the repository profile.`,\n ),\n );\n }\n }\n\n diagnostics.push(...(await analyzeFeatureCombinationsAsync(input, sourceRoot)));\n return diagnostics;\n}\n\n/*** Validate feature-first role combinations without requiring unused ceremonial layers. */\nasync function analyzeFeatureCombinationsAsync(\n input: AnalyzeSourceArchitectureInput,\n sourceRoot: string,\n): Promise<DoctorDiagnostic[]> {\n const featuresRoot = path.join(sourceRoot, 'features');\n if (!(await pathExistsAsync(featuresRoot))) return [];\n\n const featureEntries = (await fs.readdir(featuresRoot, { withFileTypes: true }))\n .filter((entry) => entry.isDirectory())\n .sort((left, right) => left.name.localeCompare(right.name));\n const diagnostics: DoctorDiagnostic[] = [];\n\n for (const entry of featureEntries) {\n const featureRoot = path.join(featuresRoot, entry.name);\n const roleNames = new Set(\n (await fs.readdir(featureRoot, { withFileTypes: true }))\n .filter((child) => child.isDirectory())\n .map((child) => child.name),\n );\n\n for (const [roleName, requirement] of Object.entries(SOURCE_POLICY.featureCombinations)) {\n if (\n roleNames.has(roleName) &&\n !requirement.requiresAnyOf.some((requiredRole) => roleNames.has(requiredRole))\n ) {\n diagnostics.push(\n createDiagnostic(\n input,\n path.join(featureRoot, roleName),\n getArchitecturePolicyRule(requirement.ruleId),\n `Feature \"${entry.name}\" declares ${roleName}/ without one of the required inward roles: ${requirement.requiresAnyOf.join(', ')}.`,\n ),\n );\n }\n }\n }\n\n return diagnostics;\n}\n\n/*** Validate that relative source imports point inward across recognized architecture roles. */\nfunction analyzeImportDirection(input: AnalyzeSourceArchitectureInput): DoctorDiagnostic[] {\n return input.activeSourceImports.flatMap((sourceImport) =>\n analyzeOneImportDirection(input, sourceImport),\n );\n}\n\n/*** Validate one relative import against repository and layer boundaries. */\nfunction analyzeOneImportDirection(\n input: AnalyzeSourceArchitectureInput,\n sourceImport: SourceImport,\n): DoctorDiagnostic[] {\n if (!sourceImport.specifier.startsWith('.')) return [];\n\n const targetPath = path.resolve(path.dirname(sourceImport.filePath), sourceImport.specifier);\n const escaped = createEscapedRepositoryDiagnostic(input, sourceImport, targetPath);\n if (escaped !== null) return [escaped];\n\n return analyzeRecognizedRoleDirection(input, sourceImport, targetPath);\n}\n\n/*** Report a relative import that escapes the standalone repository boundary. */\nfunction createEscapedRepositoryDiagnostic(\n input: AnalyzeSourceArchitectureInput,\n sourceImport: SourceImport,\n targetPath: string,\n): DoctorDiagnostic | null {\n const relativeTarget = path.relative(input.targetPath, targetPath);\n if (!relativeTarget.startsWith('..') && !path.isAbsolute(relativeTarget)) return null;\n\n return createDiagnostic(\n input,\n sourceImport.filePath,\n getArchitecturePolicyRule(SOURCE_POLICY.repositoryBoundaryRuleId),\n `Relative import \"${sourceImport.specifier}\" escapes the standalone repository root.`,\n );\n}\n\n/*** Apply inward dependency rules for recognized source architecture roles. */\nfunction analyzeRecognizedRoleDirection(\n input: AnalyzeSourceArchitectureInput,\n sourceImport: SourceImport,\n targetPath: string,\n): DoctorDiagnostic[] {\n const sourceSegments = splitRelativePath(input.targetPath, sourceImport.filePath);\n const targetSegments = splitRelativePath(input.targetPath, targetPath);\n const deliveryPolicy = SOURCE_POLICY.thinDeliveryAdapter;\n\n if (\n includesSegmentSequence(sourceSegments, deliveryPolicy.pathSegments) &&\n targetSegments.includes(deliveryPolicy.concreteAdapterSegment)\n ) {\n return [\n createDiagnostic(\n input,\n sourceImport.filePath,\n getArchitecturePolicyRule(deliveryPolicy.ruleId),\n `Thin delivery adapter must not wire concrete adapter implementation through \"${sourceImport.specifier}\". Import an application operation or composition boundary instead.`,\n ),\n ];\n }\n\n const role = resolveSourceRole(sourceSegments);\n if (role === null) return [];\n\n return createOutwardImportDiagnostic(input, sourceImport, targetSegments, role);\n}\n\n/*** Resolve the dependency rule owned by one recognized inner source role. */\nfunction resolveSourceRole(sourceSegments: readonly string[]): SourceRoleRule | null {\n for (const role of Object.values(SOURCE_POLICY.roles)) {\n if (\n sourceSegments.some((segment) => role.segments.some((roleSegment) => roleSegment === segment))\n ) {\n return {\n forbiddenSegments: new Set<string>(role.forbiddenOutwardSegments),\n label: role.label,\n rule: getArchitecturePolicyRule(role.ruleId),\n };\n }\n }\n\n return null;\n}\n\ninterface SourceRoleRule {\n readonly forbiddenSegments: ReadonlySet<string>;\n readonly label: string;\n readonly rule: ReturnType<typeof getArchitecturePolicyRule>;\n}\n\n/*** Report one outward dependency when the imported path crosses a forbidden role. */\nfunction createOutwardImportDiagnostic(\n input: AnalyzeSourceArchitectureInput,\n sourceImport: SourceImport,\n targetSegments: readonly string[],\n role: SourceRoleRule,\n): DoctorDiagnostic[] {\n const outwardRole = targetSegments.find((segment) => role.forbiddenSegments.has(segment));\n if (outwardRole === undefined) return [];\n\n return [\n createDiagnostic(\n input,\n sourceImport.filePath,\n role.rule,\n `${role.label} must not import outward ${outwardRole}/ implementation through \"${sourceImport.specifier}\".`,\n ),\n ];\n}\n\n/*** Check whether one path segment sequence occurs contiguously in another. */\nfunction includesSegmentSequence(\n segments: readonly string[],\n expected: readonly string[],\n): boolean {\n return segments.some((_, index) =>\n expected.every((segment, offset) => segments[index + offset] === segment),\n );\n}\n\n/*** Split one repository path into normalized architecture segments. */\nfunction splitRelativePath(targetPath: string, filePath: string): readonly string[] {\n return path.relative(targetPath, filePath).split(path.sep).filter(Boolean);\n}\n\n/*** Build one deterministic architecture diagnostic from Policy-owned metadata. */\nfunction createDiagnostic(\n input: AnalyzeSourceArchitectureInput,\n diagnosticPath: string,\n rule: ReturnType<typeof getArchitecturePolicyRule>,\n message: string,\n): DoctorDiagnostic {\n return {\n code: 'field-invalid',\n message,\n path: diagnosticPath,\n profile: input.profile,\n ruleId: rule.id,\n severity: rule.severity,\n };\n}\n\n/*** Check whether a filesystem path exists. */\nasync function pathExistsAsync(targetPath: string): Promise<boolean> {\n try {\n await fs.access(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n"]}
|