@webpieces/nx-webpieces-rules 0.4.811 → 0.4.812

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.
Files changed (38) hide show
  1. package/executors.json +2 -2
  2. package/package.json +8 -7
  3. package/src/executors/docs-generate/executor.d.ts +14 -13
  4. package/src/executors/docs-generate/executor.js +24 -22
  5. package/src/executors/docs-generate/executor.js.map +1 -1
  6. package/src/executors/docs-generate/schema.json +3 -3
  7. package/src/executors/openapi-generate/executor.d.ts +23 -18
  8. package/src/executors/openapi-generate/executor.js +30 -24
  9. package/src/executors/openapi-generate/executor.js.map +1 -1
  10. package/src/executors/openapi-generate/schema.json +2 -2
  11. package/src/executors/validate-nx-wiring/executor.d.ts +4 -0
  12. package/src/executors/validate-nx-wiring/executor.js +14 -1
  13. package/src/executors/validate-nx-wiring/executor.js.map +1 -1
  14. package/src/generate-targets.d.ts +59 -0
  15. package/src/generate-targets.js +118 -0
  16. package/src/generate-targets.js.map +1 -0
  17. package/src/lib/api-docs/consumer-bin-resolver.d.ts +62 -0
  18. package/src/lib/api-docs/consumer-bin-resolver.js +148 -0
  19. package/src/lib/api-docs/consumer-bin-resolver.js.map +1 -0
  20. package/src/lib/api-docs/generate-wiring.d.ts +61 -0
  21. package/src/lib/api-docs/generate-wiring.js +163 -0
  22. package/src/lib/api-docs/generate-wiring.js.map +1 -0
  23. package/src/lib/api-docs/generator-package.d.ts +23 -0
  24. package/src/lib/api-docs/generator-package.js +32 -0
  25. package/src/lib/api-docs/generator-package.js.map +1 -0
  26. package/src/lib/api-docs/generator-runner.d.ts +21 -0
  27. package/src/lib/api-docs/generator-runner.js +38 -0
  28. package/src/lib/api-docs/generator-runner.js.map +1 -0
  29. package/src/lib/{generated-docs → api-docs}/generator-target.d.ts +19 -12
  30. package/src/lib/{generated-docs → api-docs}/generator-target.js +40 -24
  31. package/src/lib/api-docs/generator-target.js.map +1 -0
  32. package/src/lib/api-docs/json-value.d.ts +6 -0
  33. package/src/lib/api-docs/json-value.js +3 -0
  34. package/src/lib/api-docs/json-value.js.map +1 -0
  35. package/src/plugin.d.ts +2 -0
  36. package/src/plugin.js +15 -2
  37. package/src/plugin.js.map +1 -1
  38. package/src/lib/generated-docs/generator-target.js.map +0 -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 build graph is wired for correct build ORDER. 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 * [\"^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). So we check each compile\n * project's RESOLVED build.dependsOn and fail if `^build` is missing.\n *\n * NOTE: the webpieces validators (architecture:validate-complete, per-project file-import\n * cycles, DI-graph unchanged) NO LONGER ride on the compile target. They live on the `ci`\n * composite target (synthesized by this plugin's createCiTarget), so `build`/compile is a\n * fast compile-only step while `nx ci` runs the full gate. This executor therefore only\n * guards build ORDER (`^build`), not the validators — those are guaranteed by the plugin.\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 { loadAndValidate } 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\n// Compile executors (and any per-project build override) must keep `^build` so build order\n// follows nx's full graph — upstreams are built into dist before dependents compile.\n// Validators no longer belong here; they ride on the `ci` composite target instead.\nconst DEFAULT_REQUIRED_DEPS: string[] = ['^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 `^build`. A project that overrode it 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❌ Build order is not wired in nx.json targetDefaults.\\n');\n console.error('Compile executors must keep \"^build\" so upstream libs build into dist first.');\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}]`);\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 order 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).\\n');\n console.error('Fix: remove build.dependsOn from these project.json files so targetDefaults');\n console.error('apply, OR include \"^build\" 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 = loadAndValidate(context.root).resolved;\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, requiredDeps);\n\n if (wiringProblems.length === 0 && gateProblems.length === 0) {\n console.log('✅ Validators are wired into the build (targetDefaults + every project)\\n');\n return { success: true };\n }\n\n if (wiringProblems.length > 0) reportFailure(wiringProblems, requiredDeps);\n if (gateProblems.length > 0) reportProjectGateFailure(gateProblems);\n return { success: false };\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-nx-wiring/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Nx Wiring Executor\n *\n * Enforces that the build graph is wired for correct build ORDER. 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 * [\"^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). So we check each compile\n * project's RESOLVED build.dependsOn and fail if `^build` is missing.\n *\n * NOTE: the webpieces validators (architecture:validate-complete, per-project file-import\n * cycles, DI-graph unchanged) NO LONGER ride on the compile target. They live on the `ci`\n * composite target (synthesized by this plugin's createCiTarget), so `build`/compile is a\n * fast compile-only step while `nx ci` runs the full gate. This executor therefore only\n * guards build ORDER (`^build`), not the validators — those are guaranteed by the plugin.\n *\n * 3. Generated API documents (#1021) — a project tagged `generate:openapi` / `generate:docs-site` has the\n * `compile` → `openapi-generate` → `build` (nx:noop) shape, and every project depending on one has\n * `test` dependsOn `^build`. See GenerateWiring for why.\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 { loadAndValidate } from '@webpieces/rules-config';\nimport { GenerateWiring } from '../../lib/api-docs/generate-wiring';\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\n// Compile executors (and any per-project build override) must keep `^build` so build order\n// follows nx's full graph — upstreams are built into dist before dependents compile.\n// Validators no longer belong here; they ride on the `ci` composite target instead.\nconst DEFAULT_REQUIRED_DEPS: string[] = ['^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 `^build`. A project that overrode it 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❌ Build order is not wired in nx.json targetDefaults.\\n');\n console.error('Compile executors must keep \"^build\" so upstream libs build into dist first.');\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}]`);\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 order 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).\\n');\n console.error('Fix: remove build.dependsOn from these project.json files so targetDefaults');\n console.error('apply, OR include \"^build\" 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 = loadAndValidate(context.root).resolved;\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 generateWiring = new GenerateWiring(projectsConfig.projects, projectGraph.dependencies);\n const generateProblems = generateWiring.problems();\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 if (generateProblems.length > 0) {\n generateWiring.report(generateProblems);\n return { success: false };\n }\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, requiredDeps);\n\n if (wiringProblems.length === 0 && gateProblems.length === 0 && generateProblems.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 if (generateProblems.length > 0) generateWiring.report(generateProblems);\n return { success: false };\n}\n"]}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The API-document targets the inference plugin adds to a project that OPTS IN with an nx tag:
3
+ *
4
+ * | tag | inferred target(s) |
5
+ * |---|---|
6
+ * | `generate:openapi` | `openapi-generate` |
7
+ * | `generate:docs-site` | `openapi-generate` + `docs-generate` (a site is rendered from a document) |
8
+ *
9
+ * The plugin owns the executor, `cache`, `inputs` and `outputs` — the parts that are the same for every
10
+ * consumer and that a hand-written target gets wrong silently (a missed output is a cache hit that
11
+ * restores a package WITHOUT its documents). The consumer states only what is theirs to decide, in its
12
+ * project.json under the SAME target name, which nx merges over what is inferred here:
13
+ *
14
+ * - `openapi-generate`: `dependsOn` (the ONE compile target whose outputPath it writes into) and
15
+ * `options.manifest` / `options.format`;
16
+ * - `docs-generate`: `options.document` / `options.siteDir` (and optionally `options.prose`).
17
+ *
18
+ * None of those has a default (`.claude/rules/no-rule-defaults.md`): a missing one fails the run naming
19
+ * the key. An untagged project costs one in-memory tag check — no source scan, no `@ApiType` search.
20
+ * A tag the INSTALLED plugin does not know yet is ignored by it, whereas a project.json naming an
21
+ * executor the installed plugin lacks breaks the graph load — which is why opting in is a tag and not
22
+ * a hand-written executor target.
23
+ *
24
+ * The api project's shape (`compile` → `openapi-generate` → `build` as an `nx:noop`) is enforced by
25
+ * `validate-nx-wiring`, not here: this runs while nx builds the graph, where a throw takes the whole
26
+ * workspace down rather than naming one project's fix.
27
+ */
28
+ import type { TargetConfiguration } from '@nx/devkit';
29
+ import { LayoutTargets } from '@webpieces/core-util';
30
+ export declare const GENERATE_OPENAPI_TAG = "generate:openapi";
31
+ export declare const GENERATE_DOCS_SITE_TAG = "generate:docs-site";
32
+ /** The two fields of the raw project.json the inference reads. Data-only. */
33
+ export declare class RawProjectJson {
34
+ readonly name: string;
35
+ readonly tags: readonly string[];
36
+ readonly targets: LayoutTargets;
37
+ constructor(name: string, tags: readonly string[], targets: LayoutTargets);
38
+ }
39
+ /** Reads the raw project.json — the tags and the consumer's own target options — at graph time. */
40
+ export declare class RawProjectJsonReader {
41
+ read(workspaceRoot: string, projectFile: string, projectRoot: string): RawProjectJson;
42
+ }
43
+ export declare class GenerateTargets {
44
+ /** Workspace-relative project root. */
45
+ private readonly projectRoot;
46
+ private readonly project;
47
+ constructor(
48
+ /** Workspace-relative project root. */
49
+ projectRoot: string, project: RawProjectJson);
50
+ /** The targets the project's tags opt it into — `{}` for an untagged project. */
51
+ infer(): Record<string, TargetConfiguration>;
52
+ /**
53
+ * `outputs` point into the compile target's outputPath, read here from the SAME lookup the
54
+ * executor writes with. When the consumer has not stated `dependsOn` yet there is no outputPath to
55
+ * point at, so no outputs are inferred — and the executor's refusal names the missing `dependsOn`.
56
+ */
57
+ private openApiGenerate;
58
+ private docsGenerate;
59
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ /**
3
+ * The API-document targets the inference plugin adds to a project that OPTS IN with an nx tag:
4
+ *
5
+ * | tag | inferred target(s) |
6
+ * |---|---|
7
+ * | `generate:openapi` | `openapi-generate` |
8
+ * | `generate:docs-site` | `openapi-generate` + `docs-generate` (a site is rendered from a document) |
9
+ *
10
+ * The plugin owns the executor, `cache`, `inputs` and `outputs` — the parts that are the same for every
11
+ * consumer and that a hand-written target gets wrong silently (a missed output is a cache hit that
12
+ * restores a package WITHOUT its documents). The consumer states only what is theirs to decide, in its
13
+ * project.json under the SAME target name, which nx merges over what is inferred here:
14
+ *
15
+ * - `openapi-generate`: `dependsOn` (the ONE compile target whose outputPath it writes into) and
16
+ * `options.manifest` / `options.format`;
17
+ * - `docs-generate`: `options.document` / `options.siteDir` (and optionally `options.prose`).
18
+ *
19
+ * None of those has a default (`.claude/rules/no-rule-defaults.md`): a missing one fails the run naming
20
+ * the key. An untagged project costs one in-memory tag check — no source scan, no `@ApiType` search.
21
+ * A tag the INSTALLED plugin does not know yet is ignored by it, whereas a project.json naming an
22
+ * executor the installed plugin lacks breaks the graph load — which is why opting in is a tag and not
23
+ * a hand-written executor target.
24
+ *
25
+ * The api project's shape (`compile` → `openapi-generate` → `build` as an `nx:noop`) is enforced by
26
+ * `validate-nx-wiring`, not here: this runs while nx builds the graph, where a throw takes the whole
27
+ * workspace down rather than naming one project's fix.
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.GenerateTargets = exports.RawProjectJsonReader = exports.RawProjectJson = exports.GENERATE_DOCS_SITE_TAG = exports.GENERATE_OPENAPI_TAG = void 0;
31
+ const core_util_1 = require("@webpieces/core-util");
32
+ const fs_1 = require("fs");
33
+ const path_1 = require("path");
34
+ exports.GENERATE_OPENAPI_TAG = 'generate:openapi';
35
+ exports.GENERATE_DOCS_SITE_TAG = 'generate:docs-site';
36
+ /** The two fields of the raw project.json the inference reads. Data-only. */
37
+ class RawProjectJson {
38
+ name;
39
+ tags;
40
+ targets;
41
+ constructor(name, tags, targets) {
42
+ this.name = name;
43
+ this.tags = tags;
44
+ this.targets = targets;
45
+ }
46
+ }
47
+ exports.RawProjectJson = RawProjectJson;
48
+ /** Reads the raw project.json — the tags and the consumer's own target options — at graph time. */
49
+ class RawProjectJsonReader {
50
+ read(workspaceRoot, projectFile, projectRoot) {
51
+ const raw = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(workspaceRoot, projectFile), 'utf8'));
52
+ return new RawProjectJson(raw.name ?? projectRoot, raw.tags ?? [], raw.targets ?? {});
53
+ }
54
+ }
55
+ exports.RawProjectJsonReader = RawProjectJsonReader;
56
+ class GenerateTargets {
57
+ projectRoot;
58
+ project;
59
+ constructor(
60
+ /** Workspace-relative project root. */
61
+ projectRoot, project) {
62
+ this.projectRoot = projectRoot;
63
+ this.project = project;
64
+ }
65
+ /** The targets the project's tags opt it into — `{}` for an untagged project. */
66
+ infer() {
67
+ const tags = this.project.tags;
68
+ const docs = tags.includes(exports.GENERATE_DOCS_SITE_TAG);
69
+ if (!docs && !tags.includes(exports.GENERATE_OPENAPI_TAG))
70
+ return {};
71
+ const targets = {};
72
+ targets[core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET] = this.openApiGenerate();
73
+ if (docs)
74
+ targets[core_util_1.GeneratedApiDocsLayout.DOCS_TARGET] = this.docsGenerate();
75
+ return targets;
76
+ }
77
+ /**
78
+ * `outputs` point into the compile target's outputPath, read here from the SAME lookup the
79
+ * executor writes with. When the consumer has not stated `dependsOn` yet there is no outputPath to
80
+ * point at, so no outputs are inferred — and the executor's refusal names the missing `dependsOn`.
81
+ */
82
+ openApiGenerate() {
83
+ const found = new core_util_1.GeneratedApiDocsLayout(this.projectRoot, this.project.name, this.project.targets)
84
+ .outputTarget().found;
85
+ const outputs = found === undefined
86
+ ? []
87
+ : [
88
+ `{workspaceRoot}/${found.outputPath}/*openapi.json`,
89
+ `{workspaceRoot}/${found.outputPath}/*openapi.yaml`,
90
+ `{workspaceRoot}/${found.outputPath}/mcp-*-tools.json`,
91
+ ];
92
+ return {
93
+ executor: '@webpieces/nx-webpieces-rules:openapi-generate',
94
+ cache: true,
95
+ inputs: ['default', '^default'],
96
+ outputs,
97
+ metadata: {
98
+ technologies: ['nx'],
99
+ description: 'Generate the OpenAPI documents + one mcp-<ContractClass>-tools.json per MCP contract into the compile outputPath (tag: generate:openapi)',
100
+ },
101
+ };
102
+ }
103
+ docsGenerate() {
104
+ return {
105
+ executor: '@webpieces/nx-webpieces-rules:docs-generate',
106
+ dependsOn: [core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET],
107
+ cache: true,
108
+ inputs: ['default', '^default'],
109
+ outputs: ['{projectRoot}/{options.siteDir}'],
110
+ metadata: {
111
+ technologies: ['nx'],
112
+ description: 'Render the static API reference site into <projectRoot>/<siteDir> for hosting (tag: generate:docs-site)',
113
+ },
114
+ };
115
+ }
116
+ }
117
+ exports.GenerateTargets = GenerateTargets;
118
+ //# sourceMappingURL=generate-targets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-targets.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/generate-targets.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The API-document targets the inference plugin adds to a project that OPTS IN with an nx tag:\n *\n * | tag | inferred target(s) |\n * |---|---|\n * | `generate:openapi` | `openapi-generate` |\n * | `generate:docs-site` | `openapi-generate` + `docs-generate` (a site is rendered from a document) |\n *\n * The plugin owns the executor, `cache`, `inputs` and `outputs` — the parts that are the same for every\n * consumer and that a hand-written target gets wrong silently (a missed output is a cache hit that\n * restores a package WITHOUT its documents). The consumer states only what is theirs to decide, in its\n * project.json under the SAME target name, which nx merges over what is inferred here:\n *\n * - `openapi-generate`: `dependsOn` (the ONE compile target whose outputPath it writes into) and\n * `options.manifest` / `options.format`;\n * - `docs-generate`: `options.document` / `options.siteDir` (and optionally `options.prose`).\n *\n * None of those has a default (`.claude/rules/no-rule-defaults.md`): a missing one fails the run naming\n * the key. An untagged project costs one in-memory tag check — no source scan, no `@ApiType` search.\n * A tag the INSTALLED plugin does not know yet is ignored by it, whereas a project.json naming an\n * executor the installed plugin lacks breaks the graph load — which is why opting in is a tag and not\n * a hand-written executor target.\n *\n * The api project's shape (`compile` → `openapi-generate` → `build` as an `nx:noop`) is enforced by\n * `validate-nx-wiring`, not here: this runs while nx builds the graph, where a throw takes the whole\n * workspace down rather than naming one project's fix.\n */\n\nimport type { TargetConfiguration } from '@nx/devkit';\nimport { GeneratedApiDocsLayout, LayoutTargets } from '@webpieces/core-util';\nimport { readFileSync } from 'fs';\nimport { join } from 'path';\n\nexport const GENERATE_OPENAPI_TAG = 'generate:openapi';\nexport const GENERATE_DOCS_SITE_TAG = 'generate:docs-site';\n\n/** The two fields of the raw project.json the inference reads. Data-only. */\nexport class RawProjectJson {\n constructor(\n readonly name: string,\n readonly tags: readonly string[],\n readonly targets: LayoutTargets,\n ) {}\n}\n\n/** The fields of a project.json that tag-driven inference reads, as they sit in the file. */\ntype ProjectJsonFields = { name?: string; tags?: string[]; targets?: LayoutTargets };\n\n/** Reads the raw project.json — the tags and the consumer's own target options — at graph time. */\nexport class RawProjectJsonReader {\n read(workspaceRoot: string, projectFile: string, projectRoot: string): RawProjectJson {\n const raw = JSON.parse(readFileSync(join(workspaceRoot, projectFile), 'utf8')) as ProjectJsonFields;\n return new RawProjectJson(raw.name ?? projectRoot, raw.tags ?? [], raw.targets ?? {});\n }\n}\n\nexport class GenerateTargets {\n constructor(\n /** Workspace-relative project root. */\n private readonly projectRoot: string,\n private readonly project: RawProjectJson,\n ) {}\n\n /** The targets the project's tags opt it into — `{}` for an untagged project. */\n infer(): Record<string, TargetConfiguration> {\n const tags = this.project.tags;\n const docs = tags.includes(GENERATE_DOCS_SITE_TAG);\n if (!docs && !tags.includes(GENERATE_OPENAPI_TAG)) return {};\n const targets: Record<string, TargetConfiguration> = {};\n targets[GeneratedApiDocsLayout.OPENAPI_TARGET] = this.openApiGenerate();\n if (docs) targets[GeneratedApiDocsLayout.DOCS_TARGET] = this.docsGenerate();\n return targets;\n }\n\n /**\n * `outputs` point into the compile target's outputPath, read here from the SAME lookup the\n * executor writes with. When the consumer has not stated `dependsOn` yet there is no outputPath to\n * point at, so no outputs are inferred — and the executor's refusal names the missing `dependsOn`.\n */\n private openApiGenerate(): TargetConfiguration {\n const found = new GeneratedApiDocsLayout(this.projectRoot, this.project.name, this.project.targets)\n .outputTarget().found;\n const outputs = found === undefined\n ? []\n : [\n `{workspaceRoot}/${found.outputPath}/*openapi.json`,\n `{workspaceRoot}/${found.outputPath}/*openapi.yaml`,\n `{workspaceRoot}/${found.outputPath}/mcp-*-tools.json`,\n ];\n return {\n executor: '@webpieces/nx-webpieces-rules:openapi-generate',\n cache: true,\n inputs: ['default', '^default'],\n outputs,\n metadata: {\n technologies: ['nx'],\n description:\n 'Generate the OpenAPI documents + one mcp-<ContractClass>-tools.json per MCP contract into the compile outputPath (tag: generate:openapi)',\n },\n };\n }\n\n private docsGenerate(): TargetConfiguration {\n return {\n executor: '@webpieces/nx-webpieces-rules:docs-generate',\n dependsOn: [GeneratedApiDocsLayout.OPENAPI_TARGET],\n cache: true,\n inputs: ['default', '^default'],\n outputs: ['{projectRoot}/{options.siteDir}'],\n metadata: {\n technologies: ['nx'],\n description:\n 'Render the static API reference site into <projectRoot>/<siteDir> for hosting (tag: generate:docs-site)',\n },\n };\n }\n}\n"]}
@@ -0,0 +1,62 @@
1
+ import { GeneratorPackage } from './generator-package';
2
+ /** What to resolve, and who is asking. Data-only, per CLAUDE.md. */
3
+ export declare class ConsumerBinRequest {
4
+ /** The failing target's name, so a refusal says WHICH step could not find its generator. */
5
+ readonly ruleName: string;
6
+ readonly generator: GeneratorPackage;
7
+ /**
8
+ * The CONSUMER's directories, nearest first — the project root, then the workspace root. Lookup
9
+ * walks up from each exactly as node does, and never from the caller's own install location.
10
+ */
11
+ readonly searchFrom: readonly string[];
12
+ constructor(
13
+ /** The failing target's name, so a refusal says WHICH step could not find its generator. */
14
+ ruleName: string, generator: GeneratorPackage,
15
+ /**
16
+ * The CONSUMER's directories, nearest first — the project root, then the workspace root. Lookup
17
+ * walks up from each exactly as node does, and never from the caller's own install location.
18
+ */
19
+ searchFrom: readonly string[]);
20
+ }
21
+ /** One generator bin, resolved out of the consumer's `node_modules`. Data-only. */
22
+ export declare class ConsumerBin {
23
+ readonly packageName: string;
24
+ readonly version: string;
25
+ /** The installed package.json this was read from. */
26
+ readonly packageJson: string;
27
+ /** Absolute path to the bin's entry script. */
28
+ readonly binPath: string;
29
+ constructor(packageName: string, version: string,
30
+ /** The installed package.json this was read from. */
31
+ packageJson: string,
32
+ /** Absolute path to the bin's entry script. */
33
+ binPath: string);
34
+ }
35
+ /**
36
+ * Finds a generator bin in the CONSUMER's `node_modules`, never in a copy bundled beside the caller,
37
+ * and refuses one too old to have the capabilities the caller needs.
38
+ *
39
+ * ## Why never a bundled copy
40
+ *
41
+ * The generator reads the decorators the consumer's contracts are written with, so it must be the
42
+ * release the consumer pinned alongside those decorators. A copy shipped inside the nx plugin would be
43
+ * the PLUGIN's release — the rules stream, which a repo deliberately runs one release behind — and would
44
+ * document an app with a reader from a different release. The lookup therefore starts only from the
45
+ * directories the caller names (the consumer's project and workspace), and walks up from each exactly
46
+ * as node's resolution does. It never consults `require.resolve` from this file, which is the one
47
+ * lookup that WOULD find a bundled copy.
48
+ *
49
+ * ## The version handshake
50
+ *
51
+ * A capability always ships in the generator (the server stream) BEFORE a caller relies on it, so an
52
+ * installed generator older than `minimumVersion` is a pin to bump, never a bug to work around. The
53
+ * refusal names the bump: `@webpieces/openapi-generator >= X.Y.Z`.
54
+ */
55
+ export declare class ConsumerBinResolver {
56
+ resolve(request: ConsumerBinRequest): ConsumerBin;
57
+ private findPackageJson;
58
+ private assertNewEnough;
59
+ /** `X.Y.Z[-pre]`, or `undefined` for anything else — which the handshake then refuses. */
60
+ private parseVersion;
61
+ private binPathOf;
62
+ }
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConsumerBinResolver = exports.ConsumerBin = exports.ConsumerBinRequest = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const rules_config_1 = require("@webpieces/rules-config");
8
+ /** What to resolve, and who is asking. Data-only, per CLAUDE.md. */
9
+ class ConsumerBinRequest {
10
+ ruleName;
11
+ generator;
12
+ searchFrom;
13
+ constructor(
14
+ /** The failing target's name, so a refusal says WHICH step could not find its generator. */
15
+ ruleName, generator,
16
+ /**
17
+ * The CONSUMER's directories, nearest first — the project root, then the workspace root. Lookup
18
+ * walks up from each exactly as node does, and never from the caller's own install location.
19
+ */
20
+ searchFrom) {
21
+ this.ruleName = ruleName;
22
+ this.generator = generator;
23
+ this.searchFrom = searchFrom;
24
+ }
25
+ }
26
+ exports.ConsumerBinRequest = ConsumerBinRequest;
27
+ /** One generator bin, resolved out of the consumer's `node_modules`. Data-only. */
28
+ class ConsumerBin {
29
+ packageName;
30
+ version;
31
+ packageJson;
32
+ binPath;
33
+ constructor(packageName, version,
34
+ /** The installed package.json this was read from. */
35
+ packageJson,
36
+ /** Absolute path to the bin's entry script. */
37
+ binPath) {
38
+ this.packageName = packageName;
39
+ this.version = version;
40
+ this.packageJson = packageJson;
41
+ this.binPath = binPath;
42
+ }
43
+ }
44
+ exports.ConsumerBin = ConsumerBin;
45
+ /** A parsed `X.Y.Z[-pre]`. A prerelease sorts BELOW its release, which is what semver says. */
46
+ class ReleaseVersion {
47
+ major;
48
+ minor;
49
+ patch;
50
+ prerelease;
51
+ constructor(major, minor, patch, prerelease) {
52
+ this.major = major;
53
+ this.minor = minor;
54
+ this.patch = patch;
55
+ this.prerelease = prerelease;
56
+ }
57
+ isAtLeast(other) {
58
+ if (this.major !== other.major)
59
+ return this.major > other.major;
60
+ if (this.minor !== other.minor)
61
+ return this.minor > other.minor;
62
+ if (this.patch !== other.patch)
63
+ return this.patch > other.patch;
64
+ return !this.prerelease || other.prerelease;
65
+ }
66
+ }
67
+ /**
68
+ * Finds a generator bin in the CONSUMER's `node_modules`, never in a copy bundled beside the caller,
69
+ * and refuses one too old to have the capabilities the caller needs.
70
+ *
71
+ * ## Why never a bundled copy
72
+ *
73
+ * The generator reads the decorators the consumer's contracts are written with, so it must be the
74
+ * release the consumer pinned alongside those decorators. A copy shipped inside the nx plugin would be
75
+ * the PLUGIN's release — the rules stream, which a repo deliberately runs one release behind — and would
76
+ * document an app with a reader from a different release. The lookup therefore starts only from the
77
+ * directories the caller names (the consumer's project and workspace), and walks up from each exactly
78
+ * as node's resolution does. It never consults `require.resolve` from this file, which is the one
79
+ * lookup that WOULD find a bundled copy.
80
+ *
81
+ * ## The version handshake
82
+ *
83
+ * A capability always ships in the generator (the server stream) BEFORE a caller relies on it, so an
84
+ * installed generator older than `minimumVersion` is a pin to bump, never a bug to work around. The
85
+ * refusal names the bump: `@webpieces/openapi-generator >= X.Y.Z`.
86
+ */
87
+ class ConsumerBinResolver {
88
+ resolve(request) {
89
+ const packageJson = this.findPackageJson(request);
90
+ const manifest = JSON.parse(fs.readFileSync(packageJson, 'utf8'));
91
+ const version = typeof manifest['version'] === 'string' ? manifest['version'] : '';
92
+ this.assertNewEnough(request, packageJson, version);
93
+ const binPath = this.binPathOf(request, packageJson, manifest);
94
+ return new ConsumerBin(request.generator.packageName, version, packageJson, binPath);
95
+ }
96
+ findPackageJson(request) {
97
+ for (const start of request.searchFrom) {
98
+ let dir = path.resolve(start);
99
+ for (;;) {
100
+ const candidate = path.join(dir, 'node_modules', ...request.generator.packageName.split('/'), 'package.json');
101
+ if (fs.existsSync(candidate))
102
+ return candidate;
103
+ const parent = path.dirname(dir);
104
+ if (parent === dir)
105
+ break;
106
+ dir = parent;
107
+ }
108
+ }
109
+ throw new rules_config_1.RuleFailError(request.ruleName, `${request.generator.packageName} is not installed in this workspace (looked in node_modules at and above: ` +
110
+ `${request.searchFrom.join(', ')}). The generator must be the consumer's OWN install — the ` +
111
+ `release pinned beside the decorators it reads — and is never taken from a bundled copy.`, undefined, undefined, [new rules_config_1.Option(`Add it as a devDependency: ${request.generator.packageName} >= ${request.generator.minimumVersion}`, true)]);
112
+ }
113
+ assertNewEnough(request, packageJson, version) {
114
+ const minimum = this.parseVersion(request.generator.minimumVersion);
115
+ if (minimum === undefined) {
116
+ // Our own constant, not the consumer's input — a malformed one is a webpieces bug.
117
+ throw new Error(`minimumVersion '${request.generator.minimumVersion}' for ${request.generator.packageName} is not X.Y.Z`);
118
+ }
119
+ const installed = this.parseVersion(version);
120
+ if (installed !== undefined && installed.isAtLeast(minimum))
121
+ return;
122
+ throw new rules_config_1.RuleFailError(request.ruleName, `${request.generator.packageName} ${version === '' ? '(no version)' : version} is installed at ${packageJson}, ` +
123
+ `and ${request.ruleName} needs ${request.generator.packageName} >= ${request.generator.minimumVersion}.` +
124
+ (installed !== undefined && installed.prerelease
125
+ ? ' A prerelease such as a workspace link\'s 0.0.0-dev is not a release and has no compiled bin.'
126
+ : ''), undefined, undefined, [new rules_config_1.Option(`Bump the devDependency to ${request.generator.packageName} >= ${request.generator.minimumVersion}`, true)]);
127
+ }
128
+ /** `X.Y.Z[-pre]`, or `undefined` for anything else — which the handshake then refuses. */
129
+ parseVersion(text) {
130
+ const match = /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(text.trim());
131
+ if (match === null)
132
+ return undefined;
133
+ return new ReleaseVersion(Number(match[1]), Number(match[2]), Number(match[3]), match[4] !== undefined);
134
+ }
135
+ binPathOf(request, packageJson, manifest) {
136
+ const bin = manifest['bin'];
137
+ const declared = typeof bin === 'string'
138
+ ? bin
139
+ : (bin !== null && typeof bin === 'object' && !Array.isArray(bin) ? bin[request.generator.binName] : undefined);
140
+ const binPath = typeof declared === 'string' ? path.resolve(path.dirname(packageJson), declared) : '';
141
+ if (binPath !== '' && fs.existsSync(binPath))
142
+ return binPath;
143
+ throw new rules_config_1.RuleFailError(request.ruleName, `${request.generator.packageName} at ${packageJson} declares no runnable '${request.generator.binName}' bin` +
144
+ (binPath === '' ? '.' : ` (${binPath} does not exist).`), undefined, undefined, [new rules_config_1.Option(`Reinstall a published release: ${request.generator.packageName} >= ${request.generator.minimumVersion}`, true)]);
145
+ }
146
+ }
147
+ exports.ConsumerBinResolver = ConsumerBinResolver;
148
+ //# sourceMappingURL=consumer-bin-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumer-bin-resolver.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-docs/consumer-bin-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { Option, RuleFailError } from '@webpieces/rules-config';\nimport { GeneratorPackage } from './generator-package';\nimport { JsonObject } from './json-value';\n\n/** What to resolve, and who is asking. Data-only, per CLAUDE.md. */\nexport class ConsumerBinRequest {\n constructor(\n /** The failing target's name, so a refusal says WHICH step could not find its generator. */\n readonly ruleName: string,\n readonly generator: GeneratorPackage,\n /**\n * The CONSUMER's directories, nearest first — the project root, then the workspace root. Lookup\n * walks up from each exactly as node does, and never from the caller's own install location.\n */\n readonly searchFrom: readonly string[],\n ) {}\n}\n\n/** One generator bin, resolved out of the consumer's `node_modules`. Data-only. */\nexport class ConsumerBin {\n constructor(\n readonly packageName: string,\n readonly version: string,\n /** The installed package.json this was read from. */\n readonly packageJson: string,\n /** Absolute path to the bin's entry script. */\n readonly binPath: string,\n ) {}\n}\n\n/** A parsed `X.Y.Z[-pre]`. A prerelease sorts BELOW its release, which is what semver says. */\nclass ReleaseVersion {\n constructor(\n readonly major: number,\n readonly minor: number,\n readonly patch: number,\n readonly prerelease: boolean,\n ) {}\n\n isAtLeast(other: ReleaseVersion): boolean {\n if (this.major !== other.major) return this.major > other.major;\n if (this.minor !== other.minor) return this.minor > other.minor;\n if (this.patch !== other.patch) return this.patch > other.patch;\n return !this.prerelease || other.prerelease;\n }\n}\n\n/**\n * Finds a generator bin in the CONSUMER's `node_modules`, never in a copy bundled beside the caller,\n * and refuses one too old to have the capabilities the caller needs.\n *\n * ## Why never a bundled copy\n *\n * The generator reads the decorators the consumer's contracts are written with, so it must be the\n * release the consumer pinned alongside those decorators. A copy shipped inside the nx plugin would be\n * the PLUGIN's release — the rules stream, which a repo deliberately runs one release behind — and would\n * document an app with a reader from a different release. The lookup therefore starts only from the\n * directories the caller names (the consumer's project and workspace), and walks up from each exactly\n * as node's resolution does. It never consults `require.resolve` from this file, which is the one\n * lookup that WOULD find a bundled copy.\n *\n * ## The version handshake\n *\n * A capability always ships in the generator (the server stream) BEFORE a caller relies on it, so an\n * installed generator older than `minimumVersion` is a pin to bump, never a bug to work around. The\n * refusal names the bump: `@webpieces/openapi-generator >= X.Y.Z`.\n */\nexport class ConsumerBinResolver {\n resolve(request: ConsumerBinRequest): ConsumerBin {\n const packageJson = this.findPackageJson(request);\n const manifest = JSON.parse(fs.readFileSync(packageJson, 'utf8')) as JsonObject;\n const version = typeof manifest['version'] === 'string' ? manifest['version'] : '';\n this.assertNewEnough(request, packageJson, version);\n const binPath = this.binPathOf(request, packageJson, manifest);\n return new ConsumerBin(request.generator.packageName, version, packageJson, binPath);\n }\n\n private findPackageJson(request: ConsumerBinRequest): string {\n for (const start of request.searchFrom) {\n let dir = path.resolve(start);\n for (;;) {\n const candidate = path.join(dir, 'node_modules', ...request.generator.packageName.split('/'), 'package.json');\n if (fs.existsSync(candidate)) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n }\n throw new RuleFailError(\n request.ruleName,\n `${request.generator.packageName} is not installed in this workspace (looked in node_modules at and above: ` +\n `${request.searchFrom.join(', ')}). The generator must be the consumer's OWN install — the ` +\n `release pinned beside the decorators it reads — and is never taken from a bundled copy.`,\n undefined,\n undefined,\n [new Option(`Add it as a devDependency: ${request.generator.packageName} >= ${request.generator.minimumVersion}`, true)],\n );\n }\n\n private assertNewEnough(request: ConsumerBinRequest, packageJson: string, version: string): void {\n const minimum = this.parseVersion(request.generator.minimumVersion);\n if (minimum === undefined) {\n // Our own constant, not the consumer's input — a malformed one is a webpieces bug.\n throw new Error(`minimumVersion '${request.generator.minimumVersion}' for ${request.generator.packageName} is not X.Y.Z`);\n }\n const installed = this.parseVersion(version);\n if (installed !== undefined && installed.isAtLeast(minimum)) return;\n throw new RuleFailError(\n request.ruleName,\n `${request.generator.packageName} ${version === '' ? '(no version)' : version} is installed at ${packageJson}, ` +\n `and ${request.ruleName} needs ${request.generator.packageName} >= ${request.generator.minimumVersion}.` +\n (installed !== undefined && installed.prerelease\n ? ' A prerelease such as a workspace link\\'s 0.0.0-dev is not a release and has no compiled bin.'\n : ''),\n undefined,\n undefined,\n [new Option(`Bump the devDependency to ${request.generator.packageName} >= ${request.generator.minimumVersion}`, true)],\n );\n }\n\n /** `X.Y.Z[-pre]`, or `undefined` for anything else — which the handshake then refuses. */\n private parseVersion(text: string): ReleaseVersion | undefined {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$/.exec(text.trim());\n if (match === null) return undefined;\n return new ReleaseVersion(Number(match[1]), Number(match[2]), Number(match[3]), match[4] !== undefined);\n }\n\n private binPathOf(request: ConsumerBinRequest, packageJson: string, manifest: JsonObject): string {\n const bin = manifest['bin'];\n const declared = typeof bin === 'string'\n ? bin\n : (bin !== null && typeof bin === 'object' && !Array.isArray(bin) ? (bin as JsonObject)[request.generator.binName] : undefined);\n const binPath = typeof declared === 'string' ? path.resolve(path.dirname(packageJson), declared) : '';\n if (binPath !== '' && fs.existsSync(binPath)) return binPath;\n throw new RuleFailError(\n request.ruleName,\n `${request.generator.packageName} at ${packageJson} declares no runnable '${request.generator.binName}' bin` +\n (binPath === '' ? '.' : ` (${binPath} does not exist).`),\n undefined,\n undefined,\n [new Option(`Reinstall a published release: ${request.generator.packageName} >= ${request.generator.minimumVersion}`, true)],\n );\n }\n}\n"]}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The build SHAPE a project opting into generated API documents must have, checked by
3
+ * `validate-nx-wiring` (#1021):
4
+ *
5
+ * ```
6
+ * <api-lib>:compile @nx/js:tsc → its outputPath (dependsOn ^build)
7
+ * <api-lib>:openapi-generate dependsOn ["compile"] → writes into compile's outputPath
8
+ * <api-lib>:build nx:noop, dependsOn ["compile", "openapi-generate"]
9
+ * ```
10
+ *
11
+ * Why that shape and no other: nx `dependsOn` only points BACKWARD, generation must run AFTER tsc
12
+ * (whose cleanup wipes the outputPath), and every consumer only ever asks for `^build` — servers,
13
+ * Dockerfiles (`nx run-many -t build -p lang-server`). With `build` as a noop over both, every existing
14
+ * `^build` pulls generation in with zero consumer change.
15
+ *
16
+ * And one rule for the projects that DEPEND on such a library: their `test` must dependsOn `^build`.
17
+ * A server spec that boots the MCP server reads the library's generated tool catalogs out of its build
18
+ * output (`McpToolCatalog.fromPackages` on a workspace source directory), so a test run that did not
19
+ * build the library first fails on a missing file — or, worse, passes against a stale one.
20
+ */
21
+ import type { ProjectConfiguration } from '@nx/devkit';
22
+ import { RuleFailError } from '@webpieces/rules-config';
23
+ /** One project's wiring defect, and the exact project.json edit that fixes it. Data-only. */
24
+ export declare class GenerateWiringProblem {
25
+ readonly project: string;
26
+ readonly problem: string;
27
+ readonly cure: string;
28
+ constructor(project: string, problem: string, cure: string);
29
+ }
30
+ /** The one field of an nx graph dependency this check reads. Data-only. */
31
+ export declare class ProjectDependency {
32
+ readonly target: string;
33
+ constructor(target: string);
34
+ }
35
+ export declare class GenerateWiring {
36
+ /** nx's RESOLVED project configurations — targetDefaults and inferred targets merged in. */
37
+ private readonly projects;
38
+ /** The project graph's dependencies, by source project. */
39
+ private readonly dependencies;
40
+ constructor(
41
+ /** nx's RESOLVED project configurations — targetDefaults and inferred targets merged in. */
42
+ projects: Readonly<Record<string, ProjectConfiguration>>,
43
+ /** The project graph's dependencies, by source project. */
44
+ dependencies: Readonly<Record<string, readonly ProjectDependency[]>>);
45
+ /** The projects tagged to generate API documents. */
46
+ generating(): string[];
47
+ /** The problems as ONE structured failure: each problem in the message, each fix as an Option. */
48
+ failure(problems: readonly GenerateWiringProblem[]): RuleFailError;
49
+ /** Render {@link failure} through the one human renderer, as validate-nx-wiring's report. */
50
+ report(problems: readonly GenerateWiringProblem[]): void;
51
+ problems(): GenerateWiringProblem[];
52
+ /**
53
+ * An UNTAGGED project naming an inferred executor by hand: a second way to opt in, which also slips
54
+ * past the shape check (and breaks the graph load the day the installed plugin lacks the executor).
55
+ */
56
+ private handWritten;
57
+ private shapeOf;
58
+ private dependentsOf;
59
+ /** Whether `target` dependsOn `wanted` — `^x` for the upstream form, `x` for a sibling. */
60
+ private names;
61
+ }