@webpieces/nx-webpieces-rules 0.3.166 → 0.3.167

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.3.166",
3
+ "version": "0.3.167",
4
4
  "description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -18,10 +18,10 @@
18
18
  "README.md"
19
19
  ],
20
20
  "dependencies": {
21
- "@webpieces/ai-hook-rules": "0.3.166",
22
- "@webpieces/code-rules": "0.3.166",
23
- "@webpieces/eslint-rules": "0.3.166",
24
- "@webpieces/rules-config": "0.3.166",
21
+ "@webpieces/ai-hook-rules": "0.3.167",
22
+ "@webpieces/code-rules": "0.3.167",
23
+ "@webpieces/eslint-rules": "0.3.167",
24
+ "@webpieces/rules-config": "0.3.167",
25
25
  "madge": "8.0.0"
26
26
  },
27
27
  "peerDependencies": {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ExecutorResult = void 0;
4
4
  class ExecutorResult {
5
+ success;
5
6
  constructor(success) {
6
7
  this.success = success;
7
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"executor-result.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/executor-result.ts"],"names":[],"mappings":";;;AAAA,MAAa,cAAc;IAGvB,YAAY,OAAgB;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AAND,wCAMC","sourcesContent":["export class ExecutorResult {\n success: boolean;\n\n constructor(success: boolean) {\n this.success = success;\n }\n}\n"]}
1
+ {"version":3,"file":"executor-result.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/executor-result.ts"],"names":[],"mappings":";;;AAAA,MAAa,cAAc;IACvB,OAAO,CAAU;IAEjB,YAAY,OAAgB;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AAND,wCAMC","sourcesContent":["export class ExecutorResult {\n success: boolean;\n\n constructor(success: boolean) {\n this.success = success;\n }\n}\n"]}
@@ -71,8 +71,10 @@ function escapeRegex(s) {
71
71
  return s.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
72
72
  }
73
73
  class TsconfigCompilerOptions {
74
+ paths;
74
75
  }
75
76
  class TsconfigBase {
77
+ compilerOptions;
76
78
  }
77
79
  /** Read tsconfig.base.json compilerOptions.paths from the workspace root, or null on failure. */
78
80
  function readTsconfigPaths(workspaceRoot) {
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-no-file-import-cycles/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No File Import Cycles Executor\n *\n * Per-project circular-dependency gate. Runs `madge` over the project's\n * TypeScript sources and fails when an import cycle is found.\n *\n * Unlike the old `nx:run-commands` target (which shelled out to a runtime\n * `npx madge` fetch — see NEEDED_CHANGES.md #1), this executor:\n * - invokes the madge it bundles as a dependency (deterministic, no network),\n * - is driven by webpieces.config.json like every other webpieces rule, so it\n * supports an on/off `mode` and a time-boxed `ignoreModifiedUntilEpoch`.\n *\n * Config (webpieces.config.json, rule key `no-file-import-cycles`):\n * \"no-file-import-cycles\": {\n * \"mode\": \"ON\", // \"OFF\" disables the gate everywhere\n * \"ignoreModifiedUntilEpoch\": 1771931925, // epoch SECONDS; while now < epoch,\n * // cycles are reported but the gate PASSES\n * // (warn, don't fail). After it, fails again.\n * \"ignoreTypeOnly\": true, // ignore `import type` re-export cycles\n * // (erased at compile time, harmless at runtime)\n * \"excludePackages\": [\"@kami/entities\"] // npm package names whose source trees madge\n * // should NOT traverse (stops foreign cycles\n * // from leaking into this project's report)\n * }\n *\n * Mirrors the dated-disable model already used for the method/file-size rules:\n * the epoch is a grace window so a strict gate can be turned on against an\n * existing codebase without an open-ended \"off everywhere\" escape hatch.\n *\n * Usage: nx run <project>:validate-no-file-import-cycles\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadConfig } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../../toError';\n\nexport type ValidateNoFileImportCyclesMode = 'ON' | 'OFF';\n\nexport interface ValidateNoFileImportCyclesOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'no-file-import-cycles';\n\n// madge ships no type declarations; describe the slice of its API we use.\n// webpieces-disable no-any-unknown -- minimal hand-typed surface for an untyped dependency\ninterface MadgeOptions {\n fileExtensions: string[];\n excludeRegExp?: string[];\n detectiveOptions?: Record<string, unknown>;\n}\ninterface MadgeInstance {\n circular(): string[][];\n}\ntype MadgeFn = (target: string, options: MadgeOptions) => Promise<MadgeInstance>;\n\n// madge's CJS export is the callable itself; some bundlers wrap it under `.default`.\ninterface MadgeModuleExtras {\n default?: MadgeFn;\n}\ntype MadgeModule = MadgeFn & MadgeModuleExtras;\n\nfunction loadMadge(): MadgeFn {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const mod: MadgeModule = require('madge');\n return mod.default ?? mod;\n}\n\n/**\n * Decide whether the gate should still FAIL on cycles (true) or only warn\n * (false), considering the ignoreModifiedUntilEpoch grace window. Logs a\n * one-line explanation when the grace window is active.\n */\nfunction isFailingActive(epoch: number | undefined): boolean {\n if (epoch === undefined) return true;\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expires = new Date(epoch * 1000).toISOString().split('T')[0];\n console.log(\n `\\n⏳ no-file-import-cycles: ignoreModifiedUntilEpoch active (expires ${expires}).` +\n '\\n Cycles will be reported but NOT fail the build until then.\\n',\n );\n return false;\n }\n return true;\n}\n\n// Never scan build output or declaration files. A project that compiles into a\n// local `dist/` (or build/out/coverage) would otherwise report cycles among the\n// emitted `*.d.ts` files instead of — or in addition to — the real source\n// cycles, so the gate would flag compiled-output noise and could diverge from a\n// plain `madge src` run. Excluding these makes the gate scan source only.\nconst EXCLUDE_BUILD_DIRS = '(^|/)(node_modules|dist|build|out|coverage|\\\\.nx|\\\\.next)(/|$)';\nconst EXCLUDE_DECLARATION_FILES = '\\\\.d\\\\.ts$';\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[/\\-\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n}\n\nclass TsconfigCompilerOptions {\n paths?: Record<string, string[]>;\n}\nclass TsconfigBase {\n compilerOptions?: TsconfigCompilerOptions;\n}\n\n/** Read tsconfig.base.json compilerOptions.paths from the workspace root, or null on failure. */\nfunction readTsconfigPaths(workspaceRoot: string): Record<string, string[]> | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort read; null on any failure\n try {\n const tsconfigPath = path.join(workspaceRoot, 'tsconfig.base.json');\n const content = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfig = JSON.parse(content) as TsconfigBase;\n return tsconfig?.compilerOptions?.paths ?? null;\n // webpieces-disable catch-error-pattern -- file missing or malformed JSON; caller handles null\n } catch (err: unknown) {\n //const error = toError(err);\n return null;\n }\n}\n\n/**\n * Walk up from startPath to find the nearest ancestor directory that contains\n * a package.json. Returns that directory path, or null if none found.\n */\nfunction findPackageRoot(startPath: string): string | null {\n let dir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);\n const fsRoot = path.parse(dir).root;\n while (dir !== fsRoot) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(pkgName: string, workspaceRoot: string): string | null {\n // First try require.resolve (works for installed / symlinked packages).\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- resolution failure is expected; fall through to tsconfig path lookup\n try {\n const pkgJson = require.resolve(`${pkgName}/package.json`, { paths: [workspaceRoot] });\n return fs.realpathSync(path.dirname(pkgJson));\n // webpieces-disable catch-error-pattern -- expected for non-installed packages; fall through to tsconfig path lookup\n } catch (err: unknown) {\n //const error = toError(err);\n // Fall through to tsconfig path resolution for pnpm workspace packages.\n }\n\n // Fallback: resolve via tsconfig.base.json compilerOptions.paths.\n // pnpm workspace packages are not in node_modules, so require.resolve fails above;\n // tsconfig.base.json maps e.g. \"@mealco-internal/kami\" → [\"libraries/kami/index.ts\"].\n const tsconfigPaths = readTsconfigPaths(workspaceRoot);\n const entries = tsconfigPaths?.[pkgName];\n if (!entries || entries.length === 0) {\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` — not found in node_modules or tsconfig.base.json paths. Skipping.`,\n );\n return null;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort fallback; warn on any failure\n try {\n const resolved = path.resolve(workspaceRoot, entries[0]);\n const pkgRoot = findPackageRoot(resolved);\n if (!pkgRoot) {\n console.warn(\n `⚠️ no-file-import-cycles: resolved \"${pkgName}\" → \"${resolved}\"` +\n ` but found no package.json in parent directories — skipping.`,\n );\n return null;\n }\n return pkgRoot;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` via tsconfig paths (${error.message}) — skipping.`,\n );\n return null;\n }\n}\n\nfunction buildMadgeOptions(ignoreTypeOnly: boolean, excludePackages: string[], workspaceRoot: string): MadgeOptions {\n const excludeRegExp = [EXCLUDE_BUILD_DIRS, EXCLUDE_DECLARATION_FILES];\n for (const pkg of excludePackages) {\n const dir = resolvePackageDir(pkg, workspaceRoot);\n if (dir) excludeRegExp.push(`^${escapeRegex(dir)}(/|$)`);\n }\n const options: MadgeOptions = {\n fileExtensions: ['ts', 'tsx'],\n excludeRegExp,\n };\n if (ignoreTypeOnly) {\n // dependency-tree's TS detective drops `import type {...}` edges with this flag.\n options.detectiveOptions = {\n ts: { skipTypeImports: true },\n tsx: { skipTypeImports: true },\n };\n }\n return options;\n}\n\nfunction reportCycles(projectName: string, cycles: string[][]): void {\n console.error(`\\n❌ Found ${cycles.length} circular import cycle(s) in ${projectName}:\\n`);\n cycles.forEach((cycle: string[], i: number) => {\n console.error(` ${i + 1}. ${cycle.join(' → ')} → ${cycle[0]}`);\n });\n console.error('\\nTo fix, break the cycle (extract a shared module, or use an interface).');\n console.error('To time-box a known cycle, a human can set \"ignoreModifiedUntilEpoch\"');\n console.error(`(epoch seconds) on the \"${RULE_NAME}\" rule in webpieces.config.json.`);\n console.error(`To turn the gate off entirely, set \"${RULE_NAME}\".mode to \"OFF\".\\n`);\n}\n\nexport default async function runExecutor(\n _options: ValidateNoFileImportCyclesOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadConfig(context.root);\n const rule = shared.rules.get(RULE_NAME);\n\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig ? path.join(context.root, projectConfig.root) : context.root;\n\n const epoch = rule?.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const ignoreTypeOnly = (rule?.options['ignoreTypeOnly'] as boolean | undefined) ?? false;\n const excludePackages = (rule?.options['excludePackages'] as string[] | undefined) ?? [];\n\n console.log(`\\n🔁 Checking import cycles in ${projectName} (madge)\\n`);\n\n const madge = loadMadge();\n const result = await madge(projectRoot, buildMadgeOptions(ignoreTypeOnly, excludePackages, context.root));\n const cycles = result.circular();\n\n if (cycles.length === 0) {\n console.log('✅ No circular import cycles found\\n');\n return { success: true };\n }\n\n reportCycles(projectName, cycles);\n\n // Grace window active → report but pass; otherwise fail.\n return { success: !isFailingActive(epoch) };\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-no-file-import-cycles/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate No File Import Cycles Executor\n *\n * Per-project circular-dependency gate. Runs `madge` over the project's\n * TypeScript sources and fails when an import cycle is found.\n *\n * Unlike the old `nx:run-commands` target (which shelled out to a runtime\n * `npx madge` fetch — see NEEDED_CHANGES.md #1), this executor:\n * - invokes the madge it bundles as a dependency (deterministic, no network),\n * - is driven by webpieces.config.json like every other webpieces rule, so it\n * supports an on/off `mode` and a time-boxed `ignoreModifiedUntilEpoch`.\n *\n * Config (webpieces.config.json, rule key `no-file-import-cycles`):\n * \"no-file-import-cycles\": {\n * \"mode\": \"ON\", // \"OFF\" disables the gate everywhere\n * \"ignoreModifiedUntilEpoch\": 1771931925, // epoch SECONDS; while now < epoch,\n * // cycles are reported but the gate PASSES\n * // (warn, don't fail). After it, fails again.\n * \"ignoreTypeOnly\": true, // ignore `import type` re-export cycles\n * // (erased at compile time, harmless at runtime)\n * \"excludePackages\": [\"@kami/entities\"] // npm package names whose source trees madge\n * // should NOT traverse (stops foreign cycles\n * // from leaking into this project's report)\n * }\n *\n * Mirrors the dated-disable model already used for the method/file-size rules:\n * the epoch is a grace window so a strict gate can be turned on against an\n * existing codebase without an open-ended \"off everywhere\" escape hatch.\n *\n * Usage: nx run <project>:validate-no-file-import-cycles\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadConfig } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../../toError';\n\nexport type ValidateNoFileImportCyclesMode = 'ON' | 'OFF';\n\nexport interface ValidateNoFileImportCyclesOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'no-file-import-cycles';\n\n// madge ships no type declarations; describe the slice of its API we use.\n// webpieces-disable no-any-unknown -- minimal hand-typed surface for an untyped dependency\ninterface MadgeOptions {\n fileExtensions: string[];\n excludeRegExp?: string[];\n detectiveOptions?: Record<string, unknown>;\n}\ninterface MadgeInstance {\n circular(): string[][];\n}\ntype MadgeFn = (target: string, options: MadgeOptions) => Promise<MadgeInstance>;\n\n// madge's CJS export is the callable itself; some bundlers wrap it under `.default`.\ninterface MadgeModuleExtras {\n default?: MadgeFn;\n}\ntype MadgeModule = MadgeFn & MadgeModuleExtras;\n\nfunction loadMadge(): MadgeFn {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const mod: MadgeModule = require('madge');\n return mod.default ?? mod;\n}\n\n/**\n * Decide whether the gate should still FAIL on cycles (true) or only warn\n * (false), considering the ignoreModifiedUntilEpoch grace window. Logs a\n * one-line explanation when the grace window is active.\n */\nfunction isFailingActive(epoch: number | undefined): boolean {\n if (epoch === undefined) return true;\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expires = new Date(epoch * 1000).toISOString().split('T')[0];\n console.log(\n `\\n⏳ no-file-import-cycles: ignoreModifiedUntilEpoch active (expires ${expires}).` +\n '\\n Cycles will be reported but NOT fail the build until then.\\n',\n );\n return false;\n }\n return true;\n}\n\n// Never scan build output or declaration files. A project that compiles into a\n// local `dist/` (or build/out/coverage) would otherwise report cycles among the\n// emitted `*.d.ts` files instead of — or in addition to — the real source\n// cycles, so the gate would flag compiled-output noise and could diverge from a\n// plain `madge src` run. Excluding these makes the gate scan source only.\nconst EXCLUDE_BUILD_DIRS = '(^|/)(node_modules|dist|build|out|coverage|\\\\.nx|\\\\.next)(/|$)';\nconst EXCLUDE_DECLARATION_FILES = '\\\\.d\\\\.ts$';\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[/\\-\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n}\n\nclass TsconfigCompilerOptions {\n paths?: Record<string, string[]>;\n}\nclass TsconfigBase {\n compilerOptions?: TsconfigCompilerOptions;\n}\n\n/** Read tsconfig.base.json compilerOptions.paths from the workspace root, or null on failure. */\nfunction readTsconfigPaths(workspaceRoot: string): Record<string, string[]> | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort read; null on any failure\n try {\n const tsconfigPath = path.join(workspaceRoot, 'tsconfig.base.json');\n const content = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfig = JSON.parse(content) as TsconfigBase;\n return tsconfig?.compilerOptions?.paths ?? null;\n // webpieces-disable catch-error-pattern -- file missing or malformed JSON; caller handles null\n } catch (err: unknown) {\n //const error = toError(err);\n return null;\n }\n}\n\n/**\n * Walk up from startPath to find the nearest ancestor directory that contains\n * a package.json. Returns that directory path, or null if none found.\n */\nfunction findPackageRoot(startPath: string): string | null {\n let dir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);\n const fsRoot = path.parse(dir).root;\n while (dir !== fsRoot) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(pkgName: string, workspaceRoot: string): string | null {\n // First try require.resolve (works for installed / symlinked packages).\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- resolution failure is expected; fall through to tsconfig path lookup\n try {\n const pkgJson = require.resolve(`${pkgName}/package.json`, { paths: [workspaceRoot] });\n return fs.realpathSync(path.dirname(pkgJson));\n // webpieces-disable catch-error-pattern -- expected for non-installed packages; fall through to tsconfig path lookup\n } catch (err: unknown) {\n //const error = toError(err);\n // Fall through to tsconfig path resolution for pnpm workspace packages.\n }\n\n // Fallback: resolve via tsconfig.base.json compilerOptions.paths.\n // pnpm workspace packages are not in node_modules, so require.resolve fails above;\n // tsconfig.base.json maps e.g. \"@mealco-internal/kami\" → [\"libraries/kami/index.ts\"].\n const tsconfigPaths = readTsconfigPaths(workspaceRoot);\n const entries = tsconfigPaths?.[pkgName];\n if (!entries || entries.length === 0) {\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` — not found in node_modules or tsconfig.base.json paths. Skipping.`,\n );\n return null;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort fallback; warn on any failure\n try {\n const resolved = path.resolve(workspaceRoot, entries[0]);\n const pkgRoot = findPackageRoot(resolved);\n if (!pkgRoot) {\n console.warn(\n `⚠️ no-file-import-cycles: resolved \"${pkgName}\" → \"${resolved}\"` +\n ` but found no package.json in parent directories — skipping.`,\n );\n return null;\n }\n return pkgRoot;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(\n `⚠️ no-file-import-cycles: could not resolve excludePackages entry \"${pkgName}\"` +\n ` via tsconfig paths (${error.message}) — skipping.`,\n );\n return null;\n }\n}\n\nfunction buildMadgeOptions(ignoreTypeOnly: boolean, excludePackages: string[], workspaceRoot: string): MadgeOptions {\n const excludeRegExp = [EXCLUDE_BUILD_DIRS, EXCLUDE_DECLARATION_FILES];\n for (const pkg of excludePackages) {\n const dir = resolvePackageDir(pkg, workspaceRoot);\n if (dir) excludeRegExp.push(`^${escapeRegex(dir)}(/|$)`);\n }\n const options: MadgeOptions = {\n fileExtensions: ['ts', 'tsx'],\n excludeRegExp,\n };\n if (ignoreTypeOnly) {\n // dependency-tree's TS detective drops `import type {...}` edges with this flag.\n options.detectiveOptions = {\n ts: { skipTypeImports: true },\n tsx: { skipTypeImports: true },\n };\n }\n return options;\n}\n\nfunction reportCycles(projectName: string, cycles: string[][]): void {\n console.error(`\\n❌ Found ${cycles.length} circular import cycle(s) in ${projectName}:\\n`);\n cycles.forEach((cycle: string[], i: number) => {\n console.error(` ${i + 1}. ${cycle.join(' → ')} → ${cycle[0]}`);\n });\n console.error('\\nTo fix, break the cycle (extract a shared module, or use an interface).');\n console.error('To time-box a known cycle, a human can set \"ignoreModifiedUntilEpoch\"');\n console.error(`(epoch seconds) on the \"${RULE_NAME}\" rule in webpieces.config.json.`);\n console.error(`To turn the gate off entirely, set \"${RULE_NAME}\".mode to \"OFF\".\\n`);\n}\n\nexport default async function runExecutor(\n _options: ValidateNoFileImportCyclesOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadConfig(context.root);\n const rule = shared.rules.get(RULE_NAME);\n\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig ? path.join(context.root, projectConfig.root) : context.root;\n\n const epoch = rule?.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const ignoreTypeOnly = (rule?.options['ignoreTypeOnly'] as boolean | undefined) ?? false;\n const excludePackages = (rule?.options['excludePackages'] as string[] | undefined) ?? [];\n\n console.log(`\\n🔁 Checking import cycles in ${projectName} (madge)\\n`);\n\n const madge = loadMadge();\n const result = await madge(projectRoot, buildMadgeOptions(ignoreTypeOnly, excludePackages, context.root));\n const cycles = result.circular();\n\n if (cycles.length === 0) {\n console.log('✅ No circular import cycles found\\n');\n return { success: true };\n }\n\n reportCycles(projectName, cycles);\n\n // Grace window active → report but pass; otherwise fail.\n return { success: !isFailingActive(epoch) };\n}\n"]}
@@ -42,6 +42,9 @@ const DEFAULT_COMPILE_EXECUTORS = [
42
42
  '@angular/build:application',
43
43
  ];
44
44
  class WiringProblem {
45
+ executorName;
46
+ missing;
47
+ found;
45
48
  constructor(executorName, missing, found) {
46
49
  this.executorName = executorName;
47
50
  this.missing = missing;
@@ -49,6 +52,10 @@ class WiringProblem {
49
52
  }
50
53
  }
51
54
  class ProjectGateProblem {
55
+ project;
56
+ executorName;
57
+ missing;
58
+ found;
52
59
  constructor(project, executorName, missing, found) {
53
60
  this.project = project;
54
61
  this.executorName = executorName;
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-nx-wiring/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;;AA8LH,8BA4CC;;AAnOD,uCAAgG;AAChG,0DAAqD;AACrD,+CAAyB;AACzB,mDAA6B;AAW7B,MAAM,qBAAqB,GAAa;IACpC,gCAAgC;IAChC,gCAAgC;CACnC,CAAC;AAEF,4FAA4F;AAC5F,MAAM,eAAe,GAAG,QAAQ,CAAC;AAEjC,MAAM,yBAAyB,GAAa;IACxC,YAAY;IACZ,4BAA4B;CAC/B,CAAC;AAEF,MAAM,aAAa;IAKf,YAAY,YAAoB,EAAE,OAAiB,EAAE,KAAe;QAChE,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;CACJ;AAED,MAAM,kBAAkB;IAMpB,YAAY,OAAe,EAAE,YAAoB,EAAE,OAAiB,EAAE,KAAe;QACjF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;CACJ;AAUD,SAAS,kBAAkB,CAAC,aAAqB;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;QAC5C,OAAO,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;QACnC,8GAA8G;IAClH,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6DAA6D;QAC7D,KAAK,GAAG,CAAC;QACT,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED,SAAS,yBAAyB,CAC9B,cAAsC,EACtC,gBAA0B;IAE1B,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;QAClC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1C,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC;YACrC,IAAI,YAAY,IAAI,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC1C,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,YAAY,CACjB,iBAA2B,EAC3B,cAAkD,EAClD,YAAsB;IAEtB,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,YAAY,IAAI,iBAAiB,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;QAC3C,MAAM,SAAS,GAAG,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,YAAY,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QACvE,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAyC;IAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACpB,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC;QACxC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;QAChC,OAAO,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACpD,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;GAGG;AACH,SAAS,uBAAuB,CAC5B,cAAsC,EACtC,gBAA6B,EAC7B,kBAA4B;IAE5B,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChE,uFAAuF;QACvF,wFAAwF;QACxF,8EAA8E;QAC9E,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;YAAE,SAAS;QACxD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;YAAE,SAAS;QACjF,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QACpF,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,SAAS,aAAa,CAAC,QAAyB,EAAE,YAAsB;IACpE,OAAO,CAAC,KAAK,CAAC,qEAAqE,CAAC,CAAC;IACrF,OAAO,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAC;IACvF,OAAO,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;IAChF,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1E,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChF,OAAO,CAAC,KAAK,CAAC,MAAM,OAAO,CAAC,YAAY,MAAM,CAAC,CAAC;QAChD,OAAO,CAAC,KAAK,CAAC,uBAAuB,QAAQ,aAAa,CAAC,CAAC;QAC5D,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,gBAAgB,WAAW,IAAI,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,qEAAqE,CAAC,CAAC;IACrF,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,wBAAwB,CAAC,QAA8B;IAC5D,OAAO,CAAC,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC1F,OAAO,CAAC,KAAK,CAAC,+EAA+E,CAAC,CAAC;IAC/F,OAAO,CAAC,KAAK,CAAC,6EAA6E,CAAC,CAAC;IAC7F,OAAO,CAAC,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAC9F,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;IACxC,OAAO,CAAC,KAAK,CAAC,6EAA6E,CAAC,CAAC;IAC7F,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;IACtE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;QACrD,OAAO,CAAC,KAAK,CAAC,iCAAiC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1E,OAAO,CAAC,KAAK,CAAC,gBAAgB,WAAW,IAAI,CAAC,CAAC;IACnD,CAAC;AACL,CAAC;AAEc,KAAK,UAAU,WAAW,CACrC,OAAgC,EAChC,OAAwB;IAExB,MAAM,MAAM,GAAG,IAAA,yBAAU,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;QAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,qBAAqB,CAAC;IACnE,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,yBAAyB,CAAC;IAE/E,OAAO,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAAC;IAE/E,MAAM,YAAY,GAAG,MAAM,IAAA,gCAAuB,GAAE,CAAC;IACrD,MAAM,cAAc,GAAG,IAAA,kDAAyC,EAAC,YAAY,CAAC,CAAC;IAE/E,MAAM,KAAK,GAAG,yBAAyB,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAC1E,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,YAAoB,EAAE,EAAE,CACvE,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAC1B,CAAC;IAEF,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;QACvE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,cAAc,GAAG,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,cAAc,GAAG,YAAY,CAAC,iBAAiB,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;IACrF,MAAM,YAAY,GAAG,uBAAuB,CAAC,cAAc,EAAE,KAAK,EAAE;QAChE,GAAG,YAAY;QACf,eAAe;KAClB,CAAC,CAAC;IAEH,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;QACxF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;QAAE,aAAa,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;IAC3E,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;QAAE,wBAAwB,CAAC,YAAY,CAAC,CAAC;IACpE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC","sourcesContent":["/**\n * Validate Nx Wiring Executor\n *\n * Enforces that the webpieces validators are actually wired into the build. Two checks:\n *\n * 1. nx.json targetDefaults — the compile executors (@nx/js:tsc, @angular/build:application)\n * must carry the load-bearing dependsOn:\n * [\"architecture:validate-complete\", \"validate-no-file-import-cycles\", \"^build\"]\n *\n * 2. Per-project override guard — a project that declares its OWN build.dependsOn OVERRIDES\n * the targetDefaults entirely (nx does not merge dependsOn). That silently drops `^build`\n * (→ upstream libs not built first, builds against an empty dist) AND the validator gates\n * (→ the project builds green while validating nothing). So we check each compile project's\n * RESOLVED build.dependsOn and fail if it is missing any required entry.\n *\n * If either is stripped, validators exist but never run and the build stays green while\n * validating nothing. This executor fails when the wiring is missing.\n *\n * Conservative by design: only REQUIRES wiring on compile executors actually in use\n * (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.\n *\n * Disable via webpieces.config.json rules[\"nx-wiring\"].mode=\"OFF\".\n *\n * Usage: nx run architecture:validate-nx-wiring\n */\n\nimport type {\n ExecutorContext,\n ProjectsConfigurations,\n TargetDependencyConfig,\n} from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadConfig } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateNxWiringOptions {\n requiredDeps?: string[];\n compileExecutors?: string[];\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst DEFAULT_REQUIRED_DEPS: string[] = [\n 'architecture:validate-complete',\n 'validate-no-file-import-cycles',\n];\n\n// Per-project build targets must ALSO keep `^build` so build order follows nx's full graph.\nconst BUILD_ORDER_DEP = '^build';\n\nconst DEFAULT_COMPILE_EXECUTORS: string[] = [\n '@nx/js:tsc',\n '@angular/build:application',\n];\n\nclass WiringProblem {\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(executorName: string, missing: string[], found: string[]) {\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\nclass ProjectGateProblem {\n project: string;\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(project: string, executorName: string, missing: string[], found: string[]) {\n this.project = project;\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\ninterface TargetDefaultEntry {\n dependsOn?: string[];\n}\n\ninterface RawNxJson {\n targetDefaults?: Record<string, TargetDefaultEntry>;\n}\n\nfunction readTargetDefaults(workspaceRoot: string): Record<string, TargetDefaultEntry> {\n const nxJsonPath = path.join(workspaceRoot, 'nx.json');\n if (!fs.existsSync(nxJsonPath)) return {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(nxJsonPath, 'utf8');\n const parsed = JSON.parse(raw) as RawNxJson;\n return parsed.targetDefaults ?? {};\n // webpieces-disable catch-error-pattern -- malformed nx.json fails open so the check does not crash the build\n } catch (err: unknown) {\n //const error = toError(err); -- malformed nx.json fails open\n void err;\n return {};\n }\n}\n\nfunction findCompileExecutorsInUse(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: string[],\n): Set<string> {\n const known = new Set(compileExecutors);\n const inUse = new Set<string>();\n for (const cfg of Object.values(projectsConfig.projects)) {\n const targets = cfg.targets ?? {};\n for (const target of Object.values(targets)) {\n const executorName = target.executor;\n if (executorName && known.has(executorName)) {\n inUse.add(executorName);\n }\n }\n }\n return inUse;\n}\n\nfunction findProblems(\n relevantExecutors: string[],\n targetDefaults: Record<string, TargetDefaultEntry>,\n requiredDeps: string[],\n): WiringProblem[] {\n const problems: WiringProblem[] = [];\n for (const executorName of relevantExecutors) {\n const entry = targetDefaults[executorName];\n const dependsOn = entry?.dependsOn ?? [];\n const missing = requiredDeps.filter((dep: string) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new WiringProblem(executorName, missing, dependsOn));\n }\n }\n return problems;\n}\n\n/**\n * Normalize an nx dependsOn entry to a comparable string. The string form (\"^build\",\n * \"validate-complete\") passes through; the object form is rendered the same way\n * ({target:\"build\", dependencies:true} → \"^build\", {target:\"build\"} → \"build\").\n */\nfunction normalizeDeps(deps: (string | TargetDependencyConfig)[]): string[] {\n return deps.map((dep) => {\n if (typeof dep === 'string') return dep;\n const target = dep.target ?? '';\n return dep.dependencies ? `^${target}` : target;\n });\n}\n\n/**\n * Check each compile project's RESOLVED build.dependsOn (targetDefaults already merged in)\n * for the required gates + `^build`. A project that overrode them away is flagged.\n */\nfunction findProjectGateProblems(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: Set<string>,\n requiredPerProject: string[],\n): ProjectGateProblem[] {\n const problems: ProjectGateProblem[] = [];\n for (const [name, cfg] of Object.entries(projectsConfig.projects)) {\n // Tooling/foundation packages (type:tooling) BUILD the validators, so gating their own\n // build on validate-complete would create a bootstrap cycle. They are exempt: they keep\n // build-order (\"^build\") only. Application/library code is still fully gated.\n if ((cfg.tags ?? []).includes('type:tooling')) continue;\n const build = cfg.targets?.['build'];\n if (!build || !build.executor || !compileExecutors.has(build.executor)) continue;\n const dependsOn = normalizeDeps(build.dependsOn ?? []);\n const missing = requiredPerProject.filter((dep) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new ProjectGateProblem(name, build.executor, missing, dependsOn));\n }\n }\n return problems;\n}\n\nfunction reportFailure(problems: WiringProblem[], requiredDeps: string[]): void {\n console.error('\\n❌ webpieces validators are not wired into your build (nx.json).\\n');\n console.error('The validators exist but no build depends on them, so they never run.');\n console.error('Add the missing dependsOn entries to nx.json targetDefaults:\\n');\n const depsList = requiredDeps.map((dep: string) => `\"${dep}\"`).join(', ');\n for (const problem of problems) {\n const missingList = problem.missing.map((dep: string) => `\"${dep}\"`).join(', ');\n console.error(` \"${problem.executorName}\": {`);\n console.error(` \"dependsOn\": [${depsList}, \"^build\"]`);\n console.error(' }');\n console.error(` missing: ${missingList}\\n`);\n }\n console.error('To turn this wiring check off, set rules[\"nx-wiring\"].mode=\"OFF\" in');\n console.error('webpieces.config.json.\\n');\n}\n\nfunction reportProjectGateFailure(problems: ProjectGateProblem[]): void {\n console.error('\\n❌ Some projects override away the build gates in their project.json.\\n');\n console.error('A project-level build.dependsOn OVERRIDES nx.json targetDefaults (nx does not');\n console.error('merge dependsOn). That silently drops \"^build\" (upstreams not built first →');\n console.error('builds against an empty dist) and/or the validators (build stays green while');\n console.error('validating nothing).\\n');\n console.error('Fix: remove build.dependsOn from these project.json files so targetDefaults');\n console.error('apply, OR include all required entries explicitly:\\n');\n for (const p of problems) {\n const missingList = p.missing.map((dep) => `\"${dep}\"`).join(', ');\n console.error(` ${p.project} (${p.executorName}):`);\n console.error(` resolved build.dependsOn: ${JSON.stringify(p.found)}`);\n console.error(` missing: ${missingList}\\n`);\n }\n}\n\nexport default async function runExecutor(\n options: ValidateNxWiringOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadConfig(context.root);\n const rule = shared.rules.get('nx-wiring');\n if (rule && rule.isOff) {\n console.log('\\n⏭️ Skipping validate-nx-wiring (mode: OFF)\\n');\n return { success: true };\n }\n\n const requiredDeps = options.requiredDeps ?? DEFAULT_REQUIRED_DEPS;\n const compileExecutors = options.compileExecutors ?? DEFAULT_COMPILE_EXECUTORS;\n\n console.log('\\n🔌 Validating webpieces validators are wired into the build\\n');\n\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const inUse = findCompileExecutorsInUse(projectsConfig, compileExecutors);\n const relevantExecutors = compileExecutors.filter((executorName: string) =>\n inUse.has(executorName),\n );\n\n if (relevantExecutors.length === 0) {\n console.log('✅ No known compile executors in use — nothing to gate\\n');\n return { success: true };\n }\n\n const targetDefaults = readTargetDefaults(context.root);\n const wiringProblems = findProblems(relevantExecutors, targetDefaults, requiredDeps);\n const gateProblems = findProjectGateProblems(projectsConfig, inUse, [\n ...requiredDeps,\n BUILD_ORDER_DEP,\n ]);\n\n if (wiringProblems.length === 0 && gateProblems.length === 0) {\n console.log('✅ Validators are wired into the build (targetDefaults + every project)\\n');\n return { success: true };\n }\n\n if (wiringProblems.length > 0) reportFailure(wiringProblems, requiredDeps);\n if (gateProblems.length > 0) reportProjectGateFailure(gateProblems);\n return { success: false };\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-nx-wiring/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Nx Wiring Executor\n *\n * Enforces that the webpieces validators are actually wired into the build. Two checks:\n *\n * 1. nx.json targetDefaults — the compile executors (@nx/js:tsc, @angular/build:application)\n * must carry the load-bearing dependsOn:\n * [\"architecture:validate-complete\", \"validate-no-file-import-cycles\", \"^build\"]\n *\n * 2. Per-project override guard — a project that declares its OWN build.dependsOn OVERRIDES\n * the targetDefaults entirely (nx does not merge dependsOn). That silently drops `^build`\n * (→ upstream libs not built first, builds against an empty dist) AND the validator gates\n * (→ the project builds green while validating nothing). So we check each compile project's\n * RESOLVED build.dependsOn and fail if it is missing any required entry.\n *\n * If either is stripped, validators exist but never run and the build stays green while\n * validating nothing. This executor fails when the wiring is missing.\n *\n * Conservative by design: only REQUIRES wiring on compile executors actually in use\n * (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.\n *\n * Disable via webpieces.config.json rules[\"nx-wiring\"].mode=\"OFF\".\n *\n * Usage: nx run architecture:validate-nx-wiring\n */\n\nimport type {\n ExecutorContext,\n ProjectsConfigurations,\n TargetDependencyConfig,\n} from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadConfig } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateNxWiringOptions {\n requiredDeps?: string[];\n compileExecutors?: string[];\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst DEFAULT_REQUIRED_DEPS: string[] = [\n 'architecture:validate-complete',\n 'validate-no-file-import-cycles',\n];\n\n// Per-project build targets must ALSO keep `^build` so build order follows nx's full graph.\nconst BUILD_ORDER_DEP = '^build';\n\nconst DEFAULT_COMPILE_EXECUTORS: string[] = [\n '@nx/js:tsc',\n '@angular/build:application',\n];\n\nclass WiringProblem {\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(executorName: string, missing: string[], found: string[]) {\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\nclass ProjectGateProblem {\n project: string;\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(project: string, executorName: string, missing: string[], found: string[]) {\n this.project = project;\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\ninterface TargetDefaultEntry {\n dependsOn?: string[];\n}\n\ninterface RawNxJson {\n targetDefaults?: Record<string, TargetDefaultEntry>;\n}\n\nfunction readTargetDefaults(workspaceRoot: string): Record<string, TargetDefaultEntry> {\n const nxJsonPath = path.join(workspaceRoot, 'nx.json');\n if (!fs.existsSync(nxJsonPath)) return {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(nxJsonPath, 'utf8');\n const parsed = JSON.parse(raw) as RawNxJson;\n return parsed.targetDefaults ?? {};\n // webpieces-disable catch-error-pattern -- malformed nx.json fails open so the check does not crash the build\n } catch (err: unknown) {\n //const error = toError(err); -- malformed nx.json fails open\n void err;\n return {};\n }\n}\n\nfunction findCompileExecutorsInUse(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: string[],\n): Set<string> {\n const known = new Set(compileExecutors);\n const inUse = new Set<string>();\n for (const cfg of Object.values(projectsConfig.projects)) {\n const targets = cfg.targets ?? {};\n for (const target of Object.values(targets)) {\n const executorName = target.executor;\n if (executorName && known.has(executorName)) {\n inUse.add(executorName);\n }\n }\n }\n return inUse;\n}\n\nfunction findProblems(\n relevantExecutors: string[],\n targetDefaults: Record<string, TargetDefaultEntry>,\n requiredDeps: string[],\n): WiringProblem[] {\n const problems: WiringProblem[] = [];\n for (const executorName of relevantExecutors) {\n const entry = targetDefaults[executorName];\n const dependsOn = entry?.dependsOn ?? [];\n const missing = requiredDeps.filter((dep: string) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new WiringProblem(executorName, missing, dependsOn));\n }\n }\n return problems;\n}\n\n/**\n * Normalize an nx dependsOn entry to a comparable string. The string form (\"^build\",\n * \"validate-complete\") passes through; the object form is rendered the same way\n * ({target:\"build\", dependencies:true} → \"^build\", {target:\"build\"} → \"build\").\n */\nfunction normalizeDeps(deps: (string | TargetDependencyConfig)[]): string[] {\n return deps.map((dep) => {\n if (typeof dep === 'string') return dep;\n const target = dep.target ?? '';\n return dep.dependencies ? `^${target}` : target;\n });\n}\n\n/**\n * Check each compile project's RESOLVED build.dependsOn (targetDefaults already merged in)\n * for the required gates + `^build`. A project that overrode them away is flagged.\n */\nfunction findProjectGateProblems(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: Set<string>,\n requiredPerProject: string[],\n): ProjectGateProblem[] {\n const problems: ProjectGateProblem[] = [];\n for (const [name, cfg] of Object.entries(projectsConfig.projects)) {\n // Tooling/foundation packages (type:tooling) BUILD the validators, so gating their own\n // build on validate-complete would create a bootstrap cycle. They are exempt: they keep\n // build-order (\"^build\") only. Application/library code is still fully gated.\n if ((cfg.tags ?? []).includes('type:tooling')) continue;\n const build = cfg.targets?.['build'];\n if (!build || !build.executor || !compileExecutors.has(build.executor)) continue;\n const dependsOn = normalizeDeps(build.dependsOn ?? []);\n const missing = requiredPerProject.filter((dep) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new ProjectGateProblem(name, build.executor, missing, dependsOn));\n }\n }\n return problems;\n}\n\nfunction reportFailure(problems: WiringProblem[], requiredDeps: string[]): void {\n console.error('\\n❌ webpieces validators are not wired into your build (nx.json).\\n');\n console.error('The validators exist but no build depends on them, so they never run.');\n console.error('Add the missing dependsOn entries to nx.json targetDefaults:\\n');\n const depsList = requiredDeps.map((dep: string) => `\"${dep}\"`).join(', ');\n for (const problem of problems) {\n const missingList = problem.missing.map((dep: string) => `\"${dep}\"`).join(', ');\n console.error(` \"${problem.executorName}\": {`);\n console.error(` \"dependsOn\": [${depsList}, \"^build\"]`);\n console.error(' }');\n console.error(` missing: ${missingList}\\n`);\n }\n console.error('To turn this wiring check off, set rules[\"nx-wiring\"].mode=\"OFF\" in');\n console.error('webpieces.config.json.\\n');\n}\n\nfunction reportProjectGateFailure(problems: ProjectGateProblem[]): void {\n console.error('\\n❌ Some projects override away the build gates in their project.json.\\n');\n console.error('A project-level build.dependsOn OVERRIDES nx.json targetDefaults (nx does not');\n console.error('merge dependsOn). That silently drops \"^build\" (upstreams not built first →');\n console.error('builds against an empty dist) and/or the validators (build stays green while');\n console.error('validating nothing).\\n');\n console.error('Fix: remove build.dependsOn from these project.json files so targetDefaults');\n console.error('apply, OR include all required entries explicitly:\\n');\n for (const p of problems) {\n const missingList = p.missing.map((dep) => `\"${dep}\"`).join(', ');\n console.error(` ${p.project} (${p.executorName}):`);\n console.error(` resolved build.dependsOn: ${JSON.stringify(p.found)}`);\n console.error(` missing: ${missingList}\\n`);\n }\n}\n\nexport default async function runExecutor(\n options: ValidateNxWiringOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadConfig(context.root);\n const rule = shared.rules.get('nx-wiring');\n if (rule && rule.isOff) {\n console.log('\\n⏭️ Skipping validate-nx-wiring (mode: OFF)\\n');\n return { success: true };\n }\n\n const requiredDeps = options.requiredDeps ?? DEFAULT_REQUIRED_DEPS;\n const compileExecutors = options.compileExecutors ?? DEFAULT_COMPILE_EXECUTORS;\n\n console.log('\\n🔌 Validating webpieces validators are wired into the build\\n');\n\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const inUse = findCompileExecutorsInUse(projectsConfig, compileExecutors);\n const relevantExecutors = compileExecutors.filter((executorName: string) =>\n inUse.has(executorName),\n );\n\n if (relevantExecutors.length === 0) {\n console.log('✅ No known compile executors in use — nothing to gate\\n');\n return { success: true };\n }\n\n const targetDefaults = readTargetDefaults(context.root);\n const wiringProblems = findProblems(relevantExecutors, targetDefaults, requiredDeps);\n const gateProblems = findProjectGateProblems(projectsConfig, inUse, [\n ...requiredDeps,\n BUILD_ORDER_DEP,\n ]);\n\n if (wiringProblems.length === 0 && gateProblems.length === 0) {\n console.log('✅ Validators are wired into the build (targetDefaults + every project)\\n');\n return { success: true };\n }\n\n if (wiringProblems.length > 0) reportFailure(wiringProblems, requiredDeps);\n if (gateProblems.length > 0) reportProjectGateFailure(gateProblems);\n return { success: false };\n}\n"]}
@@ -35,12 +35,15 @@ const DEFAULT_EXCLUDE_PATHS = [
35
35
  ];
36
36
  const DEFAULT_ALLOWED_ROOT_FILES = ['jest.setup.ts'];
37
37
  class LayerOneViolation {
38
+ filePath;
39
+ projectName;
38
40
  constructor(filePath, projectName) {
39
41
  this.filePath = filePath;
40
42
  this.projectName = projectName;
41
43
  }
42
44
  }
43
45
  class LayerTwoViolation {
46
+ filePath;
44
47
  constructor(filePath) {
45
48
  this.filePath = filePath;
46
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-ts-in-src/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate TypeScript Files in src/ Executor\n *\n * Two-layer rule:\n * Layer 1: every .ts file inside an Nx project must live under src/\n * Layer 2: every .ts file anywhere in the workspace must belong to some\n * Nx project. Orphan files (at workspace root or in a non-project\n * directory) fail the rule unless explicitly allowlisted.\n *\n * `excludePaths` is holistic — its bare dir names and globs exempt files\n * from BOTH layers. Defaults exempt **\\/*.d.ts and **\\/jest.config.ts.\n *\n * Configurable via webpieces.config.json:\n * \"validate-ts-in-src\": {\n * \"mode\": \"ON\", // \"OFF\" disables the rule\n * \"excludePaths\": [...], // dir names + globs, e.g. \"**\\/codegen.ts\"\n * \"allowedRootFiles\": [...]\n * }\n *\n * Usage: nx run architecture:validate-ts-in-src\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadConfig, isPathExcluded } from '@webpieces/rules-config';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport type ValidateTsInSrcMode = 'ON' | 'OFF' | 'MODIFIED_FILES';\n\nexport interface ValidateTsInSrcOptions {\n mode?: ValidateTsInSrcMode;\n excludePaths?: string[];\n allowedRootFiles?: string[];\n ignoreModifiedUntilEpoch?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst DEFAULT_EXCLUDE_PATHS: string[] = [\n 'node_modules', 'dist', '.nx', '.git',\n 'architecture', 'tmp', 'scripts',\n '**/*.d.ts', '**/jest.config.ts',\n];\n\nconst DEFAULT_ALLOWED_ROOT_FILES: string[] = ['jest.setup.ts'];\n\nclass LayerOneViolation {\n filePath: string;\n projectName: string;\n\n constructor(filePath: string, projectName: string) {\n this.filePath = filePath;\n this.projectName = projectName;\n }\n}\n\nclass LayerTwoViolation {\n filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n}\n\nfunction isNodeModulesDir(name: string): boolean {\n return name === 'node_modules' || name.startsWith('node_modules_');\n}\n\nfunction shouldSkipTopLevelDir(name: string, excludePaths: string[]): boolean {\n if (isNodeModulesDir(name)) return true;\n return excludePaths.includes(name);\n}\n\nasync function getProjectRoots(workspaceRoot: string): Promise<string[]> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n const roots: string[] = [];\n for (const cfg of Object.values(projectsConfig.projects)) {\n if (cfg.root === '' || cfg.root === '.') continue;\n if (cfg.root === 'architecture') continue;\n roots.push(path.join(workspaceRoot, cfg.root));\n }\n return roots;\n}\n\nfunction findTsFilesOutsideSrc(projectDir: string): string[] {\n const violations: string[] = [];\n if (!fs.existsSync(projectDir)) return violations;\n const entries = fs.readdirSync(projectDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name === 'src') continue;\n if (isNodeModulesDir(entry.name)) continue;\n if (entry.name === 'dist') continue;\n\n if (entry.isFile() && entry.name.endsWith('.ts')) {\n violations.push(path.join(projectDir, entry.name));\n }\n\n if (entry.isDirectory()) {\n const tsFiles = findTsFilesRecursively(path.join(projectDir, entry.name));\n violations.push(...tsFiles);\n }\n }\n\n return violations;\n}\n\nfunction findTsFilesRecursively(dir: string): string[] {\n const results: string[] = [];\n if (!fs.existsSync(dir)) return results;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (isNodeModulesDir(entry.name)) continue;\n if (entry.name === 'dist') continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isFile() && entry.name.endsWith('.ts')) {\n results.push(fullPath);\n } else if (entry.isDirectory()) {\n results.push(...findTsFilesRecursively(fullPath));\n }\n }\n return results;\n}\n\nfunction findOrphanTsFiles(\n dir: string,\n projectRootSet: Set<string>,\n workspaceRoot: string,\n results: string[],\n): void {\n if (!fs.existsSync(dir)) return;\n\n const relDir = path.relative(workspaceRoot, dir);\n if (projectRootSet.has(relDir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (isNodeModulesDir(entry.name)) continue;\n if (entry.name === 'dist') continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isFile() && entry.name.endsWith('.ts')) {\n results.push(fullPath);\n } else if (entry.isDirectory()) {\n findOrphanTsFiles(fullPath, projectRootSet, workspaceRoot, results);\n }\n }\n}\n\nfunction checkLayerOne(\n projectRoots: string[],\n workspaceRoot: string,\n excludePaths: string[],\n): LayerOneViolation[] {\n const violations: LayerOneViolation[] = [];\n for (const projectDir of projectRoots) {\n const projectName = path.relative(workspaceRoot, projectDir);\n const tsFiles = findTsFilesOutsideSrc(projectDir);\n for (const tsFile of tsFiles) {\n const relativePath = path.relative(workspaceRoot, tsFile);\n if (isPathExcluded(relativePath, excludePaths)) continue;\n violations.push(new LayerOneViolation(relativePath, projectName));\n }\n }\n return violations;\n}\n\nfunction checkLayerTwo(\n workspaceRoot: string,\n projectRoots: string[],\n excludePaths: string[],\n allowedRootFiles: string[],\n): LayerTwoViolation[] {\n const violations: LayerTwoViolation[] = [];\n const projectRootSet = new Set(\n projectRoots.map((p) => path.relative(workspaceRoot, p)),\n );\n\n const entries = fs.readdirSync(workspaceRoot, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile()) {\n if (!entry.name.endsWith('.ts')) continue;\n if (allowedRootFiles.includes(entry.name)) continue;\n if (isPathExcluded(entry.name, excludePaths)) continue;\n violations.push(new LayerTwoViolation(entry.name));\n continue;\n }\n if (!entry.isDirectory()) continue;\n if (shouldSkipTopLevelDir(entry.name, excludePaths)) continue;\n\n const orphans: string[] = [];\n findOrphanTsFiles(\n path.join(workspaceRoot, entry.name),\n projectRootSet,\n workspaceRoot,\n orphans,\n );\n for (const orphan of orphans) {\n const relativePath = path.relative(workspaceRoot, orphan);\n if (isPathExcluded(relativePath, excludePaths)) continue;\n violations.push(new LayerTwoViolation(relativePath));\n }\n }\n\n return violations;\n}\n\nfunction isTestFile(filePath: string): boolean {\n return filePath.includes('.spec.ts') ||\n filePath.includes('.test.ts') ||\n filePath.includes('__tests__/');\n}\n\n// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths\nfunction getChangedTsFiles(workspaceRoot: string, base: string, head?: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const changedFiles = output\n .trim()\n .split('\\n')\n .filter((f: string) => f && !isTestFile(f));\n\n if (!head) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const untrackedFiles = untrackedOutput\n .trim()\n .split('\\n')\n .filter((f: string) => f && !isTestFile(f));\n const allFiles = new Set([...changedFiles, ...untrackedFiles]);\n return Array.from(allFiles);\n // webpieces-disable catch-error-pattern -- intentional swallow; git ls-files failure falls back to staged-only list\n } catch (err: unknown) {\n //const error = toError(err);\n return changedFiles;\n }\n }\n\n return changedFiles;\n // webpieces-disable catch-error-pattern -- intentional swallow; git diff failure returns empty list\n } catch (err: unknown) {\n //const error = toError(err);\n return [];\n }\n}\n\nfunction detectBase(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mergeBase = execSync('git merge-base HEAD origin/main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n // webpieces-disable catch-error-pattern -- intentional swallow; try local main as fallback\n } catch (err: unknown) {\n //const error = toError(err);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mergeBase = execSync('git merge-base HEAD main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n // webpieces-disable catch-error-pattern -- intentional swallow; no base found is handled by caller\n } catch (err2: unknown) {\n //const error2 = toError(err2);\n // Ignore\n }\n }\n return null;\n}\n\nfunction checkSingleFileLayerOne(\n relPath: string,\n projectRoots: string[],\n workspaceRoot: string,\n excludePaths: string[],\n): LayerOneViolation | null {\n if (isPathExcluded(relPath, excludePaths)) return null;\n const fullPath = path.join(workspaceRoot, relPath);\n for (const projectRoot of projectRoots) {\n if (fullPath.startsWith(projectRoot + path.sep) || fullPath === projectRoot) {\n const relToProject = path.relative(projectRoot, fullPath);\n if (relToProject.startsWith('src' + path.sep) || relToProject === 'src') return null;\n const projectName = path.relative(workspaceRoot, projectRoot);\n return new LayerOneViolation(relPath, projectName);\n }\n }\n return null; // not in any project — that's layer 2's concern\n}\n\nfunction checkSingleFileLayerTwo(\n relPath: string,\n projectRoots: string[],\n workspaceRoot: string,\n allowedRootFiles: string[],\n excludePaths: string[],\n): LayerTwoViolation | null {\n if (isPathExcluded(relPath, excludePaths)) return null;\n const parts = relPath.split(path.sep);\n if (parts.length === 1 && allowedRootFiles.includes(parts[0] ?? '')) return null;\n const fullPath = path.join(workspaceRoot, relPath);\n for (const projectRoot of projectRoots) {\n if (fullPath.startsWith(projectRoot + path.sep)) return null; // owned by a project\n }\n return new LayerTwoViolation(relPath);\n}\n\nfunction resolveMode(normalMode: ValidateTsInSrcMode, epoch: number | undefined): ValidateTsInSrcMode {\n if (epoch === undefined || normalMode === 'OFF') {\n return normalMode;\n }\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expiresDate = new Date(epoch * 1000).toISOString().split('T')[0];\n console.log(`\\n⏭️ Skipping validate-ts-in-src validation (ignoreModifiedUntilEpoch active, expires: ${expiresDate})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nfunction reportLayerOneFailure(violations: LayerOneViolation[]): void {\n console.error('❌ TypeScript files found outside src/ directory!\\n');\n console.error('All .ts source files must be inside the project\\'s src/ directory.');\n console.error('This enforces the standard project structure:\\n');\n console.error(' packages/{category}/{name}/');\n console.error(' ├── src/ ← ALL .ts files here');\n console.error(' ├── package.json');\n console.error(' ├── project.json');\n console.error(' └── tsconfig.json\\n');\n\n for (const v of violations) {\n console.error(` ❌ ${v.filePath}`);\n }\n\n console.error('\\nTo fix, pick one:');\n console.error(' (a) Move the .ts file(s) into the src/ directory');\n console.error(' (b) Add a glob/dir to validate-ts-in-src.excludePaths in');\n console.error(' webpieces.config.json (e.g. \"**/codegen.ts\"). Defaults already');\n console.error(' exempt **/*.d.ts and **/jest.config.ts.\\n');\n}\n\nfunction reportLayerTwoFailure(violations: LayerTwoViolation[]): void {\n console.error('❌ TypeScript files found outside any Nx project!\\n');\n console.error('Every .ts file must belong to an Nx project so it is compiled,');\n console.error('linted, and tested under a known project config. Orphan files are');\n console.error('invisible to the build graph and will rot.\\n');\n\n for (const v of violations) {\n console.error(` ❌ ${v.filePath}`);\n }\n\n console.error('\\nTo fix, pick one:');\n console.error(' (a) Move the file into an existing project\\'s src/ directory');\n console.error(' (b) Create a new project (add project.json) that owns the directory');\n console.error(' (c) Add the containing top-level directory to validate-ts-in-src.excludePaths');\n console.error(' in nx.json targetDefaults, or add the filename to allowedRootFiles');\n console.error(' if it is a legitimate workspace-root file (e.g., jest.setup.ts)\\n');\n}\n\nasync function runModifiedFilesMode(\n workspaceRoot: string,\n excludePaths: string[],\n allowedRootFiles: string[],\n): Promise<ExecutorResult> {\n console.log('\\n📁 Validating TypeScript files are in src/ and owned by a project (MODIFIED_FILES mode)\\n');\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping validate-ts-in-src validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\n const projectRoots = await getProjectRoots(workspaceRoot);\n const changedFiles = getChangedTsFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed\\n');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n const layerOneViolations: LayerOneViolation[] = [];\n const layerTwoViolations: LayerTwoViolation[] = [];\n\n for (const relPath of changedFiles) {\n const l1 = checkSingleFileLayerOne(relPath, projectRoots, workspaceRoot, excludePaths);\n if (l1) layerOneViolations.push(l1);\n const l2 = checkSingleFileLayerTwo(relPath, projectRoots, workspaceRoot, allowedRootFiles, excludePaths);\n if (l2) layerTwoViolations.push(l2);\n }\n\n if (layerOneViolations.length === 0 && layerTwoViolations.length === 0) {\n console.log('✅ All changed .ts files are inside a project\\'s src/ directory\\n');\n return { success: true };\n }\n\n if (layerOneViolations.length > 0) {\n reportLayerOneFailure(layerOneViolations);\n }\n if (layerTwoViolations.length > 0) {\n reportLayerTwoFailure(layerTwoViolations);\n }\n\n console.error('To disable: set rules[\"validate-ts-in-src\"].mode to \"OFF\" in webpieces.config.json\\n');\n return { success: false };\n}\n\nasync function runOnMode(\n workspaceRoot: string,\n excludePaths: string[],\n allowedRootFiles: string[],\n): Promise<ExecutorResult> {\n console.log('\\n📁 Validating TypeScript files are in src/ and owned by a project\\n');\n\n const projectRoots = await getProjectRoots(workspaceRoot);\n\n const layerOneViolations = checkLayerOne(projectRoots, workspaceRoot, excludePaths);\n const layerTwoViolations = checkLayerTwo(\n workspaceRoot, projectRoots, excludePaths, allowedRootFiles,\n );\n\n if (layerOneViolations.length === 0 && layerTwoViolations.length === 0) {\n console.log('✅ All .ts files are inside a project\\'s src/ directory\\n');\n return { success: true };\n }\n\n if (layerOneViolations.length > 0) {\n reportLayerOneFailure(layerOneViolations);\n }\n if (layerTwoViolations.length > 0) {\n reportLayerTwoFailure(layerTwoViolations);\n }\n\n console.error('To disable: set rules[\"validate-ts-in-src\"].mode to \"OFF\" in webpieces.config.json\\n');\n return { success: false };\n}\n\nexport default async function runExecutor(\n _nxOptions: ValidateTsInSrcOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n // Config comes from webpieces.config.json — same source as ai-hooks\n // and validate-code — via @webpieces/rules-config.\n const shared = loadConfig(context.root);\n const rule = shared.rules.get('validate-ts-in-src');\n\n const rawMode = (rule?.options['mode'] as ValidateTsInSrcMode | undefined) ?? 'ON';\n const epoch = rule?.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const effectiveMode = resolveMode(rawMode, epoch);\n\n if (effectiveMode === 'OFF' || (rule && rule.isOff)) {\n console.log('\\n⏭️ Skipping validate-ts-in-src (mode: OFF)\\n');\n return { success: true };\n }\n\n const workspaceRoot = context.root;\n const excludePaths =\n (rule?.options['excludePaths'] as string[] | undefined) ?? DEFAULT_EXCLUDE_PATHS;\n const allowedRootFiles =\n (rule?.options['allowedRootFiles'] as string[] | undefined) ?? DEFAULT_ALLOWED_ROOT_FILES;\n\n if (effectiveMode === 'MODIFIED_FILES') {\n return runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFiles);\n }\n\n return runOnMode(workspaceRoot, excludePaths, allowedRootFiles);\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-ts-in-src/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate TypeScript Files in src/ Executor\n *\n * Two-layer rule:\n * Layer 1: every .ts file inside an Nx project must live under src/\n * Layer 2: every .ts file anywhere in the workspace must belong to some\n * Nx project. Orphan files (at workspace root or in a non-project\n * directory) fail the rule unless explicitly allowlisted.\n *\n * `excludePaths` is holistic — its bare dir names and globs exempt files\n * from BOTH layers. Defaults exempt **\\/*.d.ts and **\\/jest.config.ts.\n *\n * Configurable via webpieces.config.json:\n * \"validate-ts-in-src\": {\n * \"mode\": \"ON\", // \"OFF\" disables the rule\n * \"excludePaths\": [...], // dir names + globs, e.g. \"**\\/codegen.ts\"\n * \"allowedRootFiles\": [...]\n * }\n *\n * Usage: nx run architecture:validate-ts-in-src\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadConfig, isPathExcluded } from '@webpieces/rules-config';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport type ValidateTsInSrcMode = 'ON' | 'OFF' | 'MODIFIED_FILES';\n\nexport interface ValidateTsInSrcOptions {\n mode?: ValidateTsInSrcMode;\n excludePaths?: string[];\n allowedRootFiles?: string[];\n ignoreModifiedUntilEpoch?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst DEFAULT_EXCLUDE_PATHS: string[] = [\n 'node_modules', 'dist', '.nx', '.git',\n 'architecture', 'tmp', 'scripts',\n '**/*.d.ts', '**/jest.config.ts',\n];\n\nconst DEFAULT_ALLOWED_ROOT_FILES: string[] = ['jest.setup.ts'];\n\nclass LayerOneViolation {\n filePath: string;\n projectName: string;\n\n constructor(filePath: string, projectName: string) {\n this.filePath = filePath;\n this.projectName = projectName;\n }\n}\n\nclass LayerTwoViolation {\n filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n}\n\nfunction isNodeModulesDir(name: string): boolean {\n return name === 'node_modules' || name.startsWith('node_modules_');\n}\n\nfunction shouldSkipTopLevelDir(name: string, excludePaths: string[]): boolean {\n if (isNodeModulesDir(name)) return true;\n return excludePaths.includes(name);\n}\n\nasync function getProjectRoots(workspaceRoot: string): Promise<string[]> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n const roots: string[] = [];\n for (const cfg of Object.values(projectsConfig.projects)) {\n if (cfg.root === '' || cfg.root === '.') continue;\n if (cfg.root === 'architecture') continue;\n roots.push(path.join(workspaceRoot, cfg.root));\n }\n return roots;\n}\n\nfunction findTsFilesOutsideSrc(projectDir: string): string[] {\n const violations: string[] = [];\n if (!fs.existsSync(projectDir)) return violations;\n const entries = fs.readdirSync(projectDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.name === 'src') continue;\n if (isNodeModulesDir(entry.name)) continue;\n if (entry.name === 'dist') continue;\n\n if (entry.isFile() && entry.name.endsWith('.ts')) {\n violations.push(path.join(projectDir, entry.name));\n }\n\n if (entry.isDirectory()) {\n const tsFiles = findTsFilesRecursively(path.join(projectDir, entry.name));\n violations.push(...tsFiles);\n }\n }\n\n return violations;\n}\n\nfunction findTsFilesRecursively(dir: string): string[] {\n const results: string[] = [];\n if (!fs.existsSync(dir)) return results;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (isNodeModulesDir(entry.name)) continue;\n if (entry.name === 'dist') continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isFile() && entry.name.endsWith('.ts')) {\n results.push(fullPath);\n } else if (entry.isDirectory()) {\n results.push(...findTsFilesRecursively(fullPath));\n }\n }\n return results;\n}\n\nfunction findOrphanTsFiles(\n dir: string,\n projectRootSet: Set<string>,\n workspaceRoot: string,\n results: string[],\n): void {\n if (!fs.existsSync(dir)) return;\n\n const relDir = path.relative(workspaceRoot, dir);\n if (projectRootSet.has(relDir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (isNodeModulesDir(entry.name)) continue;\n if (entry.name === 'dist') continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isFile() && entry.name.endsWith('.ts')) {\n results.push(fullPath);\n } else if (entry.isDirectory()) {\n findOrphanTsFiles(fullPath, projectRootSet, workspaceRoot, results);\n }\n }\n}\n\nfunction checkLayerOne(\n projectRoots: string[],\n workspaceRoot: string,\n excludePaths: string[],\n): LayerOneViolation[] {\n const violations: LayerOneViolation[] = [];\n for (const projectDir of projectRoots) {\n const projectName = path.relative(workspaceRoot, projectDir);\n const tsFiles = findTsFilesOutsideSrc(projectDir);\n for (const tsFile of tsFiles) {\n const relativePath = path.relative(workspaceRoot, tsFile);\n if (isPathExcluded(relativePath, excludePaths)) continue;\n violations.push(new LayerOneViolation(relativePath, projectName));\n }\n }\n return violations;\n}\n\nfunction checkLayerTwo(\n workspaceRoot: string,\n projectRoots: string[],\n excludePaths: string[],\n allowedRootFiles: string[],\n): LayerTwoViolation[] {\n const violations: LayerTwoViolation[] = [];\n const projectRootSet = new Set(\n projectRoots.map((p) => path.relative(workspaceRoot, p)),\n );\n\n const entries = fs.readdirSync(workspaceRoot, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile()) {\n if (!entry.name.endsWith('.ts')) continue;\n if (allowedRootFiles.includes(entry.name)) continue;\n if (isPathExcluded(entry.name, excludePaths)) continue;\n violations.push(new LayerTwoViolation(entry.name));\n continue;\n }\n if (!entry.isDirectory()) continue;\n if (shouldSkipTopLevelDir(entry.name, excludePaths)) continue;\n\n const orphans: string[] = [];\n findOrphanTsFiles(\n path.join(workspaceRoot, entry.name),\n projectRootSet,\n workspaceRoot,\n orphans,\n );\n for (const orphan of orphans) {\n const relativePath = path.relative(workspaceRoot, orphan);\n if (isPathExcluded(relativePath, excludePaths)) continue;\n violations.push(new LayerTwoViolation(relativePath));\n }\n }\n\n return violations;\n}\n\nfunction isTestFile(filePath: string): boolean {\n return filePath.includes('.spec.ts') ||\n filePath.includes('.test.ts') ||\n filePath.includes('__tests__/');\n}\n\n// webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths\nfunction getChangedTsFiles(workspaceRoot: string, base: string, head?: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const diffTarget = head ? `${base} ${head}` : base;\n const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const changedFiles = output\n .trim()\n .split('\\n')\n .filter((f: string) => f && !isTestFile(f));\n\n if (!head) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const untrackedFiles = untrackedOutput\n .trim()\n .split('\\n')\n .filter((f: string) => f && !isTestFile(f));\n const allFiles = new Set([...changedFiles, ...untrackedFiles]);\n return Array.from(allFiles);\n // webpieces-disable catch-error-pattern -- intentional swallow; git ls-files failure falls back to staged-only list\n } catch (err: unknown) {\n //const error = toError(err);\n return changedFiles;\n }\n }\n\n return changedFiles;\n // webpieces-disable catch-error-pattern -- intentional swallow; git diff failure returns empty list\n } catch (err: unknown) {\n //const error = toError(err);\n return [];\n }\n}\n\nfunction detectBase(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mergeBase = execSync('git merge-base HEAD origin/main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n // webpieces-disable catch-error-pattern -- intentional swallow; try local main as fallback\n } catch (err: unknown) {\n //const error = toError(err);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mergeBase = execSync('git merge-base HEAD main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n // webpieces-disable catch-error-pattern -- intentional swallow; no base found is handled by caller\n } catch (err2: unknown) {\n //const error2 = toError(err2);\n // Ignore\n }\n }\n return null;\n}\n\nfunction checkSingleFileLayerOne(\n relPath: string,\n projectRoots: string[],\n workspaceRoot: string,\n excludePaths: string[],\n): LayerOneViolation | null {\n if (isPathExcluded(relPath, excludePaths)) return null;\n const fullPath = path.join(workspaceRoot, relPath);\n for (const projectRoot of projectRoots) {\n if (fullPath.startsWith(projectRoot + path.sep) || fullPath === projectRoot) {\n const relToProject = path.relative(projectRoot, fullPath);\n if (relToProject.startsWith('src' + path.sep) || relToProject === 'src') return null;\n const projectName = path.relative(workspaceRoot, projectRoot);\n return new LayerOneViolation(relPath, projectName);\n }\n }\n return null; // not in any project — that's layer 2's concern\n}\n\nfunction checkSingleFileLayerTwo(\n relPath: string,\n projectRoots: string[],\n workspaceRoot: string,\n allowedRootFiles: string[],\n excludePaths: string[],\n): LayerTwoViolation | null {\n if (isPathExcluded(relPath, excludePaths)) return null;\n const parts = relPath.split(path.sep);\n if (parts.length === 1 && allowedRootFiles.includes(parts[0] ?? '')) return null;\n const fullPath = path.join(workspaceRoot, relPath);\n for (const projectRoot of projectRoots) {\n if (fullPath.startsWith(projectRoot + path.sep)) return null; // owned by a project\n }\n return new LayerTwoViolation(relPath);\n}\n\nfunction resolveMode(normalMode: ValidateTsInSrcMode, epoch: number | undefined): ValidateTsInSrcMode {\n if (epoch === undefined || normalMode === 'OFF') {\n return normalMode;\n }\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expiresDate = new Date(epoch * 1000).toISOString().split('T')[0];\n console.log(`\\n⏭️ Skipping validate-ts-in-src validation (ignoreModifiedUntilEpoch active, expires: ${expiresDate})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nfunction reportLayerOneFailure(violations: LayerOneViolation[]): void {\n console.error('❌ TypeScript files found outside src/ directory!\\n');\n console.error('All .ts source files must be inside the project\\'s src/ directory.');\n console.error('This enforces the standard project structure:\\n');\n console.error(' packages/{category}/{name}/');\n console.error(' ├── src/ ← ALL .ts files here');\n console.error(' ├── package.json');\n console.error(' ├── project.json');\n console.error(' └── tsconfig.json\\n');\n\n for (const v of violations) {\n console.error(` ❌ ${v.filePath}`);\n }\n\n console.error('\\nTo fix, pick one:');\n console.error(' (a) Move the .ts file(s) into the src/ directory');\n console.error(' (b) Add a glob/dir to validate-ts-in-src.excludePaths in');\n console.error(' webpieces.config.json (e.g. \"**/codegen.ts\"). Defaults already');\n console.error(' exempt **/*.d.ts and **/jest.config.ts.\\n');\n}\n\nfunction reportLayerTwoFailure(violations: LayerTwoViolation[]): void {\n console.error('❌ TypeScript files found outside any Nx project!\\n');\n console.error('Every .ts file must belong to an Nx project so it is compiled,');\n console.error('linted, and tested under a known project config. Orphan files are');\n console.error('invisible to the build graph and will rot.\\n');\n\n for (const v of violations) {\n console.error(` ❌ ${v.filePath}`);\n }\n\n console.error('\\nTo fix, pick one:');\n console.error(' (a) Move the file into an existing project\\'s src/ directory');\n console.error(' (b) Create a new project (add project.json) that owns the directory');\n console.error(' (c) Add the containing top-level directory to validate-ts-in-src.excludePaths');\n console.error(' in nx.json targetDefaults, or add the filename to allowedRootFiles');\n console.error(' if it is a legitimate workspace-root file (e.g., jest.setup.ts)\\n');\n}\n\nasync function runModifiedFilesMode(\n workspaceRoot: string,\n excludePaths: string[],\n allowedRootFiles: string[],\n): Promise<ExecutorResult> {\n console.log('\\n📁 Validating TypeScript files are in src/ and owned by a project (MODIFIED_FILES mode)\\n');\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping validate-ts-in-src validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\n const projectRoots = await getProjectRoots(workspaceRoot);\n const changedFiles = getChangedTsFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed\\n');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n const layerOneViolations: LayerOneViolation[] = [];\n const layerTwoViolations: LayerTwoViolation[] = [];\n\n for (const relPath of changedFiles) {\n const l1 = checkSingleFileLayerOne(relPath, projectRoots, workspaceRoot, excludePaths);\n if (l1) layerOneViolations.push(l1);\n const l2 = checkSingleFileLayerTwo(relPath, projectRoots, workspaceRoot, allowedRootFiles, excludePaths);\n if (l2) layerTwoViolations.push(l2);\n }\n\n if (layerOneViolations.length === 0 && layerTwoViolations.length === 0) {\n console.log('✅ All changed .ts files are inside a project\\'s src/ directory\\n');\n return { success: true };\n }\n\n if (layerOneViolations.length > 0) {\n reportLayerOneFailure(layerOneViolations);\n }\n if (layerTwoViolations.length > 0) {\n reportLayerTwoFailure(layerTwoViolations);\n }\n\n console.error('To disable: set rules[\"validate-ts-in-src\"].mode to \"OFF\" in webpieces.config.json\\n');\n return { success: false };\n}\n\nasync function runOnMode(\n workspaceRoot: string,\n excludePaths: string[],\n allowedRootFiles: string[],\n): Promise<ExecutorResult> {\n console.log('\\n📁 Validating TypeScript files are in src/ and owned by a project\\n');\n\n const projectRoots = await getProjectRoots(workspaceRoot);\n\n const layerOneViolations = checkLayerOne(projectRoots, workspaceRoot, excludePaths);\n const layerTwoViolations = checkLayerTwo(\n workspaceRoot, projectRoots, excludePaths, allowedRootFiles,\n );\n\n if (layerOneViolations.length === 0 && layerTwoViolations.length === 0) {\n console.log('✅ All .ts files are inside a project\\'s src/ directory\\n');\n return { success: true };\n }\n\n if (layerOneViolations.length > 0) {\n reportLayerOneFailure(layerOneViolations);\n }\n if (layerTwoViolations.length > 0) {\n reportLayerTwoFailure(layerTwoViolations);\n }\n\n console.error('To disable: set rules[\"validate-ts-in-src\"].mode to \"OFF\" in webpieces.config.json\\n');\n return { success: false };\n}\n\nexport default async function runExecutor(\n _nxOptions: ValidateTsInSrcOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n // Config comes from webpieces.config.json — same source as ai-hooks\n // and validate-code — via @webpieces/rules-config.\n const shared = loadConfig(context.root);\n const rule = shared.rules.get('validate-ts-in-src');\n\n const rawMode = (rule?.options['mode'] as ValidateTsInSrcMode | undefined) ?? 'ON';\n const epoch = rule?.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const effectiveMode = resolveMode(rawMode, epoch);\n\n if (effectiveMode === 'OFF' || (rule && rule.isOff)) {\n console.log('\\n⏭️ Skipping validate-ts-in-src (mode: OFF)\\n');\n return { success: true };\n }\n\n const workspaceRoot = context.root;\n const excludePaths =\n (rule?.options['excludePaths'] as string[] | undefined) ?? DEFAULT_EXCLUDE_PATHS;\n const allowedRootFiles =\n (rule?.options['allowedRootFiles'] as string[] | undefined) ?? DEFAULT_ALLOWED_ROOT_FILES;\n\n if (effectiveMode === 'MODIFIED_FILES') {\n return runModifiedFilesMode(workspaceRoot, excludePaths, allowedRootFiles);\n }\n\n return runOnMode(workspaceRoot, excludePaths, allowedRootFiles);\n}\n"]}
@@ -37,7 +37,7 @@ function loadBlessedGraph(workspaceRoot, graphPath = exports.DEFAULT_GRAPH_PATH)
37
37
  }
38
38
  catch (err) {
39
39
  const error = (0, toError_1.toError)(err);
40
- throw new Error(`Failed to load graph from ${fullPath}: ${error.message}`);
40
+ throw new Error(`Failed to load graph from ${fullPath}`, { cause: error });
41
41
  }
42
42
  }
43
43
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"graph-loader.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Loader\n *\n * Handles loading and saving the blessed dependency graph file.\n * The graph is stored at architecture/dependencies.json in the workspace root.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { toError } from '../toError';\n\n/**\n * Default path for the dependencies file (relative to workspace root)\n */\nexport const DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';\n\n/**\n * Load the blessed graph from disk\n *\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)\n * @returns The blessed graph, or null if file doesn't exist\n */\nexport function loadBlessedGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): EnhancedGraph | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(fullPath, 'utf-8');\n return JSON.parse(content) as EnhancedGraph;\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to load graph from ${fullPath}: ${error.message}`);\n }\n}\n\n/**\n * Format a graph as JSON with multi-line arrays for readability\n */\nfunction formatGraphJson(graph: EnhancedGraph): string {\n const lines: string[] = ['{'];\n const keys = Object.keys(graph).sort();\n\n keys.forEach((key, index) => {\n const entry = graph[key];\n const isLast = index === keys.length - 1;\n const comma = isLast ? '' : ',';\n\n lines.push(` \"${key}\": {`);\n lines.push(` \"level\": ${entry.level},`);\n\n if (entry.dependsOn.length === 0) {\n lines.push(` \"dependsOn\": []`);\n } else {\n lines.push(` \"dependsOn\": [`);\n entry.dependsOn.forEach((dep, depIndex) => {\n const depComma = depIndex === entry.dependsOn.length - 1 ? '' : ',';\n lines.push(` \"${dep}\"${depComma}`);\n });\n lines.push(` ]`);\n }\n\n lines.push(` }${comma}`);\n\n });\n\n lines.push('}');\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * Save the graph to disk\n *\n * @param graph - The graph to save\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)\n */\nexport function saveGraph(\n graph: EnhancedGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n\n // Ensure directory exists\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Sort keys for deterministic output\n const sortedGraph: EnhancedGraph = {};\n const sortedKeys = Object.keys(graph).sort();\n for (const key of sortedKeys) {\n sortedGraph[key] = graph[key];\n }\n\n const content = formatGraphJson(sortedGraph);\n fs.writeFileSync(fullPath, content, 'utf-8');\n}\n\n/**\n * Check if the graph file exists\n */\nexport function graphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): boolean {\n const fullPath = path.join(workspaceRoot, graphPath);\n return fs.existsSync(fullPath);\n}\n"]}
1
+ {"version":3,"file":"graph-loader.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-loader.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAmBH,4CAkBC;AA2CD,8BAsBC;AAKD,0CAMC;;AA/GD,+CAAyB;AACzB,mDAA6B;AAE7B,wCAAqC;AAErC;;GAEG;AACU,QAAA,kBAAkB,GAAG,gCAAgC,CAAC;AAEnE;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAC5B,aAAqB,EACrB,YAAoB,0BAAkB;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAErD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAkB,CAAC;IAChD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,KAAoB;IACzC,MAAM,KAAK,GAAa,CAAC,GAAG,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAEvC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,MAAM,MAAM,GAAG,KAAK,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAEhC,KAAK,CAAC,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,oBAAoB,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;QAE/C,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACJ,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;YACrC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE;gBACtC,MAAM,QAAQ,GAAG,QAAQ,KAAK,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;gBACpE,KAAK,CAAC,IAAI,CAAC,gBAAgB,GAAG,IAAI,QAAQ,EAAE,CAAC,CAAC;YAClD,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC5B,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC;IAEhC,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACnC,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,SAAS,CACrB,KAAoB,EACpB,aAAqB,EACrB,YAAoB,0BAAkB;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEnC,0BAA0B;IAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,qCAAqC;IACrC,MAAM,WAAW,GAAkB,EAAE,CAAC;IACtC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7C,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC3B,WAAW,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,OAAO,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC7C,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe,CAC3B,aAAqB,EACrB,YAAoB,0BAAkB;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IACrD,OAAO,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/**\n * Graph Loader\n *\n * Handles loading and saving the blessed dependency graph file.\n * The graph is stored at architecture/dependencies.json in the workspace root.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { EnhancedGraph } from './graph-sorter';\nimport { toError } from '../toError';\n\n/**\n * Default path for the dependencies file (relative to workspace root)\n */\nexport const DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';\n\n/**\n * Load the blessed graph from disk\n *\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)\n * @returns The blessed graph, or null if file doesn't exist\n */\nexport function loadBlessedGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): EnhancedGraph | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(fullPath, 'utf-8');\n return JSON.parse(content) as EnhancedGraph;\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to load graph from ${fullPath}`, { cause: error });\n }\n}\n\n/**\n * Format a graph as JSON with multi-line arrays for readability\n */\nfunction formatGraphJson(graph: EnhancedGraph): string {\n const lines: string[] = ['{'];\n const keys = Object.keys(graph).sort();\n\n keys.forEach((key, index) => {\n const entry = graph[key];\n const isLast = index === keys.length - 1;\n const comma = isLast ? '' : ',';\n\n lines.push(` \"${key}\": {`);\n lines.push(` \"level\": ${entry.level},`);\n\n if (entry.dependsOn.length === 0) {\n lines.push(` \"dependsOn\": []`);\n } else {\n lines.push(` \"dependsOn\": [`);\n entry.dependsOn.forEach((dep, depIndex) => {\n const depComma = depIndex === entry.dependsOn.length - 1 ? '' : ',';\n lines.push(` \"${dep}\"${depComma}`);\n });\n lines.push(` ]`);\n }\n\n lines.push(` }${comma}`);\n\n });\n\n lines.push('}');\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * Save the graph to disk\n *\n * @param graph - The graph to save\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: .graphs/dependencies.json)\n */\nexport function saveGraph(\n graph: EnhancedGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n\n // Ensure directory exists\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Sort keys for deterministic output\n const sortedGraph: EnhancedGraph = {};\n const sortedKeys = Object.keys(graph).sort();\n for (const key of sortedKeys) {\n sortedGraph[key] = graph[key];\n }\n\n const content = formatGraphJson(sortedGraph);\n fs.writeFileSync(fullPath, content, 'utf-8');\n}\n\n/**\n * Check if the graph file exists\n */\nexport function graphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH\n): boolean {\n const fullPath = path.join(workspaceRoot, graphPath);\n return fs.existsSync(fullPath);\n}\n"]}
@@ -176,7 +176,7 @@ function loadRuntimeGraph(workspaceRoot, graphPath = exports.DEFAULT_RUNTIME_GRA
176
176
  }
177
177
  catch (err) {
178
178
  const error = (0, toError_1.toError)(err);
179
- throw new Error(`Failed to load runtime graph from ${fullPath}: ${error.message}`);
179
+ throw new Error(`Failed to load runtime graph from ${fullPath}`, { cause: error });
180
180
  }
181
181
  }
182
182
  /** Serialize for an in-memory equality check (matches the on-disk format). */
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-graph.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime Graph\n *\n * Assembles the runtime microservice graph from the per-service\n * `service-contract.json` files, and saves/loads the committed\n * `architecture/runtime-dependencies.json`.\n *\n * The runtime edge Z -> X (Z depends on X at runtime) is INFERRED: Z `uses` api\n * Y and X `implements` api Y. This edge does not exist in the compile-time\n * dependencies.json (both Z and X only compile-depend on the api library Y).\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { sortGraphTopologically } from './graph-sorter';\nimport { readServiceContract, resolvePackageNames } from './runtime-markers';\nimport type { WorkspaceModel } from './runtime-markers';\nimport { toError } from '../toError';\n\nexport const DEFAULT_RUNTIME_GRAPH_PATH = 'architecture/runtime-dependencies.json';\n\nexport interface RuntimeService {\n level: number;\n implements: string[];\n uses: string[];\n dependsOn: string[];\n}\n\nexport interface RuntimeApi {\n implementedBy: string[];\n usedBy: string[];\n}\n\nexport interface RuntimeEdge {\n from: string;\n to: string;\n via: string[];\n}\n\nexport interface RuntimeUnresolved {\n service: string;\n api: string;\n}\n\nexport interface RuntimeGraph {\n services: Record<string, RuntimeService>;\n apis: Record<string, RuntimeApi>;\n runtimeEdges: RuntimeEdge[];\n unresolvedUses: RuntimeUnresolved[];\n}\n\n/** One service's declared relationships, resolved to workspace project names. */\ninterface ServiceDecl {\n name: string;\n implements: string[];\n uses: string[];\n}\n\ninterface EdgeResult {\n edges: RuntimeEdge[];\n unresolved: RuntimeUnresolved[];\n}\n\n/** Collect every service project (servicePaths), with its declarations resolved to projects. */\nfunction collectServiceDecls(model: WorkspaceModel, workspaceRoot: string): ServiceDecl[] {\n const decls: ServiceDecl[] = [];\n for (const info of model.projects.values()) {\n if (!info.isService) continue;\n // A service missing its contract still appears as a node (empty edges);\n // validate-runtime-markers fails it separately for the missing file.\n const contract = readServiceContract(workspaceRoot, info.root);\n const implementsPkgs = contract ? contract.implements : [];\n const usesPkgs = contract ? contract.uses : [];\n decls.push({\n name: info.name,\n implements: resolvePackageNames(model, implementsPkgs).projects.sort(),\n uses: resolvePackageNames(model, usesPkgs).projects.sort(),\n });\n }\n return decls.sort((a: ServiceDecl, b: ServiceDecl) => a.name.localeCompare(b.name));\n}\n\nfunction buildApiIndex(decls: ServiceDecl[]): Map<string, RuntimeApi> {\n const apis = new Map<string, RuntimeApi>();\n const ensure = (api: string): RuntimeApi => {\n let entry = apis.get(api);\n if (!entry) {\n entry = { implementedBy: [], usedBy: [] };\n apis.set(api, entry);\n }\n return entry;\n };\n for (const decl of decls) {\n for (const api of decl.implements) ensure(api).implementedBy.push(decl.name);\n for (const api of decl.uses) ensure(api).usedBy.push(decl.name);\n }\n for (const entry of apis.values()) {\n entry.implementedBy.sort();\n entry.usedBy.sort();\n }\n return apis;\n}\n\n/** Build inferred runtime edges (Z -> X via Y) and the unresolved-uses list. */\nfunction buildEdges(decls: ServiceDecl[], apis: Map<string, RuntimeApi>): EdgeResult {\n const viaByEdge = new Map<string, Set<string>>();\n const unresolved: RuntimeUnresolved[] = [];\n\n for (const decl of decls) {\n for (const api of decl.uses) {\n const implementers = apis.get(api)?.implementedBy ?? [];\n if (implementers.length === 0) {\n unresolved.push({ service: decl.name, api });\n continue;\n }\n for (const target of implementers) {\n if (target === decl.name) continue;\n const key = `${decl.name} ${target}`;\n if (!viaByEdge.has(key)) viaByEdge.set(key, new Set());\n viaByEdge.get(key)!.add(api);\n }\n }\n }\n\n const edges: RuntimeEdge[] = [];\n for (const key of viaByEdge.keys()) {\n const parts = key.split(' ');\n edges.push({ from: parts[0], to: parts[1], via: Array.from(viaByEdge.get(key)!).sort() });\n }\n edges.sort((a: RuntimeEdge, b: RuntimeEdge) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));\n unresolved.sort(\n (a: RuntimeUnresolved, b: RuntimeUnresolved) =>\n a.service.localeCompare(b.service) || a.api.localeCompare(b.api),\n );\n return { edges, unresolved };\n}\n\n/** Adjacency (service -> [targets]) used for leveling + cycle checks. */\nfunction adjacencyFromEdges(serviceNames: string[], edges: RuntimeEdge[]): Record<string, string[]> {\n const adj: Record<string, string[]> = {};\n for (const name of serviceNames) adj[name] = [];\n for (const edge of edges) {\n if (!adj[edge.from]) adj[edge.from] = [];\n adj[edge.from].push(edge.to);\n }\n return adj;\n}\n\n/** Adjacency (service -> [targets]) from a loaded runtime graph. */\nexport function runtimeAdjacency(graph: RuntimeGraph): Record<string, string[]> {\n return adjacencyFromEdges(Object.keys(graph.services), graph.runtimeEdges);\n}\n\n/** Assign levels via topological sort; falls back to level 0 when a cycle exists. */\nfunction assignLevels(adjacency: Record<string, string[]>): Record<string, number> {\n const levels: Record<string, number> = {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const sorted = sortGraphTopologically(adjacency);\n for (const name of Object.keys(sorted)) levels[name] = sorted[name].level;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n for (const name of Object.keys(adjacency)) levels[name] = 0;\n }\n return levels;\n}\n\n/** Assemble the full runtime graph from the workspace model + service contracts. */\nexport function assembleRuntimeGraph(model: WorkspaceModel, workspaceRoot: string): RuntimeGraph {\n const decls = collectServiceDecls(model, workspaceRoot);\n const apis = buildApiIndex(decls);\n const edgeResult = buildEdges(decls, apis);\n\n const services: Record<string, RuntimeService> = {};\n for (const decl of decls) {\n const dependsOn = Array.from(\n new Set(edgeResult.edges.filter((e: RuntimeEdge) => e.from === decl.name).map((e: RuntimeEdge) => e.to)),\n ).sort();\n services[decl.name] = { level: 0, implements: decl.implements, uses: decl.uses, dependsOn };\n }\n\n const levels = assignLevels(adjacencyFromEdges(Object.keys(services), edgeResult.edges));\n for (const name of Object.keys(services)) services[name].level = levels[name] ?? 0;\n\n const apisObj: Record<string, RuntimeApi> = {};\n for (const api of Array.from(apis.keys()).sort()) apisObj[api] = apis.get(api)!;\n\n return {\n services,\n apis: apisObj,\n runtimeEdges: edgeResult.edges,\n unresolvedUses: edgeResult.unresolved,\n };\n}\n\n/** Deterministic JSON (sorted keys + arrays already sorted during assembly). */\nfunction formatRuntimeJson(graph: RuntimeGraph): string {\n return JSON.stringify(graph, null, 4) + '\\n';\n}\n\nexport function saveRuntimeGraph(\n graph: RuntimeGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_RUNTIME_GRAPH_PATH,\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(fullPath, formatRuntimeJson(graph), 'utf-8');\n}\n\nexport function runtimeGraphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_RUNTIME_GRAPH_PATH,\n): boolean {\n return fs.existsSync(path.join(workspaceRoot, graphPath));\n}\n\nexport function loadRuntimeGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_RUNTIME_GRAPH_PATH,\n): RuntimeGraph | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n if (!fs.existsSync(fullPath)) return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(fs.readFileSync(fullPath, 'utf-8')) as RuntimeGraph;\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to load runtime graph from ${fullPath}: ${error.message}`);\n }\n}\n\n/** Serialize for an in-memory equality check (matches the on-disk format). */\nexport function serializeRuntimeGraph(graph: RuntimeGraph): string {\n return formatRuntimeJson(graph);\n}\n"]}
1
+ {"version":3,"file":"runtime-graph.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime Graph\n *\n * Assembles the runtime microservice graph from the per-service\n * `service-contract.json` files, and saves/loads the committed\n * `architecture/runtime-dependencies.json`.\n *\n * The runtime edge Z -> X (Z depends on X at runtime) is INFERRED: Z `uses` api\n * Y and X `implements` api Y. This edge does not exist in the compile-time\n * dependencies.json (both Z and X only compile-depend on the api library Y).\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { sortGraphTopologically } from './graph-sorter';\nimport { readServiceContract, resolvePackageNames } from './runtime-markers';\nimport type { WorkspaceModel } from './runtime-markers';\nimport { toError } from '../toError';\n\nexport const DEFAULT_RUNTIME_GRAPH_PATH = 'architecture/runtime-dependencies.json';\n\nexport interface RuntimeService {\n level: number;\n implements: string[];\n uses: string[];\n dependsOn: string[];\n}\n\nexport interface RuntimeApi {\n implementedBy: string[];\n usedBy: string[];\n}\n\nexport interface RuntimeEdge {\n from: string;\n to: string;\n via: string[];\n}\n\nexport interface RuntimeUnresolved {\n service: string;\n api: string;\n}\n\nexport interface RuntimeGraph {\n services: Record<string, RuntimeService>;\n apis: Record<string, RuntimeApi>;\n runtimeEdges: RuntimeEdge[];\n unresolvedUses: RuntimeUnresolved[];\n}\n\n/** One service's declared relationships, resolved to workspace project names. */\ninterface ServiceDecl {\n name: string;\n implements: string[];\n uses: string[];\n}\n\ninterface EdgeResult {\n edges: RuntimeEdge[];\n unresolved: RuntimeUnresolved[];\n}\n\n/** Collect every service project (servicePaths), with its declarations resolved to projects. */\nfunction collectServiceDecls(model: WorkspaceModel, workspaceRoot: string): ServiceDecl[] {\n const decls: ServiceDecl[] = [];\n for (const info of model.projects.values()) {\n if (!info.isService) continue;\n // A service missing its contract still appears as a node (empty edges);\n // validate-runtime-markers fails it separately for the missing file.\n const contract = readServiceContract(workspaceRoot, info.root);\n const implementsPkgs = contract ? contract.implements : [];\n const usesPkgs = contract ? contract.uses : [];\n decls.push({\n name: info.name,\n implements: resolvePackageNames(model, implementsPkgs).projects.sort(),\n uses: resolvePackageNames(model, usesPkgs).projects.sort(),\n });\n }\n return decls.sort((a: ServiceDecl, b: ServiceDecl) => a.name.localeCompare(b.name));\n}\n\nfunction buildApiIndex(decls: ServiceDecl[]): Map<string, RuntimeApi> {\n const apis = new Map<string, RuntimeApi>();\n const ensure = (api: string): RuntimeApi => {\n let entry = apis.get(api);\n if (!entry) {\n entry = { implementedBy: [], usedBy: [] };\n apis.set(api, entry);\n }\n return entry;\n };\n for (const decl of decls) {\n for (const api of decl.implements) ensure(api).implementedBy.push(decl.name);\n for (const api of decl.uses) ensure(api).usedBy.push(decl.name);\n }\n for (const entry of apis.values()) {\n entry.implementedBy.sort();\n entry.usedBy.sort();\n }\n return apis;\n}\n\n/** Build inferred runtime edges (Z -> X via Y) and the unresolved-uses list. */\nfunction buildEdges(decls: ServiceDecl[], apis: Map<string, RuntimeApi>): EdgeResult {\n const viaByEdge = new Map<string, Set<string>>();\n const unresolved: RuntimeUnresolved[] = [];\n\n for (const decl of decls) {\n for (const api of decl.uses) {\n const implementers = apis.get(api)?.implementedBy ?? [];\n if (implementers.length === 0) {\n unresolved.push({ service: decl.name, api });\n continue;\n }\n for (const target of implementers) {\n if (target === decl.name) continue;\n const key = `${decl.name} ${target}`;\n if (!viaByEdge.has(key)) viaByEdge.set(key, new Set());\n viaByEdge.get(key)!.add(api);\n }\n }\n }\n\n const edges: RuntimeEdge[] = [];\n for (const key of viaByEdge.keys()) {\n const parts = key.split(' ');\n edges.push({ from: parts[0], to: parts[1], via: Array.from(viaByEdge.get(key)!).sort() });\n }\n edges.sort((a: RuntimeEdge, b: RuntimeEdge) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));\n unresolved.sort(\n (a: RuntimeUnresolved, b: RuntimeUnresolved) =>\n a.service.localeCompare(b.service) || a.api.localeCompare(b.api),\n );\n return { edges, unresolved };\n}\n\n/** Adjacency (service -> [targets]) used for leveling + cycle checks. */\nfunction adjacencyFromEdges(serviceNames: string[], edges: RuntimeEdge[]): Record<string, string[]> {\n const adj: Record<string, string[]> = {};\n for (const name of serviceNames) adj[name] = [];\n for (const edge of edges) {\n if (!adj[edge.from]) adj[edge.from] = [];\n adj[edge.from].push(edge.to);\n }\n return adj;\n}\n\n/** Adjacency (service -> [targets]) from a loaded runtime graph. */\nexport function runtimeAdjacency(graph: RuntimeGraph): Record<string, string[]> {\n return adjacencyFromEdges(Object.keys(graph.services), graph.runtimeEdges);\n}\n\n/** Assign levels via topological sort; falls back to level 0 when a cycle exists. */\nfunction assignLevels(adjacency: Record<string, string[]>): Record<string, number> {\n const levels: Record<string, number> = {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const sorted = sortGraphTopologically(adjacency);\n for (const name of Object.keys(sorted)) levels[name] = sorted[name].level;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n for (const name of Object.keys(adjacency)) levels[name] = 0;\n }\n return levels;\n}\n\n/** Assemble the full runtime graph from the workspace model + service contracts. */\nexport function assembleRuntimeGraph(model: WorkspaceModel, workspaceRoot: string): RuntimeGraph {\n const decls = collectServiceDecls(model, workspaceRoot);\n const apis = buildApiIndex(decls);\n const edgeResult = buildEdges(decls, apis);\n\n const services: Record<string, RuntimeService> = {};\n for (const decl of decls) {\n const dependsOn = Array.from(\n new Set(edgeResult.edges.filter((e: RuntimeEdge) => e.from === decl.name).map((e: RuntimeEdge) => e.to)),\n ).sort();\n services[decl.name] = { level: 0, implements: decl.implements, uses: decl.uses, dependsOn };\n }\n\n const levels = assignLevels(adjacencyFromEdges(Object.keys(services), edgeResult.edges));\n for (const name of Object.keys(services)) services[name].level = levels[name] ?? 0;\n\n const apisObj: Record<string, RuntimeApi> = {};\n for (const api of Array.from(apis.keys()).sort()) apisObj[api] = apis.get(api)!;\n\n return {\n services,\n apis: apisObj,\n runtimeEdges: edgeResult.edges,\n unresolvedUses: edgeResult.unresolved,\n };\n}\n\n/** Deterministic JSON (sorted keys + arrays already sorted during assembly). */\nfunction formatRuntimeJson(graph: RuntimeGraph): string {\n return JSON.stringify(graph, null, 4) + '\\n';\n}\n\nexport function saveRuntimeGraph(\n graph: RuntimeGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_RUNTIME_GRAPH_PATH,\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(fullPath, formatRuntimeJson(graph), 'utf-8');\n}\n\nexport function runtimeGraphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_RUNTIME_GRAPH_PATH,\n): boolean {\n return fs.existsSync(path.join(workspaceRoot, graphPath));\n}\n\nexport function loadRuntimeGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_RUNTIME_GRAPH_PATH,\n): RuntimeGraph | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n if (!fs.existsSync(fullPath)) return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(fs.readFileSync(fullPath, 'utf-8')) as RuntimeGraph;\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to load runtime graph from ${fullPath}`, { cause: error });\n }\n}\n\n/** Serialize for an in-memory equality check (matches the on-disk format). */\nexport function serializeRuntimeGraph(graph: RuntimeGraph): string {\n return formatRuntimeJson(graph);\n}\n"]}
@@ -108,7 +108,7 @@ function readServiceContract(workspaceRoot, projectRoot) {
108
108
  }
109
109
  catch (err) {
110
110
  const error = (0, toError_1.toError)(err);
111
- throw new Error(`Failed to parse ${projectRoot}/${exports.SERVICE_CONTRACT_FILENAME}: ${error.message}`);
111
+ throw new Error(`Failed to parse ${projectRoot}/${exports.SERVICE_CONTRACT_FILENAME}`, { cause: error });
112
112
  }
113
113
  }
114
114
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-markers.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-markers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime Markers\n *\n * Reads the per-service `service-contract.json` files and builds a workspace\n * model used to assemble the runtime microservice graph and to validate each\n * service independently.\n *\n * `service-contract.json` (at a service's project root) declares, at api-PROJECT\n * granularity (by package name), which api projects a service IMPLEMENTS (serves)\n * and which it USES (calls as a client):\n *\n * { \"implements\": [\"@scope/checkout-api\"], \"uses\": [\"@scope/payments-api\"] }\n *\n * Classification (from webpieces.config.json globs):\n * - api project: root matches `apiProjectPaths` (e.g. \"libraries/apis/*\").\n * - service: root matches `servicePaths` (e.g. \"services/*\") AND is not an\n * api project (api classification wins, so the lists may overlap\n * harmlessly). Every service must have a service-contract.json.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { isPathExcluded } from '@webpieces/rules-config';\nimport { toError } from '../toError';\n\nexport const SERVICE_CONTRACT_FILENAME = 'service-contract.json';\n\n/** Parsed `service-contract.json` contents (api package names). */\nexport interface ServiceContract {\n implements: string[];\n uses: string[];\n}\n\n/** Everything we know about one workspace project for runtime analysis. */\nexport interface ProjectInfo {\n name: string;\n root: string;\n packageName: string | null;\n isApi: boolean;\n isService: boolean;\n /** Workspace project names this project depends on (from the Nx graph). */\n deps: string[];\n}\n\n/** The whole-workspace model used by the generator and validators. */\nexport interface WorkspaceModel {\n projects: Map<string, ProjectInfo>;\n byPackageName: Map<string, string>;\n}\n\n/** Result of mapping package names to workspace projects. */\nexport interface ResolvedNames {\n projects: string[];\n unknown: string[];\n}\n\n/** Minimal shapes for the JSON files we parse. */\ninterface PackageJsonShape {\n name?: string;\n}\ninterface ServiceContractShape {\n implements?: string[];\n uses?: string[];\n}\n/** One Nx project-graph dependency edge (the slice we use). */\ninterface GraphEdge {\n target: string;\n}\n\n/** True when a project root matches one of the given globs (segment/glob/dir-prefix). */\nfunction matchesGlobs(root: string, globs: string[]): boolean {\n if (globs.length === 0) return false;\n return isPathExcluded(root, globs);\n}\n\nfunction readPackageName(workspaceRoot: string, projectRoot: string): string | null {\n const pkgPath = path.join(workspaceRoot, projectRoot, 'package.json');\n if (!fs.existsSync(pkgPath)) return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(pkgPath, 'utf-8');\n const parsed = JSON.parse(raw) as PackageJsonShape;\n return typeof parsed.name === 'string' ? parsed.name : null;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nfunction collectDeps(\n name: string,\n dependencies: Record<string, GraphEdge[]>,\n workspaceNames: Set<string>,\n): string[] {\n const deps: string[] = [];\n for (const edge of dependencies[name] ?? []) {\n if (edge.target !== name && workspaceNames.has(edge.target)) {\n deps.push(edge.target);\n }\n }\n return Array.from(new Set(deps)).sort();\n}\n\n/**\n * Build the workspace model from the Nx project graph. Dependency edges come\n * from Nx (imports + package.json), the same source dependencies.json uses, so\n * this works whether or not a service has its own package.json.\n */\nexport async function buildWorkspaceModel(\n workspaceRoot: string,\n apiProjectPaths: string[],\n servicePaths: string[],\n): Promise<WorkspaceModel> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const projects = new Map<string, ProjectInfo>();\n const byPackageName = new Map<string, string>();\n const workspaceNames = new Set(Object.keys(projectGraph.nodes));\n\n for (const [name, cfg] of Object.entries(projectsConfig.projects)) {\n const root = cfg.root;\n if (root === '' || root === '.') continue;\n\n const packageName = readPackageName(workspaceRoot, root);\n const isApi = matchesGlobs(root, apiProjectPaths);\n projects.set(name, {\n name,\n root,\n packageName,\n isApi,\n // api classification wins, so servicePaths and apiProjectPaths may overlap.\n isService: !isApi && matchesGlobs(root, servicePaths),\n deps: collectDeps(name, projectGraph.dependencies, workspaceNames),\n });\n if (packageName) byPackageName.set(packageName, name);\n }\n\n return { projects, byPackageName };\n}\n\n/** Read and normalize a project's `service-contract.json`, or null if it has none. */\nexport function readServiceContract(workspaceRoot: string, projectRoot: string): ServiceContract | null {\n const fullPath = path.join(workspaceRoot, projectRoot, SERVICE_CONTRACT_FILENAME);\n if (!fs.existsSync(fullPath)) return null;\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(fullPath, 'utf-8');\n const parsed = JSON.parse(raw) as ServiceContractShape;\n return {\n implements: Array.isArray(parsed.implements) ? parsed.implements : [],\n uses: Array.isArray(parsed.uses) ? parsed.uses : [],\n };\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to parse ${projectRoot}/${SERVICE_CONTRACT_FILENAME}: ${error.message}`);\n }\n}\n\n/**\n * Map a list of api package names (from a service contract) to workspace project\n * names. Unknown names (not a workspace package) are returned in `unknown`.\n */\nexport function resolvePackageNames(\n model: WorkspaceModel,\n packageNames: string[],\n): ResolvedNames {\n const resolved: string[] = [];\n const unknown: string[] = [];\n for (const pkg of packageNames) {\n const project = model.byPackageName.get(pkg);\n if (project) resolved.push(project);\n else unknown.push(pkg);\n }\n return { projects: resolved, unknown };\n}\n"]}
1
+ {"version":3,"file":"runtime-markers.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-markers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime Markers\n *\n * Reads the per-service `service-contract.json` files and builds a workspace\n * model used to assemble the runtime microservice graph and to validate each\n * service independently.\n *\n * `service-contract.json` (at a service's project root) declares, at api-PROJECT\n * granularity (by package name), which api projects a service IMPLEMENTS (serves)\n * and which it USES (calls as a client):\n *\n * { \"implements\": [\"@scope/checkout-api\"], \"uses\": [\"@scope/payments-api\"] }\n *\n * Classification (from webpieces.config.json globs):\n * - api project: root matches `apiProjectPaths` (e.g. \"libraries/apis/*\").\n * - service: root matches `servicePaths` (e.g. \"services/*\") AND is not an\n * api project (api classification wins, so the lists may overlap\n * harmlessly). Every service must have a service-contract.json.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { isPathExcluded } from '@webpieces/rules-config';\nimport { toError } from '../toError';\n\nexport const SERVICE_CONTRACT_FILENAME = 'service-contract.json';\n\n/** Parsed `service-contract.json` contents (api package names). */\nexport interface ServiceContract {\n implements: string[];\n uses: string[];\n}\n\n/** Everything we know about one workspace project for runtime analysis. */\nexport interface ProjectInfo {\n name: string;\n root: string;\n packageName: string | null;\n isApi: boolean;\n isService: boolean;\n /** Workspace project names this project depends on (from the Nx graph). */\n deps: string[];\n}\n\n/** The whole-workspace model used by the generator and validators. */\nexport interface WorkspaceModel {\n projects: Map<string, ProjectInfo>;\n byPackageName: Map<string, string>;\n}\n\n/** Result of mapping package names to workspace projects. */\nexport interface ResolvedNames {\n projects: string[];\n unknown: string[];\n}\n\n/** Minimal shapes for the JSON files we parse. */\ninterface PackageJsonShape {\n name?: string;\n}\ninterface ServiceContractShape {\n implements?: string[];\n uses?: string[];\n}\n/** One Nx project-graph dependency edge (the slice we use). */\ninterface GraphEdge {\n target: string;\n}\n\n/** True when a project root matches one of the given globs (segment/glob/dir-prefix). */\nfunction matchesGlobs(root: string, globs: string[]): boolean {\n if (globs.length === 0) return false;\n return isPathExcluded(root, globs);\n}\n\nfunction readPackageName(workspaceRoot: string, projectRoot: string): string | null {\n const pkgPath = path.join(workspaceRoot, projectRoot, 'package.json');\n if (!fs.existsSync(pkgPath)) return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(pkgPath, 'utf-8');\n const parsed = JSON.parse(raw) as PackageJsonShape;\n return typeof parsed.name === 'string' ? parsed.name : null;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nfunction collectDeps(\n name: string,\n dependencies: Record<string, GraphEdge[]>,\n workspaceNames: Set<string>,\n): string[] {\n const deps: string[] = [];\n for (const edge of dependencies[name] ?? []) {\n if (edge.target !== name && workspaceNames.has(edge.target)) {\n deps.push(edge.target);\n }\n }\n return Array.from(new Set(deps)).sort();\n}\n\n/**\n * Build the workspace model from the Nx project graph. Dependency edges come\n * from Nx (imports + package.json), the same source dependencies.json uses, so\n * this works whether or not a service has its own package.json.\n */\nexport async function buildWorkspaceModel(\n workspaceRoot: string,\n apiProjectPaths: string[],\n servicePaths: string[],\n): Promise<WorkspaceModel> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const projects = new Map<string, ProjectInfo>();\n const byPackageName = new Map<string, string>();\n const workspaceNames = new Set(Object.keys(projectGraph.nodes));\n\n for (const [name, cfg] of Object.entries(projectsConfig.projects)) {\n const root = cfg.root;\n if (root === '' || root === '.') continue;\n\n const packageName = readPackageName(workspaceRoot, root);\n const isApi = matchesGlobs(root, apiProjectPaths);\n projects.set(name, {\n name,\n root,\n packageName,\n isApi,\n // api classification wins, so servicePaths and apiProjectPaths may overlap.\n isService: !isApi && matchesGlobs(root, servicePaths),\n deps: collectDeps(name, projectGraph.dependencies, workspaceNames),\n });\n if (packageName) byPackageName.set(packageName, name);\n }\n\n return { projects, byPackageName };\n}\n\n/** Read and normalize a project's `service-contract.json`, or null if it has none. */\nexport function readServiceContract(workspaceRoot: string, projectRoot: string): ServiceContract | null {\n const fullPath = path.join(workspaceRoot, projectRoot, SERVICE_CONTRACT_FILENAME);\n if (!fs.existsSync(fullPath)) return null;\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(fullPath, 'utf-8');\n const parsed = JSON.parse(raw) as ServiceContractShape;\n return {\n implements: Array.isArray(parsed.implements) ? parsed.implements : [],\n uses: Array.isArray(parsed.uses) ? parsed.uses : [],\n };\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to parse ${projectRoot}/${SERVICE_CONTRACT_FILENAME}`, { cause: error });\n }\n}\n\n/**\n * Map a list of api package names (from a service contract) to workspace project\n * names. Unknown names (not a workspace package) are returned in `unknown`.\n */\nexport function resolvePackageNames(\n model: WorkspaceModel,\n packageNames: string[],\n): ResolvedNames {\n const resolved: string[] = [];\n const unknown: string[] = [];\n for (const pkg of packageNames) {\n const project = model.byPackageName.get(pkg);\n if (project) resolved.push(project);\n else unknown.push(pkg);\n }\n return { projects: resolved, unknown };\n}\n"]}