@webpieces/nx-webpieces-rules 0.4.464 → 0.4.466

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.
@@ -1 +1 @@
1
- {"version":3,"file":"package-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/package-validator.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AA0PH,0EAuCC;;AA/RD,+CAAyB;AACzB,mDAA6B;AAC7B,uCAGoB;AAgCpB;;;GAGG;AACH,SAAS,mBAAmB,CAAC,aAAqB,EAAE,WAAmB;IACnE,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAE9E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,CAAC,qDAAqD;IACtE,CAAC;IAED,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1E,MAAM,IAAI,GAAa,EAAE,CAAC;QAE1B,6CAA6C;QAC7C,KAAK,MAAM,OAAO,IAAI,CAAC,cAAc,EAAE,kBAAkB,CAAC,EAAE,CAAC;YACzD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC1C,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACvB,CAAC;YACL,CAAC;QACL,CAAC;QAED,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6BAA6B;QAC7B,KAAK,GAAG,CAAC;QACT,OAAO,CAAC,IAAI,CAAC,kCAAkC,eAAe,EAAE,CAAC,CAAC;QAClE,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAS,wBAAwB,CAC7B,aAAqB;AACrB,sGAAsG;AACtG,cAAmB;IAEnB,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEtC,oFAAoF;IACpF,KAAK,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAM,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/E,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QAC9E,IAAI,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YACjC,8DAA8D;YAC9D,IAAI,CAAC;gBACD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;gBAC1E,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC;oBACnB,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;gBAC3C,CAAC;YACL,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACpB,6BAA6B;gBAC7B,KAAK,GAAG,CAAC;gBACT,sBAAsB;YAC1B,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAC;AACf,CAAC;AAwBD;;;;;;;;;GASG;AACH,SAAS,wBAAwB,CAC7B,WAAmB,EACnB,KAAiC;IAEjC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,KAAK,GAAG,CAAC,WAAW,CAAC,CAAC;IAC5B,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACjB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAQD,SAAS,YAAY,CACjB,eAAyB,EACzB,KAAiB,EACjB,iBAA8B,EAC9B,gBAAqC,EACrC,gBAAqC;IAErC,MAAM,oBAAoB,GAAa,EAAE,CAAC;IAC1C,KAAK,MAAM,cAAc,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAC3C,MAAM,cAAc,GAAG,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,cAAc,CAAC;QAC9E,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAC5C,oBAAoB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9C,CAAC;IACL,CAAC;IAED,2EAA2E;IAC3E,0EAA0E;IAC1E,qEAAqE;IACrE,MAAM,kBAAkB,GAAa,EAAE,CAAC;IACxC,MAAM,kBAAkB,GAAa,EAAE,CAAC;IACxC,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,MAAM,cAAc,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjD,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;YAC/B,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,SAAS;QACb,CAAC;QACD,IAAI,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC;YAAE,SAAS;QACvD,IAAI,iBAAiB,CAAC,GAAG,CAAC,cAAc,CAAC;YAAE,SAAS;QACpD,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAC5E,CAAC;AAED,SAAS,qBAAqB,CAC1B,WAAmB,EACnB,KAAiB,EACjB,WAAmB,EACnB,eAAyB,EACzB,KAAiC,EACjC,gBAAqC,EACrC,gBAAqC;IAErC,MAAM,iBAAiB,GAAG,wBAAwB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACvE,MAAM,cAAc,GAAG,YAAY,CAC/B,eAAe,EACf,KAAK,EACL,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,CACnB,CAAC;IAEF,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,cAAc,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjD,MAAM,CAAC,IAAI,CACP,WAAW,WAAW,KAAK,WAAW,2CAA2C,cAAc,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAC/H,+CAA+C,CACtD,CAAC;IACN,CAAC;IAED,sFAAsF;IACtF,uFAAuF;IACvF,gFAAgF;IAChF,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,QAAQ,IAAI,cAAc,CAAC,kBAAkB,EAAE,CAAC;QACvD,MAAM,YAAY,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpD,QAAQ,CAAC,IAAI,CACT,WAAW,WAAW,KAAK,WAAW,uBAAuB,QAAQ,4CAA4C,WAAW,MAAM,YAAY,KAAK;YAC/I,6GAA6G,CACpH,CAAC;IACN,CAAC;IAED,qFAAqF;IACrF,MAAM,KAAK,GAAG,cAAc,CAAC,oBAAoB,CAAC,MAAM,KAAK,CAAC,CAAC;IAC/D,OAAO;QACH,MAAM,EAAE;YACJ,OAAO,EAAE,WAAW;YACpB,KAAK;YACL,oBAAoB,EAAE,cAAc,CAAC,oBAAoB;YACzD,kBAAkB,EAAE,cAAc,CAAC,kBAAkB;SACxD;QACD,MAAM;QACN,QAAQ;KACX,CAAC;AACN,CAAC;AAEM,KAAK,UAAU,+BAA+B,CACjD,KAAiC,EACjC,aAAqB;IAErB,MAAM,YAAY,GAAG,MAAM,IAAA,gCAAuB,GAAE,CAAC;IACrD,MAAM,cAAc,GAAG,IAAA,kDAAyC,EAAC,YAAY,CAAC,CAAC;IAE/E,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACjF,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACnD,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,gBAAgB,CAAC,OAAO,EAAE,EAAE,CAAC;QAC3D,gBAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,cAAc,GAA8B,EAAE,CAAC;IAErD,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,MAAM,aAAa,GAAG,cAAc,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC3D,IAAI,CAAC,aAAa;YAAE,SAAS;QAE7B,MAAM,eAAe,GAAG,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;QAC/E,IAAI,eAAe,KAAK,IAAI;YAAE,SAAS;QAEvC,MAAM,UAAU,GAAG,qBAAqB,CACpC,WAAW,EACX,KAAK,EACL,aAAa,CAAC,IAAI,EAClB,eAAe,EACf,KAAK,EACL,gBAAgB,EAChB,gBAAgB,CACnB,CAAC;QACF,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AAC5E,CAAC","sourcesContent":["/**\n * Package Validator\n *\n * Validates that package.json dependencies match the project.json build.dependsOn\n * This ensures the two sources of truth don't drift apart.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n createProjectGraphAsync,\n readProjectsConfigurationFromProjectGraph,\n} from '@nx/devkit';\nimport { toError } from '../toError';\n\n/**\n * Validation result for a single project\n */\nexport interface ProjectValidationResult {\n project: string;\n valid: boolean;\n missingInPackageJson: string[];\n extraInPackageJson: string[];\n}\n\n/**\n * Overall validation result\n *\n * `errors` fail the build; their only fix is ADDITIVE (\"add to package.json\"), so they can\n * never push a user toward removing a runtime-required dependency.\n *\n * `warnings` never fail the build. Workspace deps in package.json that the architecture graph\n * can't reach are reported here, NOT as errors: a transitively-reachable or even unreachable\n * entry can still be a real runtime dependency (e.g. a peerDependency or a generated client\n * that nx's import analysis doesn't traverse). Erroring on these is the \"runtime-validity trap\"\n * that previously forced a bad package.json edit — so we only warn.\n */\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n projectResults: ProjectValidationResult[];\n}\n\n/**\n * Read package.json dependencies for a project\n * Returns null if package.json doesn't exist (apps often don't have one)\n */\nfunction readPackageJsonDeps(workspaceRoot: string, projectRoot: string): string[] | null {\n const packageJsonPath = path.join(workspaceRoot, projectRoot, 'package.json');\n\n if (!fs.existsSync(packageJsonPath)) {\n return null; // No package.json - skip validation for this project\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n const deps: string[] = [];\n\n // Collect ALL dependencies from package.json\n for (const depType of ['dependencies', 'peerDependencies']) {\n const depObj = packageJson[depType] || {};\n for (const depName of Object.keys(depObj)) {\n if (!deps.includes(depName)) {\n deps.push(depName);\n }\n }\n }\n\n return deps.sort();\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n console.warn(`Could not read package.json at ${packageJsonPath}`);\n return [];\n }\n}\n\n/**\n * Build map of project names to their package names\n * e.g., \"core-util\" → \"@webpieces/core-util\"\n */\nfunction buildProjectToPackageMap(\n workspaceRoot: string,\n // webpieces-disable no-any-unknown -- Nx devkit projectsConfig type is dynamic and not strongly typed\n projectsConfig: any\n): Map<string, string> {\n const map = new Map<string, string>();\n\n // webpieces-disable no-any-unknown -- Nx devkit projects config entries are untyped\n for (const [projectName, config] of Object.entries<any>(projectsConfig.projects)) {\n const packageJsonPath = path.join(workspaceRoot, config.root, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n if (packageJson.name) {\n map.set(projectName, packageJson.name);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n // Ignore parse errors\n }\n }\n }\n\n return map;\n}\n\n/**\n * Validate that package.json dependencies match the dependency graph\n *\n * For each project in the graph:\n * - Check that all graph dependencies exist in package.json\n * - Maps project names to package names for accurate comparison\n *\n * @param graph - Enhanced graph with project dependencies (uses project names)\n * @param workspaceRoot - Absolute path to workspace root\n * @returns Validation result with errors if any\n */\ninterface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\ninterface DepClassification {\n missingInPackageJson: string[];\n extraInPackageJson: string[];\n extraWorkspaceDeps: string[];\n}\n\n/**\n * Compute the transitive closure of a project's dependencies in the graph.\n * Example: server → [core-meta, http-server]; transitive closure includes\n * http-server and everything http-server reaches (http-routing,\n * core-context, core-util, http-api).\n *\n * Used to allow package.json entries for transitive deps (a legitimate pattern:\n * npm install brings the whole dependency tree, so a consumer may list any\n * reachable package directly).\n */\nfunction computeTransitiveClosure(\n projectName: string,\n graph: Record<string, GraphEntry>\n): Set<string> {\n const closure = new Set<string>();\n const stack = [projectName];\n while (stack.length > 0) {\n const current = stack.pop()!;\n const entry = graph[current];\n if (!entry) continue;\n for (const dep of entry.dependsOn) {\n if (!closure.has(dep)) {\n closure.add(dep);\n stack.push(dep);\n }\n }\n }\n return closure;\n}\n\ninterface SingleProjectValidation {\n result: ProjectValidationResult;\n errors: string[];\n warnings: string[];\n}\n\nfunction classifyDeps(\n packageJsonDeps: string[],\n entry: GraphEntry,\n transitiveClosure: Set<string>,\n projectToPackage: Map<string, string>,\n packageToProject: Map<string, string>\n): DepClassification {\n const missingInPackageJson: string[] = [];\n for (const depProjectName of entry.dependsOn) {\n const depPackageName = projectToPackage.get(depProjectName) || depProjectName;\n if (!packageJsonDeps.includes(depPackageName)) {\n missingInPackageJson.push(depProjectName);\n }\n }\n\n // Workspace extras are OK if reachable via transitive closure (matches the\n // ESLint enforce-architecture rule which also allows transitive imports).\n // Only flag extras that are NOT reachable at all — real graph drift.\n const extraInPackageJson: string[] = [];\n const extraWorkspaceDeps: string[] = [];\n for (const dep of packageJsonDeps) {\n const depProjectName = packageToProject.get(dep);\n if (depProjectName === undefined) {\n extraInPackageJson.push(dep);\n continue;\n }\n if (entry.dependsOn.includes(depProjectName)) continue;\n if (transitiveClosure.has(depProjectName)) continue;\n extraWorkspaceDeps.push(dep);\n }\n\n return { missingInPackageJson, extraInPackageJson, extraWorkspaceDeps };\n}\n\nfunction validateSingleProject(\n projectName: string,\n entry: GraphEntry,\n projectRoot: string,\n packageJsonDeps: string[],\n graph: Record<string, GraphEntry>,\n projectToPackage: Map<string, string>,\n packageToProject: Map<string, string>\n): SingleProjectValidation {\n const transitiveClosure = computeTransitiveClosure(projectName, graph);\n const classification = classifyDeps(\n packageJsonDeps,\n entry,\n transitiveClosure,\n projectToPackage,\n packageToProject\n );\n\n const errors: string[] = [];\n if (classification.missingInPackageJson.length > 0) {\n errors.push(\n `Project ${projectName} (${projectRoot}/package.json) is missing dependencies: ${classification.missingInPackageJson.join(', ')}\\n` +\n ` Fix: Add these to package.json dependencies`\n );\n }\n\n // Unreachable workspace extras are WARN-ONLY: they may be real runtime deps that nx's\n // import analysis can't see (peerDependency / generated client). Never error — that is\n // the runtime-validity trap. We surface them so genuine drift is still visible.\n const warnings: string[] = [];\n for (const extraPkg of classification.extraWorkspaceDeps) {\n const extraProject = packageToProject.get(extraPkg);\n warnings.push(\n `Project ${projectName} (${projectRoot}/package.json) has \"${extraPkg}\" but the architecture graph has no path ${projectName} → ${extraProject}.\\n` +\n ` This is allowed (it may be a runtime-only/peer dependency). If it is genuinely unused, you may remove it.`\n );\n }\n\n // extraWorkspaceDeps do NOT affect validity — only missing (additive-fix) errors do.\n const valid = classification.missingInPackageJson.length === 0;\n return {\n result: {\n project: projectName,\n valid,\n missingInPackageJson: classification.missingInPackageJson,\n extraInPackageJson: classification.extraInPackageJson,\n },\n errors,\n warnings,\n };\n}\n\nexport async function validatePackageJsonDependencies(\n graph: Record<string, GraphEntry>,\n workspaceRoot: string\n): Promise<ValidationResult> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const projectToPackage = buildProjectToPackageMap(workspaceRoot, projectsConfig);\n const packageToProject = new Map<string, string>();\n for (const [projName, pkgName] of projectToPackage.entries()) {\n packageToProject.set(pkgName, projName);\n }\n\n const errors: string[] = [];\n const warnings: string[] = [];\n const projectResults: ProjectValidationResult[] = [];\n\n for (const [projectName, entry] of Object.entries(graph)) {\n const projectConfig = projectsConfig.projects[projectName];\n if (!projectConfig) continue;\n\n const packageJsonDeps = readPackageJsonDeps(workspaceRoot, projectConfig.root);\n if (packageJsonDeps === null) continue;\n\n const validation = validateSingleProject(\n projectName,\n entry,\n projectConfig.root,\n packageJsonDeps,\n graph,\n projectToPackage,\n packageToProject\n );\n projectResults.push(validation.result);\n errors.push(...validation.errors);\n warnings.push(...validation.warnings);\n }\n\n return { valid: errors.length === 0, errors, warnings, projectResults };\n}\n"]}
1
+ {"version":3,"file":"package-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/package-validator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Package Validator\n *\n * Validates that package.json dependencies match the architecture graph nx derived\n * from the source, and — critically — that each dependency is declared in the RIGHT\n * SECTION of package.json:\n *\n * - reached from production source → `dependencies` (or `peerDependencies`)\n * - reached ONLY from test/dev files → `devDependencies`\n *\n * WHY the section matters: production images are built with\n * `pnpm --filter=<svc> deploy --prod`, which installs exactly the `dependencies`\n * closure. A test-support package parked in `dependencies` therefore ships test\n * machinery (auth-bypass hooks, canned credentials, fake datastores) into the\n * production container. Before this validator understood `devDependencies`, moving\n * such a package to its correct home FAILED the build — the tool enforced the\n * insecure layout. Now `devDependencies` is the required home for test-only deps,\n * and listing one in `dependencies` is itself a violation.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n createProjectGraphAsync,\n readProjectsConfigurationFromProjectGraph,\n} from '@nx/devkit';\nimport { DepUsage, DepUsageScanner } from './dep-usage-scanner';\nimport { toError } from '../toError';\n\n/**\n * How hard to push back when a test-only package sits in `dependencies`\n * (i.e. inside the production deploy closure).\n *\n * - 'error' (default): fails the build. This is the guardrail the security bug asked for.\n * - 'warn': reports it without failing — the migration setting for a repo that needs a\n * few releases to clean up its package.json files.\n * - 'off': skip the check entirely.\n *\n * Missing deps, and production imports declared only in `devDependencies`, ALWAYS error:\n * their fix is unambiguous and the alternative is a broken runtime.\n */\nexport type TestOnlyDepMode = 'error' | 'warn' | 'off';\n\n/**\n * Options for {@link validatePackageJsonDependencies}. Data-only (CLAUDE.md: data\n * structures are classes, never anonymous object literals).\n */\nexport class PackageValidatorOptions {\n testOnlyDepMode: TestOnlyDepMode;\n\n constructor(testOnlyDepMode: TestOnlyDepMode = 'error') {\n this.testOnlyDepMode = testOnlyDepMode;\n }\n}\n\n/**\n * Validation result for a single project\n */\nexport class ProjectValidationResult {\n project: string;\n valid: boolean;\n missingInPackageJson: string[];\n extraInPackageJson: string[];\n /** Graph deps that are test-only but declared in `dependencies` (production closure). */\n testOnlyInProdDependencies: string[];\n\n constructor(\n project: string,\n valid: boolean,\n missingInPackageJson: string[],\n extraInPackageJson: string[],\n testOnlyInProdDependencies: string[]\n ) {\n this.project = project;\n this.valid = valid;\n this.missingInPackageJson = missingInPackageJson;\n this.extraInPackageJson = extraInPackageJson;\n this.testOnlyInProdDependencies = testOnlyInProdDependencies;\n }\n}\n\n/**\n * Overall validation result\n *\n * `errors` fail the build. Every error's fix is either ADDITIVE (\"add it to package.json\")\n * or a MOVE between sections (\"it belongs in devDependencies\") — never \"delete a\n * dependency\", so no error can push a user toward removing a runtime-required package.\n *\n * `warnings` never fail the build. Workspace deps in package.json that the architecture\n * graph can't reach are reported here, NOT as errors: a transitively-reachable or even\n * unreachable entry can still be a real runtime dependency (e.g. a peerDependency or a\n * generated client that nx's import analysis doesn't traverse). Erroring on these is the\n * \"runtime-validity trap\" that previously forced a bad package.json edit — so we only warn.\n */\nexport class ValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n projectResults: ProjectValidationResult[];\n\n constructor(\n valid: boolean,\n errors: string[],\n warnings: string[],\n projectResults: ProjectValidationResult[]\n ) {\n this.valid = valid;\n this.errors = errors;\n this.warnings = warnings;\n this.projectResults = projectResults;\n }\n}\n\n/**\n * The three package.json sections that matter, kept apart so the validator can say\n * WHICH one a package belongs in (the old code merged them and lost that information —\n * and never even read devDependencies).\n */\nexport class DeclaredDeps {\n dependencies: string[];\n devDependencies: string[];\n peerDependencies: string[];\n\n constructor(dependencies: string[], devDependencies: string[], peerDependencies: string[]) {\n this.dependencies = dependencies;\n this.devDependencies = devDependencies;\n this.peerDependencies = peerDependencies;\n }\n\n /** Every declared package name, deduped and sorted (all three sections). */\n all(): string[] {\n const merged = new Set<string>();\n for (const name of this.dependencies) merged.add(name);\n for (const name of this.devDependencies) merged.add(name);\n for (const name of this.peerDependencies) merged.add(name);\n return Array.from(merged).sort();\n }\n\n /** Declared somewhere that survives `pnpm deploy --prod`. */\n isProductionDeclared(packageName: string): boolean {\n return (\n this.dependencies.includes(packageName) ||\n this.peerDependencies.includes(packageName)\n );\n }\n\n isDevDeclared(packageName: string): boolean {\n return this.devDependencies.includes(packageName);\n }\n\n isDeclared(packageName: string): boolean {\n return this.isProductionDeclared(packageName) || this.isDevDeclared(packageName);\n }\n}\n\n/**\n * Graph shape produced by graph-sorter (an input contract we never construct here).\n */\ninterface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\n/**\n * Per-project classification of graph deps against what package.json declares.\n */\nclass DepClassification {\n /** Production-reached deps absent from `dependencies`/`peerDependencies`. */\n missingInPackageJson: string[] = [];\n /** Production-reached deps declared ONLY in `devDependencies` (runtime would break). */\n prodDepsOnlyInDev: string[] = [];\n /** Test-only deps declared in no section at all. */\n missingTestOnlyDeps: string[] = [];\n /** Test-only deps sitting in `dependencies` — i.e. shipped to production. */\n testOnlyInProdDependencies: string[] = [];\n /** Non-workspace (third-party) package.json entries — informational only. */\n extraInPackageJson: string[] = [];\n /** Workspace entries the graph cannot reach at all — warn-only drift. */\n extraWorkspaceDeps: string[] = [];\n}\n\nclass SingleProjectValidation {\n result: ProjectValidationResult;\n errors: string[];\n warnings: string[];\n\n constructor(result: ProjectValidationResult, errors: string[], warnings: string[]) {\n this.result = result;\n this.errors = errors;\n this.warnings = warnings;\n }\n}\n\n/**\n * The per-workspace lookups a single-project validation needs, passed as one object\n * so method signatures stay readable.\n */\nclass ValidationContext {\n graph: Record<string, GraphEntry>;\n projectToPackage: Map<string, string>;\n packageToProject: Map<string, string>;\n options: PackageValidatorOptions;\n\n constructor(\n graph: Record<string, GraphEntry>,\n projectToPackage: Map<string, string>,\n packageToProject: Map<string, string>,\n options: PackageValidatorOptions\n ) {\n this.graph = graph;\n this.projectToPackage = projectToPackage;\n this.packageToProject = packageToProject;\n this.options = options;\n }\n}\n\nexport class PackageValidator {\n private readonly scanner = new DepUsageScanner();\n\n /**\n * Read the three dependency sections of a project's package.json.\n * Returns null when there is no package.json (apps often have none) so the caller\n * can skip the project entirely.\n */\n readDeclaredDeps(workspaceRoot: string, projectRoot: string): DeclaredDeps | null {\n const packageJsonPath = path.join(workspaceRoot, projectRoot, 'package.json');\n if (!fs.existsSync(packageJsonPath)) {\n return null;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n return new DeclaredDeps(\n this.namesOf(packageJson, 'dependencies'),\n this.namesOf(packageJson, 'devDependencies'),\n this.namesOf(packageJson, 'peerDependencies')\n );\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(`Could not read package.json at ${packageJsonPath}: ${error.message}`);\n return new DeclaredDeps([], [], []);\n }\n }\n\n // webpieces-disable no-any-unknown -- parsed JSON is inherently untyped\n private namesOf(packageJson: any, section: string): string[] {\n const depObj = packageJson[section] || {};\n return Object.keys(depObj).sort();\n }\n\n /**\n * Build map of project names to their package names\n * e.g., \"core-util\" → \"@webpieces/core-util\"\n */\n buildProjectToPackageMap(\n workspaceRoot: string,\n // webpieces-disable no-any-unknown -- Nx devkit projectsConfig type is dynamic and not strongly typed\n projectsConfig: any\n ): Map<string, string> {\n const map = new Map<string, string>();\n\n // webpieces-disable no-any-unknown -- Nx devkit projects config entries are untyped\n for (const configEntry of Object.entries<any>(projectsConfig.projects)) {\n const projectName = configEntry[0];\n const packageJsonPath = path.join(workspaceRoot, configEntry[1].root, 'package.json');\n if (!fs.existsSync(packageJsonPath)) continue;\n const packageName = this.readPackageName(packageJsonPath);\n if (packageName !== null) map.set(projectName, packageName);\n }\n\n return map;\n }\n\n private readPackageName(packageJsonPath: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n return packageJson.name || null;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(`Could not parse ${packageJsonPath}: ${error.message}`);\n return null;\n }\n }\n\n /**\n * Compute the transitive closure of a project's dependencies in the graph.\n * Example: server → [core-meta, http-server]; the closure includes http-server and\n * everything http-server reaches.\n *\n * Used to allow package.json entries for transitive deps (a legitimate pattern:\n * npm install brings the whole dependency tree, so a consumer may list any reachable\n * package directly).\n */\n computeTransitiveClosure(\n projectName: string,\n graph: Record<string, GraphEntry>\n ): Set<string> {\n const closure = new Set<string>();\n const stack = [projectName];\n while (stack.length > 0) {\n const current = stack.pop()!;\n const entry = graph[current];\n if (!entry) continue;\n for (const dep of entry.dependsOn) {\n if (!closure.has(dep)) {\n closure.add(dep);\n stack.push(dep);\n }\n }\n }\n return closure;\n }\n\n /**\n * Split a project's graph deps into \"declared correctly\", \"missing\", and \"declared in\n * the wrong section\", using the import scan to decide which section each dep belongs in.\n */\n classifyDeps(\n declared: DeclaredDeps,\n usage: DepUsage,\n entry: GraphEntry,\n transitiveClosure: Set<string>,\n context: ValidationContext\n ): DepClassification {\n const classification = new DepClassification();\n\n for (const depProjectName of entry.dependsOn) {\n const depPackageName = context.projectToPackage.get(depProjectName) || depProjectName;\n this.classifyOneDep(depProjectName, depPackageName, declared, usage, classification);\n }\n\n // Workspace extras are OK if reachable via transitive closure (matches the ESLint\n // enforce-architecture rule which also allows transitive imports). Only flag extras\n // that are NOT reachable at all — real graph drift.\n for (const dep of declared.all()) {\n const depProjectName = context.packageToProject.get(dep);\n if (depProjectName === undefined) {\n classification.extraInPackageJson.push(dep);\n continue;\n }\n if (entry.dependsOn.includes(depProjectName)) continue;\n if (transitiveClosure.has(depProjectName)) continue;\n classification.extraWorkspaceDeps.push(dep);\n }\n\n return classification;\n }\n\n /**\n * The heart of the fix: which SECTION does this dep belong in?\n *\n * A dep is test-only when the scan saw it imported by test/dev files and by NO\n * production file. Anything else — including a dep we never saw imported at all (it\n * may be loaded reflectively at runtime) — is treated as production, so this can\n * never push a runtime-required package out of `dependencies`.\n */\n private classifyOneDep(\n depProjectName: string,\n depPackageName: string,\n declared: DeclaredDeps,\n usage: DepUsage,\n classification: DepClassification\n ): void {\n if (usage.isTestOnly(depPackageName)) {\n if (!declared.isDeclared(depPackageName)) {\n classification.missingTestOnlyDeps.push(depProjectName);\n return;\n }\n if (declared.dependencies.includes(depPackageName)) {\n classification.testOnlyInProdDependencies.push(depProjectName);\n }\n return;\n }\n\n if (declared.isProductionDeclared(depPackageName)) return;\n if (declared.isDevDeclared(depPackageName)) {\n classification.prodDepsOnlyInDev.push(depProjectName);\n return;\n }\n classification.missingInPackageJson.push(depProjectName);\n }\n\n validateSingleProject(\n projectName: string,\n entry: GraphEntry,\n projectRoot: string,\n declared: DeclaredDeps,\n usage: DepUsage,\n context: ValidationContext\n ): SingleProjectValidation {\n const transitiveClosure = this.computeTransitiveClosure(projectName, context.graph);\n const classification = this.classifyDeps(\n declared,\n usage,\n entry,\n transitiveClosure,\n context\n );\n\n const where = `Project ${projectName} (${projectRoot}/package.json)`;\n const errors = this.buildErrors(where, classification, context.options);\n const warnings = this.buildWarnings(where, projectName, classification, context);\n\n const missing = classification.missingInPackageJson.concat(\n classification.missingTestOnlyDeps\n );\n const result = new ProjectValidationResult(\n projectName,\n errors.length === 0,\n missing,\n classification.extraInPackageJson,\n classification.testOnlyInProdDependencies\n );\n return new SingleProjectValidation(result, errors, warnings);\n }\n\n private buildErrors(\n where: string,\n classification: DepClassification,\n options: PackageValidatorOptions\n ): string[] {\n const errors: string[] = [];\n if (classification.missingInPackageJson.length > 0) {\n errors.push(\n `${where} is missing dependencies: ${classification.missingInPackageJson.join(', ')}\\n` +\n ` These are imported by PRODUCTION source files.\\n` +\n ` Fix: Add them to package.json \"dependencies\"`\n );\n }\n if (classification.missingTestOnlyDeps.length > 0) {\n errors.push(\n `${where} is missing dependencies: ${classification.missingTestOnlyDeps.join(', ')}\\n` +\n ` These are imported ONLY by test/dev files (*.spec.ts, __tests__/, test configs).\\n` +\n ` Fix: Add them to package.json \"devDependencies\" — NOT \"dependencies\", which would ship them to production`\n );\n }\n if (classification.prodDepsOnlyInDev.length > 0) {\n errors.push(\n `${where} declares production imports only in devDependencies: ${classification.prodDepsOnlyInDev.join(', ')}\\n` +\n ` Production source files import them, so \\`pnpm deploy --prod\\` would omit them and the runtime would break.\\n` +\n ` Fix: Move them to package.json \"dependencies\"`\n );\n }\n const testOnlyInProd = classification.testOnlyInProdDependencies;\n if (options.testOnlyDepMode === 'error' && testOnlyInProd.length > 0) {\n errors.push(this.testOnlyInProdMessage(where, testOnlyInProd));\n }\n return errors;\n }\n\n private buildWarnings(\n where: string,\n projectName: string,\n classification: DepClassification,\n context: ValidationContext\n ): string[] {\n const warnings: string[] = [];\n\n const testOnlyInProd = classification.testOnlyInProdDependencies;\n if (context.options.testOnlyDepMode === 'warn' && testOnlyInProd.length > 0) {\n warnings.push(this.testOnlyInProdMessage(where, testOnlyInProd));\n }\n\n // Unreachable workspace extras are WARN-ONLY: they may be real runtime deps that nx's\n // import analysis can't see (peerDependency / generated client). Never error — that is\n // the runtime-validity trap. We surface them so genuine drift is still visible.\n for (const extraPkg of classification.extraWorkspaceDeps) {\n const extraProject = context.packageToProject.get(extraPkg);\n warnings.push(\n `${where} has \"${extraPkg}\" but the architecture graph has no path ${projectName} → ${extraProject}.\\n` +\n ` This is allowed (it may be a runtime-only/peer dependency). If it is genuinely unused, you may remove it.`\n );\n }\n return warnings;\n }\n\n private testOnlyInProdMessage(where: string, deps: string[]): string {\n return (\n `${where} lists test-only packages in \"dependencies\": ${deps.join(', ')}\\n` +\n ` No production source file imports them — only test/dev files do — yet \\`pnpm deploy --prod\\`\\n` +\n ` installs the \"dependencies\" closure, so this ships test machinery (fakes, auth-bypass hooks,\\n` +\n ` canned credentials) into the production image.\\n` +\n ` Fix: Move them to package.json \"devDependencies\"`\n );\n }\n\n async validate(\n graph: Record<string, GraphEntry>,\n workspaceRoot: string,\n options: PackageValidatorOptions\n ): Promise<ValidationResult> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const projectToPackage = this.buildProjectToPackageMap(workspaceRoot, projectsConfig);\n const packageToProject = new Map<string, string>();\n for (const pair of projectToPackage.entries()) {\n packageToProject.set(pair[1], pair[0]);\n }\n const context = new ValidationContext(graph, projectToPackage, packageToProject, options);\n\n const errors: string[] = [];\n const warnings: string[] = [];\n const projectResults: ProjectValidationResult[] = [];\n\n for (const graphPair of Object.entries(graph)) {\n const projectName = graphPair[0];\n const entry = graphPair[1];\n const projectConfig = projectsConfig.projects[projectName];\n if (!projectConfig) continue;\n\n const declared = this.readDeclaredDeps(workspaceRoot, projectConfig.root);\n if (declared === null) continue;\n\n const usage = this.scanner.scan(path.join(workspaceRoot, projectConfig.root));\n const validation = this.validateSingleProject(\n projectName,\n entry,\n projectConfig.root,\n declared,\n usage,\n context\n );\n projectResults.push(validation.result);\n errors.push(...validation.errors);\n warnings.push(...validation.warnings);\n }\n\n return new ValidationResult(errors.length === 0, errors, warnings, projectResults);\n }\n}\n\n/**\n * Validate that package.json dependencies cover the dependency graph AND that each dep\n * is declared in the correct section (dependencies vs devDependencies).\n *\n * @param graph - Enhanced graph with project dependencies (uses project names)\n * @param workspaceRoot - Absolute path to workspace root\n * @param options - Strictness of the \"test-only dep in the production closure\" check\n */\n// webpieces-disable no-function-outside-class -- stable module entry point imported by the executor; it only delegates to PackageValidator\nexport async function validatePackageJsonDependencies(\n graph: Record<string, GraphEntry>,\n workspaceRoot: string,\n options: PackageValidatorOptions = new PackageValidatorOptions()\n): Promise<ValidationResult> {\n return new PackageValidator().validate(graph, workspaceRoot, options);\n}\n"]}
@@ -12,15 +12,20 @@
12
12
  * Y and X `implements` api Y. This edge does not exist in the compile-time
13
13
  * dependencies.json (both Z and X only compile-depend on the api library Y).
14
14
  *
15
- * WHICH X is decided by the call site, not by "everyone who implements Y":
16
- * `createRpcClient(Y, new ClientConfig('helper-fsdb'))` names its target, kept as
17
- * `ApiRef.targetService`, and matched against each node's DECLARED `serviceName`
18
- * (project.json metadata.webpieces.serviceName). Fanning an edge out to every
19
- * implementer instead is catastrophic for a company-wide contract registered in a
20
- * shared library it manufactures calls that cannot happen, and cycles that do
21
- * not exist. When a target cannot be resolved the old fan-out still happens, but
22
- * a warning names the call site (see RuntimeGraphReport.warnings): a wrong-but-
23
- * green graph is worse than a failing one, so it must never degrade silently.
15
+ * WHICH X is decided by the call site, not by "everyone who implements Y", in
16
+ * priority order: (1) a literal at the call site —
17
+ * `createRpcClient(Y, new ClientConfig('helper-fsdb'))` kept as
18
+ * `ApiRef.targetService`; else (2) the calling project's declared `callsService`
19
+ * (project.json metadata.webpieces.callsService), the symmetric half of
20
+ * `serviceName` for the shared-library case where the client is built once from a
21
+ * config field so no literal can sit at the call site; else (3) fan-out. A named
22
+ * target from (1) or (2) is matched against each node's DECLARED `serviceName`.
23
+ * Fanning an edge out to every implementer is catastrophic for a company-wide
24
+ * contract registered in a shared library — it manufactures calls that cannot
25
+ * happen, and cycles that do not exist. When a target cannot be resolved the old
26
+ * fan-out still happens, but a warning names the call site (see
27
+ * RuntimeGraphReport.warnings): a wrong-but-green graph is worse than a failing
28
+ * one, so it must never degrade silently.
24
29
  */
25
30
  import type { EnhancedGraph } from './graph-sorter';
26
31
  import type { ApiTransport } from './api-usage/api-relations';
@@ -32,6 +37,13 @@ export interface RuntimeService {
32
37
  * project.json. Absent for a service nothing calls by name (e.g. a browser app).
33
38
  */
34
39
  serviceName?: string;
40
+ /**
41
+ * The service(s) this node's clients call when the call site carries no literal `ClientConfig`,
42
+ * declared in its project.json (metadata.webpieces.callsService). A single name, or an
43
+ * `{ apiClassName: serviceName }` map. Absent when the node declares no target. Mirrors
44
+ * GraphEntry.callsService; it is the CALLING-side counterpart of `serviceName`.
45
+ */
46
+ callsService?: string | Record<string, string>;
35
47
  implements: string[];
36
48
  /**
37
49
  * apiClassName -> the LIBRARY project whose apiRelations declared that implements, for the apis
@@ -13,15 +13,20 @@
13
13
  * Y and X `implements` api Y. This edge does not exist in the compile-time
14
14
  * dependencies.json (both Z and X only compile-depend on the api library Y).
15
15
  *
16
- * WHICH X is decided by the call site, not by "everyone who implements Y":
17
- * `createRpcClient(Y, new ClientConfig('helper-fsdb'))` names its target, kept as
18
- * `ApiRef.targetService`, and matched against each node's DECLARED `serviceName`
19
- * (project.json metadata.webpieces.serviceName). Fanning an edge out to every
20
- * implementer instead is catastrophic for a company-wide contract registered in a
21
- * shared library it manufactures calls that cannot happen, and cycles that do
22
- * not exist. When a target cannot be resolved the old fan-out still happens, but
23
- * a warning names the call site (see RuntimeGraphReport.warnings): a wrong-but-
24
- * green graph is worse than a failing one, so it must never degrade silently.
16
+ * WHICH X is decided by the call site, not by "everyone who implements Y", in
17
+ * priority order: (1) a literal at the call site —
18
+ * `createRpcClient(Y, new ClientConfig('helper-fsdb'))` kept as
19
+ * `ApiRef.targetService`; else (2) the calling project's declared `callsService`
20
+ * (project.json metadata.webpieces.callsService), the symmetric half of
21
+ * `serviceName` for the shared-library case where the client is built once from a
22
+ * config field so no literal can sit at the call site; else (3) fan-out. A named
23
+ * target from (1) or (2) is matched against each node's DECLARED `serviceName`.
24
+ * Fanning an edge out to every implementer is catastrophic for a company-wide
25
+ * contract registered in a shared library — it manufactures calls that cannot
26
+ * happen, and cycles that do not exist. When a target cannot be resolved the old
27
+ * fan-out still happens, but a warning names the call site (see
28
+ * RuntimeGraphReport.warnings): a wrong-but-green graph is worse than a failing
29
+ * one, so it must never degrade silently.
25
30
  */
26
31
  Object.defineProperty(exports, "__esModule", { value: true });
27
32
  exports.RuntimeGraphReport = exports.DEFAULT_RUNTIME_GRAPH_PATH = void 0;
@@ -302,25 +307,61 @@ class RuntimeGraphDeriver {
302
307
  return { edges: this.edgesFromKeys(viaByKey), unresolved: sortUnresolved(unresolved) };
303
308
  }
304
309
  /**
305
- * WHICH implementers this one `uses` reaches. A call site naming its target resolves to exactly
306
- * ONE node; anything else falls back to the historical fan-out the only safe superset — and
307
- * records WHY, so a fanned-out (i.e. possibly fictional) edge can never pass for a derived one.
310
+ * WHICH implementers this one `uses` reaches. Resolution order (most specific wins):
311
+ * 1. a literal `ClientConfig` at the call site (`ref.targetService`)names ONE node;
312
+ * 2. else the calling project's declared `callsService` (project.json) for the shared-library
313
+ * case where the literal cannot sit at the call site, it lives one indirection away in the app;
314
+ * 3. else the historical fan-out — the only safe superset — recording WHY, so a fanned-out (i.e.
315
+ * possibly fictional) edge can never pass for a derived one.
316
+ * A named target (1 or 2) that resolves to no module, or to a module that does not serve the
317
+ * contract, is a PROBLEM (fails the build), never a silent drop.
308
318
  */
309
319
  targetsFor(user, ref, implementers) {
310
- const wanted = ref.targetService;
311
- if (wanted === undefined)
312
- return this.untargetedFanOut(user, ref, implementers);
320
+ const literal = ref.targetService;
321
+ if (literal !== undefined)
322
+ return this.resolveNamedTarget(user, ref, implementers, literal, 'literal');
323
+ const declared = this.callsServiceFor(user, ref.api);
324
+ if (declared !== undefined)
325
+ return this.resolveNamedTarget(user, ref, implementers, declared, 'callsService');
326
+ return this.untargetedFanOut(user, ref, implementers);
327
+ }
328
+ /**
329
+ * The declared `callsService` target for this node's use of `api`, or undefined when none applies.
330
+ * A string declaration aims every untargeted use at one service; a map aims per api-class.
331
+ */
332
+ callsServiceFor(user, api) {
333
+ const declared = this.projects[user]?.callsService;
334
+ if (declared === undefined)
335
+ return undefined;
336
+ if (typeof declared === 'string')
337
+ return declared;
338
+ return declared[api];
339
+ }
340
+ /**
341
+ * Resolve a NAMED target (from a call-site literal or a project-level `callsService`) to the one
342
+ * module that serves it. A name no module answers to, or a module that does not serve the
343
+ * contract, fails the build — the wording names which declaration to fix, `source` deciding it.
344
+ */
345
+ resolveNamedTarget(user, ref, implementers, wanted, source) {
313
346
  const node = this.nodeByServiceName.get(wanted);
347
+ const served = `${implementers.length} module(s) serve this contract (${implementers.join(', ')})`;
314
348
  if (node === undefined) {
315
- this.problems.push(`${user} calls "${ref.api}" at service '${wanted}', but NO module answers to that name. ` +
316
- `${implementers.length} module(s) serve this contract (${implementers.join(', ')}). Either use ` +
317
- `the module name, or declare metadata.webpieces.serviceName: '${wanted}' on the module that ` +
318
- `serves it (and translate any environment prefix in ClientRegistry.setDeriver, not here).`);
349
+ this.problems.push(source === 'callsService'
350
+ ? `${user} declares metadata.webpieces.callsService '${wanted}' in its project.json, but NO ` +
351
+ `module answers to that name. ${served}. Point callsService at a module name, or declare ` +
352
+ `metadata.webpieces.serviceName: '${wanted}' on the module that serves "${ref.api}".`
353
+ : `${user} calls "${ref.api}" at service '${wanted}', but NO module answers to that name. ` +
354
+ `${served}. Either use the module name, or declare metadata.webpieces.serviceName: ` +
355
+ `'${wanted}' on the module that serves it (and translate any environment prefix in ` +
356
+ `ClientRegistry.setDeriver, not here).`);
319
357
  return implementers;
320
358
  }
321
359
  if (!implementers.includes(node)) {
322
- this.problems.push(`${user} calls "${ref.api}" at service '${wanted}' (module ${node}), but ${node} does NOT serve ` +
323
- `that contract ${implementers.join(', ')} do. At runtime that call has nothing to answer it.`);
360
+ const via = source === 'callsService'
361
+ ? `${user} declares callsService '${wanted}' (module ${node})`
362
+ : `${user} calls "${ref.api}" at service '${wanted}' (module ${node})`;
363
+ this.problems.push(`${via}, but ${node} does NOT serve "${ref.api}" — ${implementers.join(', ')} do. At runtime that ` +
364
+ `call has nothing to answer it.`);
324
365
  return implementers;
325
366
  }
326
367
  return [node];
@@ -334,7 +375,9 @@ class RuntimeGraphDeriver {
334
375
  if (implementers.length > 1) {
335
376
  this.warnings.push(`${user} uses "${ref.api}" with no literal client config, and ${implementers.length} services ` +
336
377
  `implement it (${implementers.join(', ')}) — an edge is drawn to EVERY one, so all but one ` +
337
- `are fiction. Name the target: createRpcClient(${ref.api}, new ClientConfig('<serviceName>')).`);
378
+ `are fiction. Name the target: createRpcClient(${ref.api}, new ClientConfig('<serviceName>')); ` +
379
+ `or, when the client is built in a shared library (no literal can sit at the call site), ` +
380
+ `declare metadata.webpieces.callsService: '<serviceName>' on ${user}'s project.json.`);
338
381
  }
339
382
  return implementers;
340
383
  }
@@ -356,6 +399,7 @@ class RuntimeGraphDeriver {
356
399
  const service = {
357
400
  level: 0,
358
401
  serviceName: this.projects[decl.name]?.serviceName,
402
+ callsService: this.projects[decl.name]?.callsService,
359
403
  implements: decl.implementsApis.map((r) => r.api),
360
404
  implementsVia: decl.implementsVia.size > 0 ? sortedRecord(decl.implementsVia) : undefined,
361
405
  // One api used against two services is two refs but ONE api in this list.
@@ -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 * Derives the runtime microservice graph SOLELY from architecture/dependencies.json\n * (the single source of truth): each project's `apiRelations` carry which api\n * classes it implements/uses and their transport (rpc | pubsub). Both\n * `architecture:generate` and `architecture:validate-runtime-architecture` call\n * the SAME `deriveRuntimeGraph`, so the committed graph and the validated graph\n * can never diverge.\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 * WHICH X is decided by the call site, not by \"everyone who implements Y\":\n * `createRpcClient(Y, new ClientConfig('helper-fsdb'))` names its target, kept as\n * `ApiRef.targetService`, and matched against each node's DECLARED `serviceName`\n * (project.json metadata.webpieces.serviceName). Fanning an edge out to every\n * implementer instead is catastrophic for a company-wide contract registered in a\n * shared library — it manufactures calls that cannot happen, and cycles that do\n * not exist. When a target cannot be resolved the old fan-out still happens, but\n * a warning names the call site (see RuntimeGraphReport.warnings): a wrong-but-\n * green graph is worse than a failing one, so it must never degrade silently.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { sortGraphTopologically } from './graph-sorter';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { ApiRef, ApiTransport } from './api-usage/api-relations';\nimport { apiRefKey, sortApiRefs } from './api-usage/api-relations';\nimport { toError } from '../toError';\n\nexport const DEFAULT_RUNTIME_GRAPH_PATH = 'architecture/runtime-dependencies.json';\n\nexport interface RuntimeService {\n level: number;\n /**\n * The name clients address this service by (`new ClientConfig('helper-fsdb')`), declared in its\n * project.json. Absent for a service nothing calls by name (e.g. a browser app).\n */\n serviceName?: string;\n implements: string[];\n /**\n * apiClassName -> the LIBRARY project whose apiRelations declared that implements, for the apis\n * this service serves through an embedded library rather than its own source (e.g. a shared\n * route-registration lib). Answers \"who implements WarmupApi, and where did that come from?\",\n * which previously required walking the dependsOn closure by hand.\n */\n implementsVia?: Record<string, string>;\n uses: string[];\n dependsOn: string[];\n /**\n * When false, this service is hidden from the rendered runtime graph (its\n * node AND every edge touching it are omitted from the HTML/DOT). It stays\n * in runtime-dependencies.json so the data view is complete. Absent means\n * drawn (the default). Mirrors GraphEntry.drawOnGraph from the `drawOnGraph:`\n * nx tag.\n */\n drawOnGraph?: boolean;\n}\n\nexport interface RuntimeApi {\n implementedBy: string[];\n usedBy: string[];\n /** Transport of this API — 'rpc' (direct call) or 'pubsub' (delivered through a queue). */\n type?: ApiTransport;\n /**\n * The api-lib project that OWNS this contract. For a contract nothing in-repo implements, this\n * is the external library the calls leave the repo through (`lib-firestore`, `lib-gmail`), which\n * is what the runtime viz labels its terminal external nodes with.\n */\n owner?: string;\n}\n\nexport interface RuntimeEdge {\n from: string;\n to: string;\n via: string[];\n /**\n * Transport of this edge. 'rpc' → a direct call arrow. 'pubsub' → the producer enqueues and the\n * consumer is delivered later, so the runtime viz draws it as producer → QUEUE → consumer.\n * Edges are split by transport, so every edge is a single kind.\n */\n type?: ApiTransport;\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\ninterface EdgeResult {\n edges: RuntimeEdge[];\n unresolved: RuntimeUnresolved[];\n}\n\n/**\n * The derived graph PLUS everything the derivation had to guess at. `warnings` is deliberately not\n * part of RuntimeGraph: it is not committed data, it is the report that stops a guessed edge from\n * passing for a derived one. Executors print it.\n */\nexport class RuntimeGraphReport {\n constructor(\n public readonly graph: RuntimeGraph,\n /** Human-readable lines naming every call site whose target the graph could not pin down. */\n public readonly warnings: string[],\n /**\n * Call sites that name a target the repo does not contain. Unlike a warning these FAIL the\n * build: the contract IS served in-repo, so the name is a typo or a stale rename, and the\n * only reason it stayed invisible is that the graph quietly fanned the edge out instead.\n * (A call to a service outside the repo never reaches here — nothing in-repo implements its\n * contract, so it is `unresolvedUses`.)\n */\n public readonly problems: string[] = [],\n ) {}\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/** One project's implements/uses at api-CLASS granularity, from dependencies.json apiRelations. */\ninterface ScanDecl {\n name: string;\n implementsApis: ApiRef[];\n usesApis: ApiRef[];\n /** apiClassName -> the embedded LIBRARY project that declared the implements (never the node itself). */\n implementsVia: Map<string, string>;\n}\n\n/**\n * Accumulates one node's effective relations while its dependsOn closure is walked, keeping the\n * PROVENANCE the walk would otherwise throw away: which library contributed an implements, and\n * which api-lib owns each contract.\n */\nclass RelationSink {\n readonly implementsApis: ApiRef[] = [];\n readonly usesApis: ApiRef[] = [];\n readonly implementsVia = new Map<string, string>();\n\n constructor(\n /** The runtime node these relations are attributed to. */\n private readonly node: string,\n ) {}\n\n /** `from` is the project whose apiRelations declared this — the node itself, or a lib it embeds. */\n addImplements(ref: ApiRef, from: string): void {\n this.implementsApis.push(ref);\n // First contributor wins, matching dedupApiRefs' keep-the-first rule on the ref list.\n if (from !== this.node && !this.implementsVia.has(ref.api)) this.implementsVia.set(ref.api, from);\n }\n\n addUses(ref: ApiRef): void {\n this.usesApis.push(ref);\n }\n}\n\n/**\n * Derives the runtime microservice graph from architecture/dependencies.json `apiRelations`:\n * implementers × users per API, split by transport. An rpc edge is a direct call; a pubsub edge flows\n * through a queue (drawn producer → queue → consumer by the visualizer).\n *\n * Nodes are the runnable apps only — role:server / role:client. A library is NEVER a node; its\n * implements/uses (a shared client-factory or route-registration lib) are attributed to the\n * servers/clients that embed it, walked transitively over dependsOn (see collectDecls).\n */\nclass RuntimeGraphDeriver {\n /** apiClassName -> the api-lib project that owns the contract (from the apiRelations key). */\n private readonly apiOwners = new Map<string, string>();\n /** Addressable name -> the runtime node answering to it; how a targeted call resolves. */\n private readonly nodeByServiceName = new Map<string, string>();\n private readonly warnings: string[] = [];\n private readonly problems: string[] = [];\n\n constructor(\n private readonly projects: EnhancedGraph,\n /** Project names tagged drawOnGraph:false — kept in the JSON but flagged so the viz omits them. */\n private readonly hiddenProjects: Set<string>\n ) {\n // A node ALWAYS answers to its own module name, so a repo whose deployed names match its\n // project names needs no declaration at all — and no alias can ever redirect 'ai-chat' away\n // from the ai-chat module. Module names are therefore claimed FIRST and are unshadowable.\n for (const name of Object.keys(projects).sort()) {\n if (this.isNode(name)) this.nodeByServiceName.set(name, name);\n }\n // `serviceName` then adds the alias a repo needs when its deployed name is NOT its module\n // name (helper-svr serving 'helper-portal'). Colliding with a module is a misconfiguration,\n // not a precedence question: the alias is silently unreachable, so say so.\n for (const name of Object.keys(projects).sort()) {\n const declared = projects[name].serviceName;\n if (!this.isNode(name) || declared === undefined) continue;\n const claimant = this.nodeByServiceName.get(declared);\n if (claimant === undefined) {\n this.nodeByServiceName.set(declared, name);\n } else if (claimant !== name) {\n this.problems.push(\n `${name} declares serviceName '${declared}', but that is already the module name of ` +\n `${claimant} — the alias can never be reached. Rename one of them.`,\n );\n }\n }\n }\n\n assemble(): RuntimeGraphReport {\n const decls = this.collectDecls();\n const apis = this.buildApis(decls);\n const edgeResult = this.buildEdges(decls, apis);\n const services = this.buildServices(decls, edgeResult.edges);\n const apisObj: Record<string, RuntimeApi> = {};\n for (const api of Array.from(apis.keys()).sort()) apisObj[api] = apis.get(api)!;\n const graph: RuntimeGraph = {\n services,\n apis: apisObj,\n runtimeEdges: edgeResult.edges,\n unresolvedUses: edgeResult.unresolved,\n };\n return new RuntimeGraphReport(graph, this.warnings, this.problems);\n }\n\n /**\n * One ScanDecl per RUNTIME NODE (role:server / role:client). A node's effective\n * relations are its OWN apiRelations PLUS those of every library in its transitive\n * dependsOn closure: a lib that calls createRpcClient/addRoutes runs inside the\n * server/client that embeds it, so the relation is attributed to that node — the\n * lib is never a runtime node. Recursion stops at other nodes (each server/client\n * owns its own relations). dependsOn is transitively reduced, but reduction\n * preserves reachability, so the recursive walk still reaches every lib a node\n * embeds through libraries.\n */\n private collectDecls(): ScanDecl[] {\n const decls: ScanDecl[] = [];\n for (const name of Object.keys(this.projects).sort()) {\n if (!this.isNode(name)) continue;\n const sink = new RelationSink(name);\n this.collectEffectiveRelations(name, sink, new Set<string>([name]));\n if (sink.implementsApis.length > 0 || sink.usesApis.length > 0) {\n decls.push({\n name,\n implementsApis: dedupApiRefs(sortApiRefs(sink.implementsApis)),\n usesApis: dedupApiRefs(sortApiRefs(sink.usesApis)),\n implementsVia: sink.implementsVia,\n });\n }\n }\n return decls;\n }\n\n /** True when a project is a runtime node — a server or client app (never a library). */\n private isNode(name: string): boolean {\n const role = this.projects[name]?.role;\n return role === 'server' || role === 'client';\n }\n\n /**\n * Accumulate `name`'s own api relations, then recurse through its LIBRARY deps\n * (skipping other nodes, which own their own relations). `visited` guards against\n * re-walking a lib reachable by more than one path (and any cycle).\n */\n private collectEffectiveRelations(name: string, sink: RelationSink, visited: Set<string>): void {\n const entry = this.projects[name];\n if (entry === undefined) return;\n const relations = entry.apiRelations;\n if (relations !== undefined) {\n for (const owner of Object.keys(relations).sort()) {\n for (const ref of relations[owner].implements) {\n this.apiOwners.set(ref.api, owner);\n sink.addImplements(ref, name);\n }\n for (const ref of relations[owner].uses) {\n this.apiOwners.set(ref.api, owner);\n sink.addUses(ref);\n }\n }\n }\n for (const dep of entry.dependsOn) {\n if (visited.has(dep)) continue;\n visited.add(dep);\n if (this.isNode(dep)) continue; // another server/client owns its own relations\n this.collectEffectiveRelations(dep, sink, visited);\n }\n }\n\n /** apiClassName -> { implementedBy, usedBy, type }. */\n private buildApis(decls: ScanDecl[]): Map<string, RuntimeApi> {\n const apis = new Map<string, RuntimeApi>();\n const ensure = (api: string, type: ApiTransport): RuntimeApi => {\n let entry = apis.get(api);\n if (!entry) {\n entry = { implementedBy: [], usedBy: [], type };\n apis.set(api, entry);\n }\n return entry;\n };\n for (const decl of decls) {\n for (const ref of decl.implementsApis) ensure(ref.api, ref.type).implementedBy.push(decl.name);\n for (const ref of decl.usesApis) ensure(ref.api, ref.type).usedBy.push(decl.name);\n }\n for (const api of apis.keys()) {\n const entry = apis.get(api)!;\n entry.implementedBy.sort();\n entry.usedBy = Array.from(new Set(entry.usedBy)).sort();\n const owner = this.apiOwners.get(api);\n if (owner !== undefined) entry.owner = owner;\n }\n return apis;\n }\n\n /** Inferred edges U -> I via api A, split by transport; plus uses with no implementer. */\n private buildEdges(decls: ScanDecl[], apis: Map<string, RuntimeApi>): EdgeResult {\n const viaByKey = new Map<string, Set<string>>();\n const unresolved: RuntimeUnresolved[] = [];\n for (const decl of decls) {\n for (const ref of decl.usesApis) {\n const implementers = apis.get(ref.api)?.implementedBy ?? [];\n if (implementers.length === 0) {\n unresolved.push({ service: decl.name, api: ref.api });\n continue;\n }\n for (const target of this.targetsFor(decl.name, ref, implementers)) {\n if (target === decl.name) continue;\n const key = `${decl.name} ${target} ${ref.type}`;\n if (!viaByKey.has(key)) viaByKey.set(key, new Set());\n viaByKey.get(key)!.add(ref.api);\n }\n }\n }\n return { edges: this.edgesFromKeys(viaByKey), unresolved: sortUnresolved(unresolved) };\n }\n\n /**\n * WHICH implementers this one `uses` reaches. A call site naming its target resolves to exactly\n * ONE node; anything else falls back to the historical fan-out — the only safe superset — and\n * records WHY, so a fanned-out (i.e. possibly fictional) edge can never pass for a derived one.\n */\n private targetsFor(user: string, ref: ApiRef, implementers: string[]): string[] {\n const wanted = ref.targetService;\n if (wanted === undefined) return this.untargetedFanOut(user, ref, implementers);\n\n const node = this.nodeByServiceName.get(wanted);\n if (node === undefined) {\n this.problems.push(\n `${user} calls \"${ref.api}\" at service '${wanted}', but NO module answers to that name. ` +\n `${implementers.length} module(s) serve this contract (${implementers.join(', ')}). Either use ` +\n `the module name, or declare metadata.webpieces.serviceName: '${wanted}' on the module that ` +\n `serves it (and translate any environment prefix in ClientRegistry.setDeriver, not here).`,\n );\n return implementers;\n }\n if (!implementers.includes(node)) {\n this.problems.push(\n `${user} calls \"${ref.api}\" at service '${wanted}' (module ${node}), but ${node} does NOT serve ` +\n `that contract — ${implementers.join(', ')} do. At runtime that call has nothing to answer it.`,\n );\n return implementers;\n }\n return [node];\n }\n\n /**\n * A use with no literal client config. One implementer is unambiguous, so it stays silent; more\n * than one means every edge but one is fiction — exactly the failure this mechanism exists to\n * stop — so it is reported even though the graph still (conservatively) draws them all.\n */\n private untargetedFanOut(user: string, ref: ApiRef, implementers: string[]): string[] {\n if (implementers.length > 1) {\n this.warnings.push(\n `${user} uses \"${ref.api}\" with no literal client config, and ${implementers.length} services ` +\n `implement it (${implementers.join(', ')}) — an edge is drawn to EVERY one, so all but one ` +\n `are fiction. Name the target: createRpcClient(${ref.api}, new ClientConfig('<serviceName>')).`,\n );\n }\n return implementers;\n }\n\n private edgesFromKeys(viaByKey: Map<string, Set<string>>): RuntimeEdge[] {\n const edges: RuntimeEdge[] = [];\n for (const key of viaByKey.keys()) {\n const parts = key.split(' ');\n edges.push({ from: parts[0], to: parts[1], via: Array.from(viaByKey.get(key)!).sort(), type: parts[2] as ApiTransport });\n }\n edges.sort(\n (a: RuntimeEdge, b: RuntimeEdge) =>\n a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.type ?? '').localeCompare(b.type ?? ''),\n );\n return edges;\n }\n\n private buildServices(decls: ScanDecl[], edges: RuntimeEdge[]): Record<string, RuntimeService> {\n const services: Record<string, RuntimeService> = {};\n for (const decl of decls) {\n const dependsOn = Array.from(\n new Set(edges.filter((e: RuntimeEdge) => e.from === decl.name).map((e: RuntimeEdge) => e.to)),\n ).sort();\n // Keys are written in this order; an undefined value is omitted by JSON.stringify, so\n // the committed JSON stays clean AND deterministic without conditional assembly.\n const service: RuntimeService = {\n level: 0,\n serviceName: this.projects[decl.name]?.serviceName,\n implements: decl.implementsApis.map((r: ApiRef) => r.api),\n implementsVia: decl.implementsVia.size > 0 ? sortedRecord(decl.implementsVia) : undefined,\n // One api used against two services is two refs but ONE api in this list.\n uses: Array.from(new Set(decl.usesApis.map((r: ApiRef) => r.api))),\n dependsOn,\n };\n services[decl.name] = service;\n if (this.hiddenProjects.has(decl.name)) services[decl.name].drawOnGraph = false;\n }\n const levels = assignLevels(adjacencyFromEdges(Object.keys(services), edges));\n for (const name of Object.keys(services)) services[name].level = levels[name] ?? 0;\n return services;\n }\n}\n\n/**\n * Derive the runtime graph from dependencies.json's project `apiRelations` — the\n * single source of truth shared by generate + validate. `hiddenProjects`\n * (drawOnGraph:false, defaults to none) are kept in the graph but flagged so the\n * runtime visualizer omits their nodes + edges.\n */\n// webpieces-disable no-function-outside-class -- module entry point for the runtime graph derivation\nexport function deriveRuntimeGraph(\n projects: EnhancedGraph,\n hiddenProjects: Set<string> = new Set<string>()\n): RuntimeGraph {\n return deriveRuntimeGraphReport(projects, hiddenProjects).graph;\n}\n\n/**\n * The same derivation, plus the warnings it produced (every edge it had to GUESS at). Executors use\n * this form and print the warnings; `deriveRuntimeGraph` is the convenience form for callers that\n * only want the data. The warnings are deliberately kept OUT of runtime-dependencies.json — a graph\n * file that records its own doubts would just get committed and stop being read.\n */\n// webpieces-disable no-function-outside-class -- module entry point for the runtime graph derivation\nexport function deriveRuntimeGraphReport(\n projects: EnhancedGraph,\n hiddenProjects: Set<string> = new Set<string>()\n): RuntimeGraphReport {\n return new RuntimeGraphDeriver(projects, hiddenProjects).assemble();\n}\n\n/** Drop duplicate api refs, keeping the first — needed after a node absorbs the same api from both\n * its own relations and an embedded lib's. Keyed by api AND target service: the same contract aimed\n * at two different services is two distinct relations (two distinct edges), not a duplicate. Input\n * is pre-sorted, so output stays deterministic. */\n// webpieces-disable no-function-outside-class -- pure list helper, matches the sibling helpers in this file\nfunction dedupApiRefs(refs: ApiRef[]): ApiRef[] {\n const seen = new Set<string>();\n const out: ApiRef[] = [];\n for (const ref of refs) {\n const key = apiRefKey(ref);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(ref);\n }\n return out;\n}\n\n/** Sort a Map into a plain object with sorted keys, so the committed JSON is deterministic. */\n// webpieces-disable no-function-outside-class -- pure data helper, matches the sibling helpers in this file\nfunction sortedRecord(map: Map<string, string>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const key of [...map.keys()].sort()) out[key] = map.get(key)!;\n return out;\n}\n\n/** Sort AND de-duplicate: one api used against two targets must not be reported unresolved twice. */\n// webpieces-disable no-function-outside-class -- pure sort helper, matches the sibling helpers in this file\nfunction sortUnresolved(unresolved: RuntimeUnresolved[]): RuntimeUnresolved[] {\n const byKey = new Map<string, RuntimeUnresolved>();\n for (const entry of unresolved) byKey.set(`${entry.service} ${entry.api}`, entry);\n return [...byKey.values()].sort(\n (a: RuntimeUnresolved, b: RuntimeUnresolved) => a.service.localeCompare(b.service) || a.api.localeCompare(b.api),\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"]}
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 * Derives the runtime microservice graph SOLELY from architecture/dependencies.json\n * (the single source of truth): each project's `apiRelations` carry which api\n * classes it implements/uses and their transport (rpc | pubsub). Both\n * `architecture:generate` and `architecture:validate-runtime-architecture` call\n * the SAME `deriveRuntimeGraph`, so the committed graph and the validated graph\n * can never diverge.\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 * WHICH X is decided by the call site, not by \"everyone who implements Y\", in\n * priority order: (1) a literal at the call site —\n * `createRpcClient(Y, new ClientConfig('helper-fsdb'))` — kept as\n * `ApiRef.targetService`; else (2) the calling project's declared `callsService`\n * (project.json metadata.webpieces.callsService), the symmetric half of\n * `serviceName` for the shared-library case where the client is built once from a\n * config field so no literal can sit at the call site; else (3) fan-out. A named\n * target from (1) or (2) is matched against each node's DECLARED `serviceName`.\n * Fanning an edge out to every implementer is catastrophic for a company-wide\n * contract registered in a shared library — it manufactures calls that cannot\n * happen, and cycles that do not exist. When a target cannot be resolved the old\n * fan-out still happens, but a warning names the call site (see\n * RuntimeGraphReport.warnings): a wrong-but-green graph is worse than a failing\n * one, so it must never degrade silently.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { sortGraphTopologically } from './graph-sorter';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { ApiRef, ApiTransport } from './api-usage/api-relations';\nimport { apiRefKey, sortApiRefs } from './api-usage/api-relations';\nimport { toError } from '../toError';\n\nexport const DEFAULT_RUNTIME_GRAPH_PATH = 'architecture/runtime-dependencies.json';\n\nexport interface RuntimeService {\n level: number;\n /**\n * The name clients address this service by (`new ClientConfig('helper-fsdb')`), declared in its\n * project.json. Absent for a service nothing calls by name (e.g. a browser app).\n */\n serviceName?: string;\n /**\n * The service(s) this node's clients call when the call site carries no literal `ClientConfig`,\n * declared in its project.json (metadata.webpieces.callsService). A single name, or an\n * `{ apiClassName: serviceName }` map. Absent when the node declares no target. Mirrors\n * GraphEntry.callsService; it is the CALLING-side counterpart of `serviceName`.\n */\n callsService?: string | Record<string, string>;\n implements: string[];\n /**\n * apiClassName -> the LIBRARY project whose apiRelations declared that implements, for the apis\n * this service serves through an embedded library rather than its own source (e.g. a shared\n * route-registration lib). Answers \"who implements WarmupApi, and where did that come from?\",\n * which previously required walking the dependsOn closure by hand.\n */\n implementsVia?: Record<string, string>;\n uses: string[];\n dependsOn: string[];\n /**\n * When false, this service is hidden from the rendered runtime graph (its\n * node AND every edge touching it are omitted from the HTML/DOT). It stays\n * in runtime-dependencies.json so the data view is complete. Absent means\n * drawn (the default). Mirrors GraphEntry.drawOnGraph from the `drawOnGraph:`\n * nx tag.\n */\n drawOnGraph?: boolean;\n}\n\nexport interface RuntimeApi {\n implementedBy: string[];\n usedBy: string[];\n /** Transport of this API — 'rpc' (direct call) or 'pubsub' (delivered through a queue). */\n type?: ApiTransport;\n /**\n * The api-lib project that OWNS this contract. For a contract nothing in-repo implements, this\n * is the external library the calls leave the repo through (`lib-firestore`, `lib-gmail`), which\n * is what the runtime viz labels its terminal external nodes with.\n */\n owner?: string;\n}\n\nexport interface RuntimeEdge {\n from: string;\n to: string;\n via: string[];\n /**\n * Transport of this edge. 'rpc' → a direct call arrow. 'pubsub' → the producer enqueues and the\n * consumer is delivered later, so the runtime viz draws it as producer → QUEUE → consumer.\n * Edges are split by transport, so every edge is a single kind.\n */\n type?: ApiTransport;\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\ninterface EdgeResult {\n edges: RuntimeEdge[];\n unresolved: RuntimeUnresolved[];\n}\n\n/**\n * The derived graph PLUS everything the derivation had to guess at. `warnings` is deliberately not\n * part of RuntimeGraph: it is not committed data, it is the report that stops a guessed edge from\n * passing for a derived one. Executors print it.\n */\nexport class RuntimeGraphReport {\n constructor(\n public readonly graph: RuntimeGraph,\n /** Human-readable lines naming every call site whose target the graph could not pin down. */\n public readonly warnings: string[],\n /**\n * Call sites that name a target the repo does not contain. Unlike a warning these FAIL the\n * build: the contract IS served in-repo, so the name is a typo or a stale rename, and the\n * only reason it stayed invisible is that the graph quietly fanned the edge out instead.\n * (A call to a service outside the repo never reaches here — nothing in-repo implements its\n * contract, so it is `unresolvedUses`.)\n */\n public readonly problems: string[] = [],\n ) {}\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/** One project's implements/uses at api-CLASS granularity, from dependencies.json apiRelations. */\ninterface ScanDecl {\n name: string;\n implementsApis: ApiRef[];\n usesApis: ApiRef[];\n /** apiClassName -> the embedded LIBRARY project that declared the implements (never the node itself). */\n implementsVia: Map<string, string>;\n}\n\n/**\n * Accumulates one node's effective relations while its dependsOn closure is walked, keeping the\n * PROVENANCE the walk would otherwise throw away: which library contributed an implements, and\n * which api-lib owns each contract.\n */\nclass RelationSink {\n readonly implementsApis: ApiRef[] = [];\n readonly usesApis: ApiRef[] = [];\n readonly implementsVia = new Map<string, string>();\n\n constructor(\n /** The runtime node these relations are attributed to. */\n private readonly node: string,\n ) {}\n\n /** `from` is the project whose apiRelations declared this — the node itself, or a lib it embeds. */\n addImplements(ref: ApiRef, from: string): void {\n this.implementsApis.push(ref);\n // First contributor wins, matching dedupApiRefs' keep-the-first rule on the ref list.\n if (from !== this.node && !this.implementsVia.has(ref.api)) this.implementsVia.set(ref.api, from);\n }\n\n addUses(ref: ApiRef): void {\n this.usesApis.push(ref);\n }\n}\n\n/**\n * Derives the runtime microservice graph from architecture/dependencies.json `apiRelations`:\n * implementers × users per API, split by transport. An rpc edge is a direct call; a pubsub edge flows\n * through a queue (drawn producer → queue → consumer by the visualizer).\n *\n * Nodes are the runnable apps only — role:server / role:client. A library is NEVER a node; its\n * implements/uses (a shared client-factory or route-registration lib) are attributed to the\n * servers/clients that embed it, walked transitively over dependsOn (see collectDecls).\n */\nclass RuntimeGraphDeriver {\n /** apiClassName -> the api-lib project that owns the contract (from the apiRelations key). */\n private readonly apiOwners = new Map<string, string>();\n /** Addressable name -> the runtime node answering to it; how a targeted call resolves. */\n private readonly nodeByServiceName = new Map<string, string>();\n private readonly warnings: string[] = [];\n private readonly problems: string[] = [];\n\n constructor(\n private readonly projects: EnhancedGraph,\n /** Project names tagged drawOnGraph:false — kept in the JSON but flagged so the viz omits them. */\n private readonly hiddenProjects: Set<string>\n ) {\n // A node ALWAYS answers to its own module name, so a repo whose deployed names match its\n // project names needs no declaration at all — and no alias can ever redirect 'ai-chat' away\n // from the ai-chat module. Module names are therefore claimed FIRST and are unshadowable.\n for (const name of Object.keys(projects).sort()) {\n if (this.isNode(name)) this.nodeByServiceName.set(name, name);\n }\n // `serviceName` then adds the alias a repo needs when its deployed name is NOT its module\n // name (helper-svr serving 'helper-portal'). Colliding with a module is a misconfiguration,\n // not a precedence question: the alias is silently unreachable, so say so.\n for (const name of Object.keys(projects).sort()) {\n const declared = projects[name].serviceName;\n if (!this.isNode(name) || declared === undefined) continue;\n const claimant = this.nodeByServiceName.get(declared);\n if (claimant === undefined) {\n this.nodeByServiceName.set(declared, name);\n } else if (claimant !== name) {\n this.problems.push(\n `${name} declares serviceName '${declared}', but that is already the module name of ` +\n `${claimant} — the alias can never be reached. Rename one of them.`,\n );\n }\n }\n }\n\n assemble(): RuntimeGraphReport {\n const decls = this.collectDecls();\n const apis = this.buildApis(decls);\n const edgeResult = this.buildEdges(decls, apis);\n const services = this.buildServices(decls, edgeResult.edges);\n const apisObj: Record<string, RuntimeApi> = {};\n for (const api of Array.from(apis.keys()).sort()) apisObj[api] = apis.get(api)!;\n const graph: RuntimeGraph = {\n services,\n apis: apisObj,\n runtimeEdges: edgeResult.edges,\n unresolvedUses: edgeResult.unresolved,\n };\n return new RuntimeGraphReport(graph, this.warnings, this.problems);\n }\n\n /**\n * One ScanDecl per RUNTIME NODE (role:server / role:client). A node's effective\n * relations are its OWN apiRelations PLUS those of every library in its transitive\n * dependsOn closure: a lib that calls createRpcClient/addRoutes runs inside the\n * server/client that embeds it, so the relation is attributed to that node — the\n * lib is never a runtime node. Recursion stops at other nodes (each server/client\n * owns its own relations). dependsOn is transitively reduced, but reduction\n * preserves reachability, so the recursive walk still reaches every lib a node\n * embeds through libraries.\n */\n private collectDecls(): ScanDecl[] {\n const decls: ScanDecl[] = [];\n for (const name of Object.keys(this.projects).sort()) {\n if (!this.isNode(name)) continue;\n const sink = new RelationSink(name);\n this.collectEffectiveRelations(name, sink, new Set<string>([name]));\n if (sink.implementsApis.length > 0 || sink.usesApis.length > 0) {\n decls.push({\n name,\n implementsApis: dedupApiRefs(sortApiRefs(sink.implementsApis)),\n usesApis: dedupApiRefs(sortApiRefs(sink.usesApis)),\n implementsVia: sink.implementsVia,\n });\n }\n }\n return decls;\n }\n\n /** True when a project is a runtime node — a server or client app (never a library). */\n private isNode(name: string): boolean {\n const role = this.projects[name]?.role;\n return role === 'server' || role === 'client';\n }\n\n /**\n * Accumulate `name`'s own api relations, then recurse through its LIBRARY deps\n * (skipping other nodes, which own their own relations). `visited` guards against\n * re-walking a lib reachable by more than one path (and any cycle).\n */\n private collectEffectiveRelations(name: string, sink: RelationSink, visited: Set<string>): void {\n const entry = this.projects[name];\n if (entry === undefined) return;\n const relations = entry.apiRelations;\n if (relations !== undefined) {\n for (const owner of Object.keys(relations).sort()) {\n for (const ref of relations[owner].implements) {\n this.apiOwners.set(ref.api, owner);\n sink.addImplements(ref, name);\n }\n for (const ref of relations[owner].uses) {\n this.apiOwners.set(ref.api, owner);\n sink.addUses(ref);\n }\n }\n }\n for (const dep of entry.dependsOn) {\n if (visited.has(dep)) continue;\n visited.add(dep);\n if (this.isNode(dep)) continue; // another server/client owns its own relations\n this.collectEffectiveRelations(dep, sink, visited);\n }\n }\n\n /** apiClassName -> { implementedBy, usedBy, type }. */\n private buildApis(decls: ScanDecl[]): Map<string, RuntimeApi> {\n const apis = new Map<string, RuntimeApi>();\n const ensure = (api: string, type: ApiTransport): RuntimeApi => {\n let entry = apis.get(api);\n if (!entry) {\n entry = { implementedBy: [], usedBy: [], type };\n apis.set(api, entry);\n }\n return entry;\n };\n for (const decl of decls) {\n for (const ref of decl.implementsApis) ensure(ref.api, ref.type).implementedBy.push(decl.name);\n for (const ref of decl.usesApis) ensure(ref.api, ref.type).usedBy.push(decl.name);\n }\n for (const api of apis.keys()) {\n const entry = apis.get(api)!;\n entry.implementedBy.sort();\n entry.usedBy = Array.from(new Set(entry.usedBy)).sort();\n const owner = this.apiOwners.get(api);\n if (owner !== undefined) entry.owner = owner;\n }\n return apis;\n }\n\n /** Inferred edges U -> I via api A, split by transport; plus uses with no implementer. */\n private buildEdges(decls: ScanDecl[], apis: Map<string, RuntimeApi>): EdgeResult {\n const viaByKey = new Map<string, Set<string>>();\n const unresolved: RuntimeUnresolved[] = [];\n for (const decl of decls) {\n for (const ref of decl.usesApis) {\n const implementers = apis.get(ref.api)?.implementedBy ?? [];\n if (implementers.length === 0) {\n unresolved.push({ service: decl.name, api: ref.api });\n continue;\n }\n for (const target of this.targetsFor(decl.name, ref, implementers)) {\n if (target === decl.name) continue;\n const key = `${decl.name} ${target} ${ref.type}`;\n if (!viaByKey.has(key)) viaByKey.set(key, new Set());\n viaByKey.get(key)!.add(ref.api);\n }\n }\n }\n return { edges: this.edgesFromKeys(viaByKey), unresolved: sortUnresolved(unresolved) };\n }\n\n /**\n * WHICH implementers this one `uses` reaches. Resolution order (most specific wins):\n * 1. a literal `ClientConfig` at the call site (`ref.targetService`) — names ONE node;\n * 2. else the calling project's declared `callsService` (project.json) — for the shared-library\n * case where the literal cannot sit at the call site, it lives one indirection away in the app;\n * 3. else the historical fan-out — the only safe superset — recording WHY, so a fanned-out (i.e.\n * possibly fictional) edge can never pass for a derived one.\n * A named target (1 or 2) that resolves to no module, or to a module that does not serve the\n * contract, is a PROBLEM (fails the build), never a silent drop.\n */\n private targetsFor(user: string, ref: ApiRef, implementers: string[]): string[] {\n const literal = ref.targetService;\n if (literal !== undefined) return this.resolveNamedTarget(user, ref, implementers, literal, 'literal');\n\n const declared = this.callsServiceFor(user, ref.api);\n if (declared !== undefined) return this.resolveNamedTarget(user, ref, implementers, declared, 'callsService');\n\n return this.untargetedFanOut(user, ref, implementers);\n }\n\n /**\n * The declared `callsService` target for this node's use of `api`, or undefined when none applies.\n * A string declaration aims every untargeted use at one service; a map aims per api-class.\n */\n private callsServiceFor(user: string, api: string): string | undefined {\n const declared = this.projects[user]?.callsService;\n if (declared === undefined) return undefined;\n if (typeof declared === 'string') return declared;\n return declared[api];\n }\n\n /**\n * Resolve a NAMED target (from a call-site literal or a project-level `callsService`) to the one\n * module that serves it. A name no module answers to, or a module that does not serve the\n * contract, fails the build — the wording names which declaration to fix, `source` deciding it.\n */\n private resolveNamedTarget(\n user: string,\n ref: ApiRef,\n implementers: string[],\n wanted: string,\n source: 'literal' | 'callsService',\n ): string[] {\n const node = this.nodeByServiceName.get(wanted);\n const served = `${implementers.length} module(s) serve this contract (${implementers.join(', ')})`;\n if (node === undefined) {\n this.problems.push(\n source === 'callsService'\n ? `${user} declares metadata.webpieces.callsService '${wanted}' in its project.json, but NO ` +\n `module answers to that name. ${served}. Point callsService at a module name, or declare ` +\n `metadata.webpieces.serviceName: '${wanted}' on the module that serves \"${ref.api}\".`\n : `${user} calls \"${ref.api}\" at service '${wanted}', but NO module answers to that name. ` +\n `${served}. Either use the module name, or declare metadata.webpieces.serviceName: ` +\n `'${wanted}' on the module that serves it (and translate any environment prefix in ` +\n `ClientRegistry.setDeriver, not here).`,\n );\n return implementers;\n }\n if (!implementers.includes(node)) {\n const via =\n source === 'callsService'\n ? `${user} declares callsService '${wanted}' (module ${node})`\n : `${user} calls \"${ref.api}\" at service '${wanted}' (module ${node})`;\n this.problems.push(\n `${via}, but ${node} does NOT serve \"${ref.api}\" — ${implementers.join(', ')} do. At runtime that ` +\n `call has nothing to answer it.`,\n );\n return implementers;\n }\n return [node];\n }\n\n /**\n * A use with no literal client config. One implementer is unambiguous, so it stays silent; more\n * than one means every edge but one is fiction — exactly the failure this mechanism exists to\n * stop — so it is reported even though the graph still (conservatively) draws them all.\n */\n private untargetedFanOut(user: string, ref: ApiRef, implementers: string[]): string[] {\n if (implementers.length > 1) {\n this.warnings.push(\n `${user} uses \"${ref.api}\" with no literal client config, and ${implementers.length} services ` +\n `implement it (${implementers.join(', ')}) — an edge is drawn to EVERY one, so all but one ` +\n `are fiction. Name the target: createRpcClient(${ref.api}, new ClientConfig('<serviceName>')); ` +\n `or, when the client is built in a shared library (no literal can sit at the call site), ` +\n `declare metadata.webpieces.callsService: '<serviceName>' on ${user}'s project.json.`,\n );\n }\n return implementers;\n }\n\n private edgesFromKeys(viaByKey: Map<string, Set<string>>): RuntimeEdge[] {\n const edges: RuntimeEdge[] = [];\n for (const key of viaByKey.keys()) {\n const parts = key.split(' ');\n edges.push({ from: parts[0], to: parts[1], via: Array.from(viaByKey.get(key)!).sort(), type: parts[2] as ApiTransport });\n }\n edges.sort(\n (a: RuntimeEdge, b: RuntimeEdge) =>\n a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.type ?? '').localeCompare(b.type ?? ''),\n );\n return edges;\n }\n\n private buildServices(decls: ScanDecl[], edges: RuntimeEdge[]): Record<string, RuntimeService> {\n const services: Record<string, RuntimeService> = {};\n for (const decl of decls) {\n const dependsOn = Array.from(\n new Set(edges.filter((e: RuntimeEdge) => e.from === decl.name).map((e: RuntimeEdge) => e.to)),\n ).sort();\n // Keys are written in this order; an undefined value is omitted by JSON.stringify, so\n // the committed JSON stays clean AND deterministic without conditional assembly.\n const service: RuntimeService = {\n level: 0,\n serviceName: this.projects[decl.name]?.serviceName,\n callsService: this.projects[decl.name]?.callsService,\n implements: decl.implementsApis.map((r: ApiRef) => r.api),\n implementsVia: decl.implementsVia.size > 0 ? sortedRecord(decl.implementsVia) : undefined,\n // One api used against two services is two refs but ONE api in this list.\n uses: Array.from(new Set(decl.usesApis.map((r: ApiRef) => r.api))),\n dependsOn,\n };\n services[decl.name] = service;\n if (this.hiddenProjects.has(decl.name)) services[decl.name].drawOnGraph = false;\n }\n const levels = assignLevels(adjacencyFromEdges(Object.keys(services), edges));\n for (const name of Object.keys(services)) services[name].level = levels[name] ?? 0;\n return services;\n }\n}\n\n/**\n * Derive the runtime graph from dependencies.json's project `apiRelations` — the\n * single source of truth shared by generate + validate. `hiddenProjects`\n * (drawOnGraph:false, defaults to none) are kept in the graph but flagged so the\n * runtime visualizer omits their nodes + edges.\n */\n// webpieces-disable no-function-outside-class -- module entry point for the runtime graph derivation\nexport function deriveRuntimeGraph(\n projects: EnhancedGraph,\n hiddenProjects: Set<string> = new Set<string>()\n): RuntimeGraph {\n return deriveRuntimeGraphReport(projects, hiddenProjects).graph;\n}\n\n/**\n * The same derivation, plus the warnings it produced (every edge it had to GUESS at). Executors use\n * this form and print the warnings; `deriveRuntimeGraph` is the convenience form for callers that\n * only want the data. The warnings are deliberately kept OUT of runtime-dependencies.json — a graph\n * file that records its own doubts would just get committed and stop being read.\n */\n// webpieces-disable no-function-outside-class -- module entry point for the runtime graph derivation\nexport function deriveRuntimeGraphReport(\n projects: EnhancedGraph,\n hiddenProjects: Set<string> = new Set<string>()\n): RuntimeGraphReport {\n return new RuntimeGraphDeriver(projects, hiddenProjects).assemble();\n}\n\n/** Drop duplicate api refs, keeping the first — needed after a node absorbs the same api from both\n * its own relations and an embedded lib's. Keyed by api AND target service: the same contract aimed\n * at two different services is two distinct relations (two distinct edges), not a duplicate. Input\n * is pre-sorted, so output stays deterministic. */\n// webpieces-disable no-function-outside-class -- pure list helper, matches the sibling helpers in this file\nfunction dedupApiRefs(refs: ApiRef[]): ApiRef[] {\n const seen = new Set<string>();\n const out: ApiRef[] = [];\n for (const ref of refs) {\n const key = apiRefKey(ref);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(ref);\n }\n return out;\n}\n\n/** Sort a Map into a plain object with sorted keys, so the committed JSON is deterministic. */\n// webpieces-disable no-function-outside-class -- pure data helper, matches the sibling helpers in this file\nfunction sortedRecord(map: Map<string, string>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const key of [...map.keys()].sort()) out[key] = map.get(key)!;\n return out;\n}\n\n/** Sort AND de-duplicate: one api used against two targets must not be reported unresolved twice. */\n// webpieces-disable no-function-outside-class -- pure sort helper, matches the sibling helpers in this file\nfunction sortUnresolved(unresolved: RuntimeUnresolved[]): RuntimeUnresolved[] {\n const byKey = new Map<string, RuntimeUnresolved>();\n for (const entry of unresolved) byKey.set(`${entry.service} ${entry.api}`, entry);\n return [...byKey.values()].sort(\n (a: RuntimeUnresolved, b: RuntimeUnresolved) => a.service.localeCompare(b.service) || a.api.localeCompare(b.api),\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"]}
@@ -25,6 +25,8 @@
25
25
  import { ProjectInfo } from './project-info';
26
26
  /** The project.json key path holding the declared name: metadata.webpieces.serviceName. */
27
27
  export declare const SERVICE_NAME_METADATA_PATH = "metadata.webpieces.serviceName";
28
+ /** The project.json key path holding a client's declared target: metadata.webpieces.callsService. */
29
+ export declare const CALLS_SERVICE_METADATA_PATH = "metadata.webpieces.callsService";
28
30
  export declare class ServiceNameResolution {
29
31
  /** Declared service name, or null when none is declared (or resolution failed). */
30
32
  readonly serviceName: string | null;
@@ -42,6 +44,35 @@ export declare class ServiceNameResolution {
42
44
  * or not a string) is a problem, because that is a typo the author wants told about.
43
45
  */
44
46
  export declare function resolveServiceName(info: ProjectInfo, workspaceRoot: string): ServiceNameResolution;
47
+ /**
48
+ * A client's declared target — the service it addresses when the call site cannot carry a literal
49
+ * `ClientConfig` (the client is built in a SHARED library from a config field, so the literal lives
50
+ * in the app, not the lib, one indirection away). Symmetric to `serviceName`: the IMPLEMENTING side
51
+ * declares the name it answers to, the CALLING side declares the name it calls.
52
+ *
53
+ * Two shapes:
54
+ * - a single string — every untargeted `uses` in this project aims at that one service (the common
55
+ * case: most clients talk to exactly one server);
56
+ * - an `{ apiClassName: serviceName }` map — the mixed case, a client that genuinely calls several.
57
+ */
58
+ export declare class CallsServiceResolution {
59
+ /** Declared target(s), or null when none is declared (or resolution failed). */
60
+ readonly callsService: string | Record<string, string> | null;
61
+ /** Problem description when the declaration is present but unusable, otherwise null. */
62
+ readonly problem: string | null;
63
+ constructor(
64
+ /** Declared target(s), or null when none is declared (or resolution failed). */
65
+ callsService: string | Record<string, string> | null,
66
+ /** Problem description when the declaration is present but unusable, otherwise null. */
67
+ problem: string | null);
68
+ }
69
+ /**
70
+ * Read `metadata.webpieces.callsService` from the project's project.json. A missing file, a missing
71
+ * key, or an unparseable file all resolve to "not declared". A PRESENT value must be either a
72
+ * non-empty string or a non-empty object of non-empty string values — anything else is a typo the
73
+ * author wants told about.
74
+ */
75
+ export declare function resolveCallsService(info: ProjectInfo, workspaceRoot: string): CallsServiceResolution;
45
76
  /**
46
77
  * Two projects claiming the same service name make every targeted client edge ambiguous, so the
47
78
  * graph would have to guess. Reported as a metadata problem instead. Appends to `problems`.