@webpieces/nx-webpieces-rules 0.3.129 → 0.3.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/executors/generate/executor.js +4 -4
- package/src/executors/generate/executor.js.map +1 -1
- package/src/executors/validate-architecture-unchanged/executor.js +3 -3
- package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
- package/src/executors/validate-no-skiplevel-deps/executor.d.ts +12 -6
- package/src/executors/validate-no-skiplevel-deps/executor.js +16 -113
- package/src/executors/validate-no-skiplevel-deps/executor.js.map +1 -1
- package/src/executors/validate-nx-wiring/executor.d.ts +15 -14
- package/src/executors/validate-nx-wiring/executor.js +94 -27
- package/src/executors/validate-nx-wiring/executor.js.map +1 -1
- package/src/executors/validate-packagejson/executor.js +14 -8
- package/src/executors/validate-packagejson/executor.js.map +1 -1
- package/src/lib/graph-generator.d.ts +27 -8
- package/src/lib/graph-generator.js +48 -53
- package/src/lib/graph-generator.js.map +1 -1
- package/src/lib/package-validator.d.ts +10 -0
- package/src/lib/package-validator.js +12 -5
- package/src/lib/package-validator.js.map +1 -1
- package/src/lib/transitive-reduction.d.ts +25 -0
- package/src/lib/transitive-reduction.js +56 -0
- package/src/lib/transitive-reduction.js.map +1 -0
- package/src/plugin.js +4 -1
- package/src/plugin.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-nx-wiring/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Nx Wiring Executor\n *\n * Enforces that the webpieces validators are actually wired into the build in\n * nx.json. The plugin auto-infers the validator targets (architecture:validate-complete,\n * per-project validate-no-file-import-cycles), but the load-bearing connection that\n * makes a build DEPEND on them lives in each repo's hand-edited nx.json:\n *\n * \"@nx/js:tsc\": {\n * \"dependsOn\": [\"architecture:validate-complete\", \"validate-no-file-import-cycles\", \"^build\"]\n * }\n *\n * If that dependsOn is stripped, the validators exist but never run and a build stays\n * green while validating nothing. This executor fails when the wiring is missing.\n *\n * Conservative by design: it only REQUIRES wiring on compile executors that are\n * actually used somewhere in the project graph (@nx/js:tsc, @angular/build:application).\n * A repo that uses neither has nothing to gate and passes.\n *\n * Disable per validator in webpieces.config.json (rules[name].mode=\"OFF\"), but the\n * wiring itself is governed by the \"nx-wiring\" rule (default on) so the build gate stays.\n *\n * Usage: nx run architecture:validate-nx-wiring\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadConfig } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateNxWiringOptions {\n requiredDeps?: string[];\n compileExecutors?: string[];\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst DEFAULT_REQUIRED_DEPS: string[] = [\n 'architecture:validate-complete',\n 'validate-no-file-import-cycles',\n];\n\nconst DEFAULT_COMPILE_EXECUTORS: string[] = [\n '@nx/js:tsc',\n '@angular/build:application',\n];\n\nclass WiringProblem {\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(executorName: string, missing: string[], found: string[]) {\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\ninterface TargetDefaultEntry {\n dependsOn?: string[];\n}\n\ninterface RawNxJson {\n targetDefaults?: Record<string, TargetDefaultEntry>;\n}\n\nfunction readTargetDefaults(workspaceRoot: string): Record<string, TargetDefaultEntry> {\n const nxJsonPath = path.join(workspaceRoot, 'nx.json');\n if (!fs.existsSync(nxJsonPath)) return {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(nxJsonPath, 'utf8');\n const parsed = JSON.parse(raw) as RawNxJson;\n return parsed.targetDefaults ?? {};\n // webpieces-disable catch-error-pattern -- malformed nx.json fails open so the check does not crash the build\n } catch (err: unknown) {\n //const error = toError(err); -- malformed nx.json fails open\n void err;\n return {};\n }\n}\n\nasync function findCompileExecutorsInUse(compileExecutors: string[]): Promise<Set<string>> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n const known = new Set(compileExecutors);\n const inUse = new Set<string>();\n for (const cfg of Object.values(projectsConfig.projects)) {\n const targets = cfg.targets ?? {};\n for (const target of Object.values(targets)) {\n const executorName = target.executor;\n if (executorName && known.has(executorName)) {\n inUse.add(executorName);\n }\n }\n }\n return inUse;\n}\n\nfunction findProblems(\n relevantExecutors: string[],\n targetDefaults: Record<string, TargetDefaultEntry>,\n requiredDeps: string[],\n): WiringProblem[] {\n const problems: WiringProblem[] = [];\n for (const executorName of relevantExecutors) {\n const entry = targetDefaults[executorName];\n const dependsOn = entry?.dependsOn ?? [];\n const missing = requiredDeps.filter((dep: string) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new WiringProblem(executorName, missing, dependsOn));\n }\n }\n return problems;\n}\n\nfunction reportFailure(problems: WiringProblem[], requiredDeps: string[]): void {\n console.error('\\n❌ webpieces validators are not wired into your build.\\n');\n console.error('The validators exist but no build depends on them, so they never run.');\n console.error('Add the missing dependsOn entries to nx.json targetDefaults:\\n');\n const depsList = requiredDeps.map((dep: string) => `\"${dep}\"`).join(', ');\n for (const problem of problems) {\n const missingList = problem.missing.map((dep: string) => `\"${dep}\"`).join(', ');\n console.error(` \"${problem.executorName}\": {`);\n console.error(` \"dependsOn\": [${depsList}, \"^build\"]`);\n console.error(' }');\n console.error(` missing: ${missingList}\\n`);\n }\n console.error('To disable an INDIVIDUAL validator, set rules[name].mode=\"OFF\" in');\n console.error('webpieces.config.json — but the wiring above must stay installed so the');\n console.error('build gate keeps working. To turn this wiring check itself off, set');\n console.error('rules[\"nx-wiring\"].mode=\"OFF\" in webpieces.config.json.\\n');\n}\n\nexport default async function runExecutor(\n options: ValidateNxWiringOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadConfig(context.root);\n const rule = shared.rules.get('nx-wiring');\n if (rule && rule.isOff) {\n console.log('\\n⏭️ Skipping validate-nx-wiring (mode: OFF)\\n');\n return { success: true };\n }\n\n const requiredDeps = options.requiredDeps ?? DEFAULT_REQUIRED_DEPS;\n const compileExecutors = options.compileExecutors ?? DEFAULT_COMPILE_EXECUTORS;\n\n console.log('\\n🔌 Validating webpieces validators are wired into the build\\n');\n\n const inUse = await findCompileExecutorsInUse(compileExecutors);\n const relevantExecutors = compileExecutors.filter((executorName: string) =>\n inUse.has(executorName),\n );\n\n if (relevantExecutors.length === 0) {\n console.log('✅ No known compile executors in use — nothing to gate\\n');\n return { success: true };\n }\n\n const targetDefaults = readTargetDefaults(context.root);\n const problems = findProblems(relevantExecutors, targetDefaults, requiredDeps);\n\n if (problems.length === 0) {\n console.log('✅ Validators are wired into the build\\n');\n return { success: true };\n }\n\n reportFailure(problems, requiredDeps);\n return { success: false };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-nx-wiring/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Nx Wiring Executor\n *\n * Enforces that the webpieces validators are actually wired into the build. Two checks:\n *\n * 1. nx.json targetDefaults — the compile executors (@nx/js:tsc, @angular/build:application)\n * must carry the load-bearing dependsOn:\n * [\"architecture:validate-complete\", \"validate-no-file-import-cycles\", \"^build\"]\n *\n * 2. Per-project override guard — a project that declares its OWN build.dependsOn OVERRIDES\n * the targetDefaults entirely (nx does not merge dependsOn). That silently drops `^build`\n * (→ upstream libs not built first, builds against an empty dist) AND the validator gates\n * (→ the project builds green while validating nothing). So we check each compile project's\n * RESOLVED build.dependsOn and fail if it is missing any required entry.\n *\n * If either is stripped, validators exist but never run and the build stays green while\n * validating nothing. This executor fails when the wiring is missing.\n *\n * Conservative by design: only REQUIRES wiring on compile executors actually in use\n * (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.\n *\n * Disable via webpieces.config.json rules[\"nx-wiring\"].mode=\"OFF\".\n *\n * Usage: nx run architecture:validate-nx-wiring\n */\n\nimport type {\n ExecutorContext,\n ProjectsConfigurations,\n TargetDependencyConfig,\n} from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadConfig } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateNxWiringOptions {\n requiredDeps?: string[];\n compileExecutors?: string[];\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst DEFAULT_REQUIRED_DEPS: string[] = [\n 'architecture:validate-complete',\n 'validate-no-file-import-cycles',\n];\n\n// Per-project build targets must ALSO keep `^build` so build order follows nx's full graph.\nconst BUILD_ORDER_DEP = '^build';\n\nconst DEFAULT_COMPILE_EXECUTORS: string[] = [\n '@nx/js:tsc',\n '@angular/build:application',\n];\n\nclass WiringProblem {\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(executorName: string, missing: string[], found: string[]) {\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\nclass ProjectGateProblem {\n project: string;\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(project: string, executorName: string, missing: string[], found: string[]) {\n this.project = project;\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\ninterface TargetDefaultEntry {\n dependsOn?: string[];\n}\n\ninterface RawNxJson {\n targetDefaults?: Record<string, TargetDefaultEntry>;\n}\n\nfunction readTargetDefaults(workspaceRoot: string): Record<string, TargetDefaultEntry> {\n const nxJsonPath = path.join(workspaceRoot, 'nx.json');\n if (!fs.existsSync(nxJsonPath)) return {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(nxJsonPath, 'utf8');\n const parsed = JSON.parse(raw) as RawNxJson;\n return parsed.targetDefaults ?? {};\n // webpieces-disable catch-error-pattern -- malformed nx.json fails open so the check does not crash the build\n } catch (err: unknown) {\n //const error = toError(err); -- malformed nx.json fails open\n void err;\n return {};\n }\n}\n\nfunction findCompileExecutorsInUse(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: string[],\n): Set<string> {\n const known = new Set(compileExecutors);\n const inUse = new Set<string>();\n for (const cfg of Object.values(projectsConfig.projects)) {\n const targets = cfg.targets ?? {};\n for (const target of Object.values(targets)) {\n const executorName = target.executor;\n if (executorName && known.has(executorName)) {\n inUse.add(executorName);\n }\n }\n }\n return inUse;\n}\n\nfunction findProblems(\n relevantExecutors: string[],\n targetDefaults: Record<string, TargetDefaultEntry>,\n requiredDeps: string[],\n): WiringProblem[] {\n const problems: WiringProblem[] = [];\n for (const executorName of relevantExecutors) {\n const entry = targetDefaults[executorName];\n const dependsOn = entry?.dependsOn ?? [];\n const missing = requiredDeps.filter((dep: string) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new WiringProblem(executorName, missing, dependsOn));\n }\n }\n return problems;\n}\n\n/**\n * Normalize an nx dependsOn entry to a comparable string. The string form (\"^build\",\n * \"validate-complete\") passes through; the object form is rendered the same way\n * ({target:\"build\", dependencies:true} → \"^build\", {target:\"build\"} → \"build\").\n */\nfunction normalizeDeps(deps: (string | TargetDependencyConfig)[]): string[] {\n return deps.map((dep) => {\n if (typeof dep === 'string') return dep;\n const target = dep.target ?? '';\n return dep.dependencies ? `^${target}` : target;\n });\n}\n\n/**\n * Check each compile project's RESOLVED build.dependsOn (targetDefaults already merged in)\n * for the required gates + `^build`. A project that overrode them away is flagged.\n */\nfunction findProjectGateProblems(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: Set<string>,\n requiredPerProject: string[],\n): ProjectGateProblem[] {\n const problems: ProjectGateProblem[] = [];\n for (const [name, cfg] of Object.entries(projectsConfig.projects)) {\n // Tooling/foundation packages (type:tooling) BUILD the validators, so gating their own\n // build on validate-complete would create a bootstrap cycle. They are exempt: they keep\n // build-order (\"^build\") only. Application/library code is still fully gated.\n if ((cfg.tags ?? []).includes('type:tooling')) continue;\n const build = cfg.targets?.['build'];\n if (!build || !build.executor || !compileExecutors.has(build.executor)) continue;\n const dependsOn = normalizeDeps(build.dependsOn ?? []);\n const missing = requiredPerProject.filter((dep) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new ProjectGateProblem(name, build.executor, missing, dependsOn));\n }\n }\n return problems;\n}\n\nfunction reportFailure(problems: WiringProblem[], requiredDeps: string[]): void {\n console.error('\\n❌ webpieces validators are not wired into your build (nx.json).\\n');\n console.error('The validators exist but no build depends on them, so they never run.');\n console.error('Add the missing dependsOn entries to nx.json targetDefaults:\\n');\n const depsList = requiredDeps.map((dep: string) => `\"${dep}\"`).join(', ');\n for (const problem of problems) {\n const missingList = problem.missing.map((dep: string) => `\"${dep}\"`).join(', ');\n console.error(` \"${problem.executorName}\": {`);\n console.error(` \"dependsOn\": [${depsList}, \"^build\"]`);\n console.error(' }');\n console.error(` missing: ${missingList}\\n`);\n }\n console.error('To turn this wiring check off, set rules[\"nx-wiring\"].mode=\"OFF\" in');\n console.error('webpieces.config.json.\\n');\n}\n\nfunction reportProjectGateFailure(problems: ProjectGateProblem[]): void {\n console.error('\\n❌ Some projects override away the build gates in their project.json.\\n');\n console.error('A project-level build.dependsOn OVERRIDES nx.json targetDefaults (nx does not');\n console.error('merge dependsOn). That silently drops \"^build\" (upstreams not built first →');\n console.error('builds against an empty dist) and/or the validators (build stays green while');\n console.error('validating nothing).\\n');\n console.error('Fix: remove build.dependsOn from these project.json files so targetDefaults');\n console.error('apply, OR include all required entries explicitly:\\n');\n for (const p of problems) {\n const missingList = p.missing.map((dep) => `\"${dep}\"`).join(', ');\n console.error(` ${p.project} (${p.executorName}):`);\n console.error(` resolved build.dependsOn: ${JSON.stringify(p.found)}`);\n console.error(` missing: ${missingList}\\n`);\n }\n}\n\nexport default async function runExecutor(\n options: ValidateNxWiringOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadConfig(context.root);\n const rule = shared.rules.get('nx-wiring');\n if (rule && rule.isOff) {\n console.log('\\n⏭️ Skipping validate-nx-wiring (mode: OFF)\\n');\n return { success: true };\n }\n\n const requiredDeps = options.requiredDeps ?? DEFAULT_REQUIRED_DEPS;\n const compileExecutors = options.compileExecutors ?? DEFAULT_COMPILE_EXECUTORS;\n\n console.log('\\n🔌 Validating webpieces validators are wired into the build\\n');\n\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const inUse = findCompileExecutorsInUse(projectsConfig, compileExecutors);\n const relevantExecutors = compileExecutors.filter((executorName: string) =>\n inUse.has(executorName),\n );\n\n if (relevantExecutors.length === 0) {\n console.log('✅ No known compile executors in use — nothing to gate\\n');\n return { success: true };\n }\n\n const targetDefaults = readTargetDefaults(context.root);\n const wiringProblems = findProblems(relevantExecutors, targetDefaults, requiredDeps);\n const gateProblems = findProjectGateProblems(projectsConfig, inUse, [\n ...requiredDeps,\n BUILD_ORDER_DEP,\n ]);\n\n if (wiringProblems.length === 0 && gateProblems.length === 0) {\n console.log('✅ Validators are wired into the build (targetDefaults + every project)\\n');\n return { success: true };\n }\n\n if (wiringProblems.length > 0) reportFailure(wiringProblems, requiredDeps);\n if (gateProblems.length > 0) reportProjectGateFailure(gateProblems);\n return { success: false };\n}\n"]}
|
|
@@ -19,17 +19,24 @@ async function runExecutor(options, context) {
|
|
|
19
19
|
console.log('\n📦 Validating Package.json Dependencies\n');
|
|
20
20
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
21
21
|
try {
|
|
22
|
-
// Step 1:
|
|
23
|
-
console.log(
|
|
24
|
-
const
|
|
22
|
+
// Step 1: Build the full graph from nx, then transitively reduce it (the view)
|
|
23
|
+
console.log("📊 Generating dependency graph from nx's project graph...");
|
|
24
|
+
const reducedGraph = await (0, graph_generator_1.generateReducedGraph)();
|
|
25
25
|
// Step 2: Topological sort (to get enhanced graph with levels)
|
|
26
26
|
console.log('🔄 Computing topological layers...');
|
|
27
|
-
const enhancedGraph = (0, graph_sorter_1.sortGraphTopologically)(
|
|
27
|
+
const enhancedGraph = (0, graph_sorter_1.sortGraphTopologically)(reducedGraph);
|
|
28
28
|
// Step 3: Validate package.json dependencies match
|
|
29
|
-
console.log('📦 Validating package.json dependencies match
|
|
29
|
+
console.log('📦 Validating package.json dependencies match the architecture graph...');
|
|
30
30
|
const packageValidation = await (0, package_validator_1.validatePackageJsonDependencies)(enhancedGraph, workspaceRoot);
|
|
31
|
+
// Warnings never fail the build (e.g. runtime-only / peer deps).
|
|
32
|
+
if (packageValidation.warnings.length > 0) {
|
|
33
|
+
console.warn('\n⚠️ Package.json notices (non-fatal):');
|
|
34
|
+
for (const warning of packageValidation.warnings) {
|
|
35
|
+
console.warn(` ${warning}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
31
38
|
if (!packageValidation.valid) {
|
|
32
|
-
console.error('❌ Package.json validation failed!');
|
|
39
|
+
console.error('\n❌ Package.json validation failed!');
|
|
33
40
|
console.error('\nErrors:');
|
|
34
41
|
for (const error of packageValidation.errors) {
|
|
35
42
|
console.error(` ${error}`);
|
|
@@ -37,10 +44,9 @@ async function runExecutor(options, context) {
|
|
|
37
44
|
console.error('\nTo fix:');
|
|
38
45
|
console.error(' 1. Review the missing dependencies above');
|
|
39
46
|
console.error(' 2. Add the missing dependencies to the respective package.json files');
|
|
40
|
-
console.error(' 3. Ensure dependencies in package.json match build.dependsOn in project.json');
|
|
41
47
|
return { success: false };
|
|
42
48
|
}
|
|
43
|
-
console.log('✅ Package.json dependencies
|
|
49
|
+
console.log('✅ Package.json dependencies cover the architecture graph');
|
|
44
50
|
// Print summary
|
|
45
51
|
const validProjects = packageValidation.projectResults.filter(r => r.valid).length;
|
|
46
52
|
const totalProjects = packageValidation.projectResults.length;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-packagejson/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAgBH,
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-packagejson/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAgBH,8BA0DC;AAvED,+DAAiE;AACjE,yDAAgE;AAChE,mEAA8E;AAC9E,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,OAAmC,EACnC,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAE3D,8DAA8D;IAC9D,IAAI,CAAC;QACD,+EAA+E;QAC/E,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;QACzE,MAAM,YAAY,GAAG,MAAM,IAAA,sCAAoB,GAAE,CAAC;QAElD,+DAA+D;QAC/D,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,IAAA,qCAAsB,EAAC,YAAY,CAAC,CAAC;QAE3D,mDAAmD;QACnD,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;QACvF,MAAM,iBAAiB,GAAG,MAAM,IAAA,mDAA+B,EAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QAE9F,iEAAiE;QACjE,IAAI,iBAAiB,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;YACxD,KAAK,MAAM,OAAO,IAAI,iBAAiB,CAAC,QAAQ,EAAE,CAAC;gBAC/C,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;YACjC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACrD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,MAAM,EAAE,CAAC;gBAC3C,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;YAChC,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC5D,OAAO,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAC;YACxF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;QAExE,gBAAgB;QAChB,MAAM,aAAa,GAAG,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QACnF,MAAM,aAAa,GAAG,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,aAAa,aAAa,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,eAAe,aAAa,GAAG,aAAa,EAAE,CAAC,CAAC;QAE5D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate Package.json Executor\n *\n * Validates that package.json dependencies match project.json build dependencies.\n * This ensures the two sources of truth don't drift apart.\n *\n * Usage:\n * nx run architecture:validate-packagejson\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { validatePackageJsonDependencies } from '../../lib/package-validator';\nimport { toError } from '../../toError';\n\nexport interface ValidatePackageJsonOptions {\n // No options needed for now\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: ValidatePackageJsonOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n console.log('\\n📦 Validating Package.json Dependencies\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it (the view)\n console.log(\"📊 Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to get enhanced graph with levels)\n console.log('🔄 Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Validate package.json dependencies match\n console.log('📦 Validating package.json dependencies match the architecture graph...');\n const packageValidation = await validatePackageJsonDependencies(enhancedGraph, workspaceRoot);\n\n // Warnings never fail the build (e.g. runtime-only / peer deps).\n if (packageValidation.warnings.length > 0) {\n console.warn('\\n⚠️ Package.json notices (non-fatal):');\n for (const warning of packageValidation.warnings) {\n console.warn(` ${warning}`);\n }\n }\n\n if (!packageValidation.valid) {\n console.error('\\n❌ Package.json validation failed!');\n console.error('\\nErrors:');\n for (const error of packageValidation.errors) {\n console.error(` ${error}`);\n }\n console.error('\\nTo fix:');\n console.error(' 1. Review the missing dependencies above');\n console.error(' 2. Add the missing dependencies to the respective package.json files');\n return { success: false };\n }\n\n console.log('✅ Package.json dependencies cover the architecture graph');\n\n // Print summary\n const validProjects = packageValidation.projectResults.filter(r => r.valid).length;\n const totalProjects = packageValidation.projectResults.length;\n console.log(`\\n📈 Validation Summary:`);\n console.log(` Projects validated: ${totalProjects}`);\n console.log(` Valid: ${validProjects}`);\n console.log(` Invalid: ${totalProjects - validProjects}`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('❌ Package.json validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
|
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Graph Generator
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Builds the workspace dependency graph from nx's OWN project graph
|
|
5
|
+
* (createProjectGraphAsync). nx already derives those edges from BOTH source
|
|
6
|
+
* imports AND package.json workspace deps, so there is no hand-maintained edge
|
|
7
|
+
* list and no separate import scan — we consume what nx already computed.
|
|
8
|
+
*
|
|
9
|
+
* - generateGraph() → the FULL graph (every workspace edge nx knows).
|
|
10
|
+
* This is what build order follows via `^build`.
|
|
11
|
+
* - generateReducedGraph() → transitive reduction of the full graph: the minimal
|
|
12
|
+
* edge set with identical reachability, used as the
|
|
13
|
+
* architecture VIEW written to dependencies.json.
|
|
6
14
|
*/
|
|
7
15
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
16
|
+
* Build the full dependency graph from nx's project graph.
|
|
17
|
+
*
|
|
18
|
+
* nx's `projectGraph.nodes` are the workspace projects; `projectGraph.dependencies`
|
|
19
|
+
* holds every edge nx inferred (imports + package.json). We keep only edges whose
|
|
20
|
+
* target is another workspace project (dropping `npm:` externals) and drop excluded
|
|
21
|
+
* projects.
|
|
22
|
+
*
|
|
23
|
+
* Returns: { projectName: [workspaceDependencyNames] } (deps sorted, deduped)
|
|
10
24
|
*/
|
|
11
25
|
export declare function generateRawGraph(): Promise<Record<string, string[]>>;
|
|
12
26
|
/**
|
|
13
|
-
*
|
|
27
|
+
* The full workspace dependency graph (every edge nx knows).
|
|
14
28
|
*/
|
|
15
|
-
export declare function
|
|
29
|
+
export declare function generateGraph(): Promise<Record<string, string[]>>;
|
|
16
30
|
/**
|
|
17
|
-
*
|
|
31
|
+
* The transitively-reduced view of the full graph. This is the canonical
|
|
32
|
+
* "architecture graph" written to and validated against dependencies.json.
|
|
33
|
+
*
|
|
34
|
+
* Reduction is undefined on cycles; callers that care about cycles
|
|
35
|
+
* (validate-no-architecture-cycles) run on the FULL graph and throw first. For the
|
|
36
|
+
* view executors, the downstream topological sort also throws on any cycle.
|
|
18
37
|
*/
|
|
19
|
-
export declare function
|
|
38
|
+
export declare function generateReducedGraph(): Promise<Record<string, string[]>>;
|
|
@@ -2,83 +2,78 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Graph Generator
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Builds the workspace dependency graph from nx's OWN project graph
|
|
6
|
+
* (createProjectGraphAsync). nx already derives those edges from BOTH source
|
|
7
|
+
* imports AND package.json workspace deps, so there is no hand-maintained edge
|
|
8
|
+
* list and no separate import scan — we consume what nx already computed.
|
|
9
|
+
*
|
|
10
|
+
* - generateGraph() → the FULL graph (every workspace edge nx knows).
|
|
11
|
+
* This is what build order follows via `^build`.
|
|
12
|
+
* - generateReducedGraph() → transitive reduction of the full graph: the minimal
|
|
13
|
+
* edge set with identical reachability, used as the
|
|
14
|
+
* architecture VIEW written to dependencies.json.
|
|
7
15
|
*/
|
|
8
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
17
|
exports.generateRawGraph = generateRawGraph;
|
|
10
|
-
exports.transformGraph = transformGraph;
|
|
11
18
|
exports.generateGraph = generateGraph;
|
|
19
|
+
exports.generateReducedGraph = generateReducedGraph;
|
|
12
20
|
const devkit_1 = require("@nx/devkit");
|
|
21
|
+
const transitive_reduction_1 = require("./transitive-reduction");
|
|
13
22
|
/**
|
|
14
23
|
* Projects to exclude from graph validation (tools, configs, etc.)
|
|
15
24
|
*/
|
|
16
25
|
const EXCLUDED_PROJECTS = new Set(['architecture']);
|
|
17
26
|
/**
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (typeof dep === 'string') {
|
|
27
|
-
// Format: "project-name:build" or just "build" (for self)
|
|
28
|
-
const match = dep.match(/^([^:]+):build$/);
|
|
29
|
-
if (match) {
|
|
30
|
-
deps.push(match[1]);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
// 2. Also read from implicitDependencies
|
|
36
|
-
if (projectConfig.implicitDependencies && Array.isArray(projectConfig.implicitDependencies)) {
|
|
37
|
-
for (const dep of projectConfig.implicitDependencies) {
|
|
38
|
-
if (typeof dep === 'string' && !deps.includes(dep)) {
|
|
39
|
-
deps.push(dep);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return deps.sort();
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Generate raw dependency graph from project.json files
|
|
47
|
-
* Returns: { projectName: [dependencyNames] }
|
|
27
|
+
* Build the full dependency graph from nx's project graph.
|
|
28
|
+
*
|
|
29
|
+
* nx's `projectGraph.nodes` are the workspace projects; `projectGraph.dependencies`
|
|
30
|
+
* holds every edge nx inferred (imports + package.json). We keep only edges whose
|
|
31
|
+
* target is another workspace project (dropping `npm:` externals) and drop excluded
|
|
32
|
+
* projects.
|
|
33
|
+
*
|
|
34
|
+
* Returns: { projectName: [workspaceDependencyNames] } (deps sorted, deduped)
|
|
48
35
|
*/
|
|
49
36
|
async function generateRawGraph() {
|
|
50
37
|
const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
|
|
51
|
-
const
|
|
38
|
+
const workspaceProjects = new Set(Object.keys(projectGraph.nodes));
|
|
52
39
|
const rawDeps = {};
|
|
53
|
-
for (const
|
|
54
|
-
// Skip excluded projects (tools, plugins)
|
|
40
|
+
for (const projectName of workspaceProjects) {
|
|
55
41
|
if (EXCLUDED_PROJECTS.has(projectName)) {
|
|
56
42
|
continue;
|
|
57
43
|
}
|
|
58
|
-
|
|
59
|
-
const deps =
|
|
60
|
-
|
|
44
|
+
const edges = projectGraph.dependencies[projectName] ?? [];
|
|
45
|
+
const deps = new Set();
|
|
46
|
+
for (const edge of edges) {
|
|
47
|
+
const target = edge.target;
|
|
48
|
+
// Keep only workspace→workspace edges; skip self and excluded projects.
|
|
49
|
+
if (target === projectName)
|
|
50
|
+
continue;
|
|
51
|
+
if (!workspaceProjects.has(target))
|
|
52
|
+
continue; // drops npm: externals
|
|
53
|
+
if (EXCLUDED_PROJECTS.has(target))
|
|
54
|
+
continue;
|
|
55
|
+
deps.add(target);
|
|
56
|
+
}
|
|
57
|
+
rawDeps[projectName] = Array.from(deps).sort();
|
|
61
58
|
}
|
|
62
59
|
return rawDeps;
|
|
63
60
|
}
|
|
64
61
|
/**
|
|
65
|
-
*
|
|
62
|
+
* The full workspace dependency graph (every edge nx knows).
|
|
66
63
|
*/
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
for (const [projectName, deps] of Object.entries(rawGraph)) {
|
|
70
|
-
// Use project names as-is - don't force @webpieces scope on client projects
|
|
71
|
-
const transformedName = projectName;
|
|
72
|
-
const transformedDeps = deps.sort();
|
|
73
|
-
result[transformedName] = transformedDeps;
|
|
74
|
-
}
|
|
75
|
-
return result;
|
|
64
|
+
async function generateGraph() {
|
|
65
|
+
return generateRawGraph();
|
|
76
66
|
}
|
|
77
67
|
/**
|
|
78
|
-
*
|
|
68
|
+
* The transitively-reduced view of the full graph. This is the canonical
|
|
69
|
+
* "architecture graph" written to and validated against dependencies.json.
|
|
70
|
+
*
|
|
71
|
+
* Reduction is undefined on cycles; callers that care about cycles
|
|
72
|
+
* (validate-no-architecture-cycles) run on the FULL graph and throw first. For the
|
|
73
|
+
* view executors, the downstream topological sort also throws on any cycle.
|
|
79
74
|
*/
|
|
80
|
-
async function
|
|
81
|
-
const
|
|
82
|
-
return
|
|
75
|
+
async function generateReducedGraph() {
|
|
76
|
+
const full = await generateGraph();
|
|
77
|
+
return (0, transitive_reduction_1.transitiveReduction)(full);
|
|
83
78
|
}
|
|
84
79
|
//# sourceMappingURL=graph-generator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-generator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-generator.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"graph-generator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-generator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAoBH,4CA0BC;AAKD,sCAEC;AAUD,oDAGC;AAhED,uCAAqD;AACrD,iEAA6D;AAE7D;;GAEG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,cAAc,CAAC,CAAC,CAAC;AAE5D;;;;;;;;;GASG;AACI,KAAK,UAAU,gBAAgB;IAClC,MAAM,YAAY,GAAG,MAAM,IAAA,gCAAuB,GAAE,CAAC;IACrD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAEnE,MAAM,OAAO,GAA6B,EAAE,CAAC;IAE7C,KAAK,MAAM,WAAW,IAAI,iBAAiB,EAAE,CAAC;QAC1C,IAAI,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YACrC,SAAS;QACb,CAAC;QAED,MAAM,KAAK,GAAG,YAAY,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAC3B,wEAAwE;YACxE,IAAI,MAAM,KAAK,WAAW;gBAAE,SAAS;YACrC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS,CAAC,uBAAuB;YACrE,IAAI,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS;YAC5C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QAED,OAAO,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,aAAa;IAC/B,OAAO,gBAAgB,EAAE,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,oBAAoB;IACtC,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,OAAO,IAAA,0CAAmB,EAAC,IAAI,CAAC,CAAC;AACrC,CAAC","sourcesContent":["/**\n * Graph Generator\n *\n * Builds the workspace dependency graph from nx's OWN project graph\n * (createProjectGraphAsync). nx already derives those edges from BOTH source\n * imports AND package.json workspace deps, so there is no hand-maintained edge\n * list and no separate import scan — we consume what nx already computed.\n *\n * - generateGraph() → the FULL graph (every workspace edge nx knows).\n * This is what build order follows via `^build`.\n * - generateReducedGraph() → transitive reduction of the full graph: the minimal\n * edge set with identical reachability, used as the\n * architecture VIEW written to dependencies.json.\n */\n\nimport { createProjectGraphAsync } from '@nx/devkit';\nimport { transitiveReduction } from './transitive-reduction';\n\n/**\n * Projects to exclude from graph validation (tools, configs, etc.)\n */\nconst EXCLUDED_PROJECTS = new Set<string>(['architecture']);\n\n/**\n * Build the full dependency graph from nx's project graph.\n *\n * nx's `projectGraph.nodes` are the workspace projects; `projectGraph.dependencies`\n * holds every edge nx inferred (imports + package.json). We keep only edges whose\n * target is another workspace project (dropping `npm:` externals) and drop excluded\n * projects.\n *\n * Returns: { projectName: [workspaceDependencyNames] } (deps sorted, deduped)\n */\nexport async function generateRawGraph(): Promise<Record<string, string[]>> {\n const projectGraph = await createProjectGraphAsync();\n const workspaceProjects = new Set(Object.keys(projectGraph.nodes));\n\n const rawDeps: Record<string, string[]> = {};\n\n for (const projectName of workspaceProjects) {\n if (EXCLUDED_PROJECTS.has(projectName)) {\n continue;\n }\n\n const edges = projectGraph.dependencies[projectName] ?? [];\n const deps = new Set<string>();\n for (const edge of edges) {\n const target = edge.target;\n // Keep only workspace→workspace edges; skip self and excluded projects.\n if (target === projectName) continue;\n if (!workspaceProjects.has(target)) continue; // drops npm: externals\n if (EXCLUDED_PROJECTS.has(target)) continue;\n deps.add(target);\n }\n\n rawDeps[projectName] = Array.from(deps).sort();\n }\n\n return rawDeps;\n}\n\n/**\n * The full workspace dependency graph (every edge nx knows).\n */\nexport async function generateGraph(): Promise<Record<string, string[]>> {\n return generateRawGraph();\n}\n\n/**\n * The transitively-reduced view of the full graph. This is the canonical\n * \"architecture graph\" written to and validated against dependencies.json.\n *\n * Reduction is undefined on cycles; callers that care about cycles\n * (validate-no-architecture-cycles) run on the FULL graph and throw first. For the\n * view executors, the downstream topological sort also throws on any cycle.\n */\nexport async function generateReducedGraph(): Promise<Record<string, string[]>> {\n const full = await generateGraph();\n return transitiveReduction(full);\n}\n"]}
|
|
@@ -15,10 +15,20 @@ export interface ProjectValidationResult {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Overall validation result
|
|
18
|
+
*
|
|
19
|
+
* `errors` fail the build; their only fix is ADDITIVE ("add to package.json"), so they can
|
|
20
|
+
* never push a user toward removing a runtime-required dependency.
|
|
21
|
+
*
|
|
22
|
+
* `warnings` never fail the build. Workspace deps in package.json that the architecture graph
|
|
23
|
+
* can't reach are reported here, NOT as errors: a transitively-reachable or even unreachable
|
|
24
|
+
* entry can still be a real runtime dependency (e.g. a peerDependency or a generated client
|
|
25
|
+
* that nx's import analysis doesn't traverse). Erroring on these is the "runtime-validity trap"
|
|
26
|
+
* that previously forced a bad package.json edit — so we only warn.
|
|
18
27
|
*/
|
|
19
28
|
export interface ValidationResult {
|
|
20
29
|
valid: boolean;
|
|
21
30
|
errors: string[];
|
|
31
|
+
warnings: string[];
|
|
22
32
|
projectResults: ProjectValidationResult[];
|
|
23
33
|
}
|
|
24
34
|
/**
|
|
@@ -132,13 +132,17 @@ function validateSingleProject(projectName, entry, projectRoot, packageJsonDeps,
|
|
|
132
132
|
errors.push(`Project ${projectName} (${projectRoot}/package.json) is missing dependencies: ${classification.missingInPackageJson.join(', ')}\n` +
|
|
133
133
|
` Fix: Add these to package.json dependencies`);
|
|
134
134
|
}
|
|
135
|
+
// Unreachable workspace extras are WARN-ONLY: they may be real runtime deps that nx's
|
|
136
|
+
// import analysis can't see (peerDependency / generated client). Never error — that is
|
|
137
|
+
// the runtime-validity trap. We surface them so genuine drift is still visible.
|
|
138
|
+
const warnings = [];
|
|
135
139
|
for (const extraPkg of classification.extraWorkspaceDeps) {
|
|
136
140
|
const extraProject = packageToProject.get(extraPkg);
|
|
137
|
-
|
|
138
|
-
`
|
|
141
|
+
warnings.push(`Project ${projectName} (${projectRoot}/package.json) has "${extraPkg}" but the architecture graph has no path ${projectName} → ${extraProject}.\n` +
|
|
142
|
+
` This is allowed (it may be a runtime-only/peer dependency). If it is genuinely unused, you may remove it.`);
|
|
139
143
|
}
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
// extraWorkspaceDeps do NOT affect validity — only missing (additive-fix) errors do.
|
|
145
|
+
const valid = classification.missingInPackageJson.length === 0;
|
|
142
146
|
return {
|
|
143
147
|
result: {
|
|
144
148
|
project: projectName,
|
|
@@ -147,6 +151,7 @@ function validateSingleProject(projectName, entry, projectRoot, packageJsonDeps,
|
|
|
147
151
|
extraInPackageJson: classification.extraInPackageJson,
|
|
148
152
|
},
|
|
149
153
|
errors,
|
|
154
|
+
warnings,
|
|
150
155
|
};
|
|
151
156
|
}
|
|
152
157
|
async function validatePackageJsonDependencies(graph, workspaceRoot) {
|
|
@@ -158,6 +163,7 @@ async function validatePackageJsonDependencies(graph, workspaceRoot) {
|
|
|
158
163
|
packageToProject.set(pkgName, projName);
|
|
159
164
|
}
|
|
160
165
|
const errors = [];
|
|
166
|
+
const warnings = [];
|
|
161
167
|
const projectResults = [];
|
|
162
168
|
for (const [projectName, entry] of Object.entries(graph)) {
|
|
163
169
|
const projectConfig = projectsConfig.projects[projectName];
|
|
@@ -169,7 +175,8 @@ async function validatePackageJsonDependencies(graph, workspaceRoot) {
|
|
|
169
175
|
const validation = validateSingleProject(projectName, entry, projectConfig.root, packageJsonDeps, graph, projectToPackage, packageToProject);
|
|
170
176
|
projectResults.push(validation.result);
|
|
171
177
|
errors.push(...validation.errors);
|
|
178
|
+
warnings.push(...validation.warnings);
|
|
172
179
|
}
|
|
173
|
-
return { valid: errors.length === 0, errors, projectResults };
|
|
180
|
+
return { valid: errors.length === 0, errors, warnings, projectResults };
|
|
174
181
|
}
|
|
175
182
|
//# sourceMappingURL=package-validator.js.map
|
|
@@ -1 +1 @@
|
|
|
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 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 */\nexport interface ValidationResult {\n valid: boolean;\n errors: 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, http-filters,\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}\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 for (const extraPkg of classification.extraWorkspaceDeps) {\n const extraProject = packageToProject.get(extraPkg);\n errors.push(\n `Project ${projectName} (${projectRoot}/package.json) has \"${extraPkg}\" in package.json but architecture/dependencies.json has no path ${projectName} → ${extraProject} (not even transitively).\\n` +\n ` Fix: Either add \"${extraProject}:build\" to project.json:build.dependsOn (then run \\`nx run architecture:generate\\`), or remove \"${extraPkg}\" from package.json dependencies.`\n );\n }\n\n const valid =\n classification.missingInPackageJson.length === 0 &&\n classification.extraWorkspaceDeps.length === 0;\n return {\n result: {\n project: projectName,\n valid,\n missingInPackageJson: classification.missingInPackageJson,\n extraInPackageJson: classification.extraInPackageJson,\n },\n errors,\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 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 }\n\n return { valid: errors.length === 0, errors, 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 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, http-filters,\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"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transitive Reduction
|
|
3
|
+
*
|
|
4
|
+
* Computes the transitive reduction of a DAG: the minimal set of edges that
|
|
5
|
+
* preserves the exact same reachability (transitive closure) as the input graph.
|
|
6
|
+
*
|
|
7
|
+
* For a DAG the transitive reduction is unique. An edge u → v is redundant when v
|
|
8
|
+
* is reachable from u through some OTHER direct child w of u (w ≠ v). Removing all
|
|
9
|
+
* redundant edges yields the reduced graph.
|
|
10
|
+
*
|
|
11
|
+
* This is purely a VIEW transformation for architecture/dependencies.json — it must
|
|
12
|
+
* never feed back into package.json or build order. Build order continues to follow
|
|
13
|
+
* nx's full project graph (via `^build`); reduction preserves reachability, so any
|
|
14
|
+
* topological order valid for the full graph is also valid for the reduced graph.
|
|
15
|
+
*
|
|
16
|
+
* The input MUST be acyclic. Reduction is undefined on cycles; callers run the
|
|
17
|
+
* cycle-detecting topological sort (graph-sorter) which throws on cycles first.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Compute the transitive reduction of a DAG.
|
|
21
|
+
*
|
|
22
|
+
* @param graph - Full DAG as { project: [directChildren] }
|
|
23
|
+
* @returns Reduced graph { project: [minimalDirectChildren] } (children sorted)
|
|
24
|
+
*/
|
|
25
|
+
export declare function transitiveReduction(graph: Record<string, string[]>): Record<string, string[]>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Transitive Reduction
|
|
4
|
+
*
|
|
5
|
+
* Computes the transitive reduction of a DAG: the minimal set of edges that
|
|
6
|
+
* preserves the exact same reachability (transitive closure) as the input graph.
|
|
7
|
+
*
|
|
8
|
+
* For a DAG the transitive reduction is unique. An edge u → v is redundant when v
|
|
9
|
+
* is reachable from u through some OTHER direct child w of u (w ≠ v). Removing all
|
|
10
|
+
* redundant edges yields the reduced graph.
|
|
11
|
+
*
|
|
12
|
+
* This is purely a VIEW transformation for architecture/dependencies.json — it must
|
|
13
|
+
* never feed back into package.json or build order. Build order continues to follow
|
|
14
|
+
* nx's full project graph (via `^build`); reduction preserves reachability, so any
|
|
15
|
+
* topological order valid for the full graph is also valid for the reduced graph.
|
|
16
|
+
*
|
|
17
|
+
* The input MUST be acyclic. Reduction is undefined on cycles; callers run the
|
|
18
|
+
* cycle-detecting topological sort (graph-sorter) which throws on cycles first.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.transitiveReduction = transitiveReduction;
|
|
22
|
+
/**
|
|
23
|
+
* Compute the transitive reduction of a DAG.
|
|
24
|
+
*
|
|
25
|
+
* @param graph - Full DAG as { project: [directChildren] }
|
|
26
|
+
* @returns Reduced graph { project: [minimalDirectChildren] } (children sorted)
|
|
27
|
+
*/
|
|
28
|
+
function transitiveReduction(graph) {
|
|
29
|
+
// Memoized reachability (transitive closure) per node.
|
|
30
|
+
const closure = new Map();
|
|
31
|
+
function reach(node) {
|
|
32
|
+
const cached = closure.get(node);
|
|
33
|
+
if (cached)
|
|
34
|
+
return cached;
|
|
35
|
+
const acc = new Set();
|
|
36
|
+
// Set before recursion: safe for a DAG, and guards against runaway recursion
|
|
37
|
+
// if the input is unexpectedly cyclic (closure stays finite).
|
|
38
|
+
closure.set(node, acc);
|
|
39
|
+
for (const child of graph[node] ?? []) {
|
|
40
|
+
acc.add(child);
|
|
41
|
+
for (const reachable of reach(child)) {
|
|
42
|
+
acc.add(reachable);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return acc;
|
|
46
|
+
}
|
|
47
|
+
const reduced = {};
|
|
48
|
+
for (const u of Object.keys(graph)) {
|
|
49
|
+
const children = graph[u] ?? [];
|
|
50
|
+
// Keep u → v only if NO sibling w (w ≠ v) already reaches v.
|
|
51
|
+
const kept = children.filter((v) => !children.some((w) => w !== v && reach(w).has(v)));
|
|
52
|
+
reduced[u] = kept.sort();
|
|
53
|
+
}
|
|
54
|
+
return reduced;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=transitive-reduction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transitive-reduction.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/transitive-reduction.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAQH,kDAiCC;AAvCD;;;;;GAKG;AACH,SAAgB,mBAAmB,CAC/B,KAA+B;IAE/B,uDAAuD;IACvD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE/C,SAAS,KAAK,CAAC,IAAY;QACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,6EAA6E;QAC7E,8DAA8D;QAC9D,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACvB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACf,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACvB,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED,MAAM,OAAO,GAA6B,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChC,6DAA6D;QAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAC3D,CAAC;QACF,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC","sourcesContent":["/**\n * Transitive Reduction\n *\n * Computes the transitive reduction of a DAG: the minimal set of edges that\n * preserves the exact same reachability (transitive closure) as the input graph.\n *\n * For a DAG the transitive reduction is unique. An edge u → v is redundant when v\n * is reachable from u through some OTHER direct child w of u (w ≠ v). Removing all\n * redundant edges yields the reduced graph.\n *\n * This is purely a VIEW transformation for architecture/dependencies.json — it must\n * never feed back into package.json or build order. Build order continues to follow\n * nx's full project graph (via `^build`); reduction preserves reachability, so any\n * topological order valid for the full graph is also valid for the reduced graph.\n *\n * The input MUST be acyclic. Reduction is undefined on cycles; callers run the\n * cycle-detecting topological sort (graph-sorter) which throws on cycles first.\n */\n\n/**\n * Compute the transitive reduction of a DAG.\n *\n * @param graph - Full DAG as { project: [directChildren] }\n * @returns Reduced graph { project: [minimalDirectChildren] } (children sorted)\n */\nexport function transitiveReduction(\n graph: Record<string, string[]>\n): Record<string, string[]> {\n // Memoized reachability (transitive closure) per node.\n const closure = new Map<string, Set<string>>();\n\n function reach(node: string): Set<string> {\n const cached = closure.get(node);\n if (cached) return cached;\n\n const acc = new Set<string>();\n // Set before recursion: safe for a DAG, and guards against runaway recursion\n // if the input is unexpectedly cyclic (closure stays finite).\n closure.set(node, acc);\n for (const child of graph[node] ?? []) {\n acc.add(child);\n for (const reachable of reach(child)) {\n acc.add(reachable);\n }\n }\n return acc;\n }\n\n const reduced: Record<string, string[]> = {};\n for (const u of Object.keys(graph)) {\n const children = graph[u] ?? [];\n // Keep u → v only if NO sibling w (w ≠ v) already reaches v.\n const kept = children.filter(\n (v) => !children.some((w) => w !== v && reach(w).has(v))\n );\n reduced[u] = kept.sort();\n }\n return reduced;\n}\n"]}
|