@webpieces/nx-webpieces-rules 0.4.812 → 0.4.814
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/executors.json +1 -1
- package/package.json +8 -8
- package/src/executors/openapi-generate/executor.d.ts +8 -6
- package/src/executors/openapi-generate/executor.js +8 -6
- package/src/executors/openapi-generate/executor.js.map +1 -1
- package/src/executors/openapi-generate/schema.json +1 -1
- package/src/executors/validate-nx-wiring/executor.d.ts +5 -3
- package/src/executors/validate-nx-wiring/executor.js +7 -4
- package/src/executors/validate-nx-wiring/executor.js.map +1 -1
- package/src/generate-targets.d.ts +4 -4
- package/src/generate-targets.js +5 -5
- package/src/generate-targets.js.map +1 -1
- package/src/lib/api-docs/generate-wiring.d.ts +80 -17
- package/src/lib/api-docs/generate-wiring.js +214 -49
- package/src/lib/api-docs/generate-wiring.js.map +1 -1
- package/src/lib/api-docs/generator-target.d.ts +1 -1
- package/src/lib/api-docs/generator-target.js +1 -1
- package/src/lib/api-docs/generator-target.js.map +1 -1
package/executors.json
CHANGED
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
"openapi-generate": {
|
|
154
154
|
"implementation": "./src/executors/openapi-generate/executor",
|
|
155
155
|
"schema": "./src/executors/openapi-generate/schema.json",
|
|
156
|
-
"description": "Per-project, inferred from the generate:openapi tag: render the contracts to OpenAPI + one mcp-<ContractClass>-tools.json per MCP contract into the
|
|
156
|
+
"description": "Per-project, inferred from the generate:openapi tag: render the contracts to OpenAPI + one mcp-<ContractClass>-tools.json per MCP contract into the build (tsc) target outputPath, with the consumer's own @webpieces/openapi-generator"
|
|
157
157
|
},
|
|
158
158
|
"docs-generate": {
|
|
159
159
|
"implementation": "./src/executors/docs-generate/executor",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.814",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@webpieces/ai-hook-rules": "0.4.
|
|
22
|
-
"@webpieces/api-doc-model": "0.4.
|
|
23
|
-
"@webpieces/code-rules": "0.4.
|
|
24
|
-
"@webpieces/core-util": "0.4.
|
|
25
|
-
"@webpieces/eslint-rules": "0.4.
|
|
26
|
-
"@webpieces/pr-gate": "0.4.
|
|
27
|
-
"@webpieces/rules-config": "0.4.
|
|
21
|
+
"@webpieces/ai-hook-rules": "0.4.814",
|
|
22
|
+
"@webpieces/api-doc-model": "0.4.814",
|
|
23
|
+
"@webpieces/code-rules": "0.4.814",
|
|
24
|
+
"@webpieces/core-util": "0.4.814",
|
|
25
|
+
"@webpieces/eslint-rules": "0.4.814",
|
|
26
|
+
"@webpieces/pr-gate": "0.4.814",
|
|
27
|
+
"@webpieces/rules-config": "0.4.814",
|
|
28
28
|
"madge": "8.0.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Renders a project's contracts to OpenAPI, plus ONE MCP tool catalog per contract
|
|
5
5
|
* (`mcp-<ContractClass>-tools.json`), INTO the outputPath of the target it dependsOn — the api
|
|
6
|
-
* library's
|
|
6
|
+
* library's `build` (its @nx/js:tsc step) — so the documents are packed and published inside the api library's npm
|
|
7
7
|
* package: a consumer installs the library and has the contract. They are build output, never
|
|
8
8
|
* committed.
|
|
9
9
|
*
|
|
@@ -13,15 +13,17 @@
|
|
|
13
13
|
*
|
|
14
14
|
* "tags": ["generate:openapi"],
|
|
15
15
|
* "targets": {
|
|
16
|
-
* "
|
|
17
|
-
*
|
|
16
|
+
* "build": { "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"],
|
|
17
|
+
* "options": { "outputPath": "dist/<project>", ... } },
|
|
18
18
|
* "openapi-generate": {
|
|
19
|
-
* "dependsOn": ["
|
|
19
|
+
* "dependsOn": ["build"],
|
|
20
20
|
* "options": { "manifest": "<project>/openapi.manifest.json", "format": "both" }
|
|
21
|
-
* }
|
|
22
|
-
* "build": { "executor": "nx:noop", "dependsOn": ["compile", "openapi-generate"] }
|
|
21
|
+
* }
|
|
23
22
|
* }
|
|
24
23
|
*
|
|
24
|
+
* and dependents pull it in through nx.json: `"^openapi-generate"` in every targetDefaults entry
|
|
25
|
+
* governing a `build` or `test` (#1023 — see GenerateWiring).
|
|
26
|
+
*
|
|
25
27
|
* The output directory is the outputPath of the ONE target `dependsOn` names (GeneratedApiDocsLayout
|
|
26
28
|
* in @webpieces/core-util — the same lookup the MCP server reads with), never a hardcoded target name
|
|
27
29
|
* and never an assumed dist/. `outputs` are CHECKED against what a run wrote — see GeneratorTarget. The
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Renders a project's contracts to OpenAPI, plus ONE MCP tool catalog per contract
|
|
6
6
|
* (`mcp-<ContractClass>-tools.json`), INTO the outputPath of the target it dependsOn — the api
|
|
7
|
-
* library's
|
|
7
|
+
* library's `build` (its @nx/js:tsc step) — so the documents are packed and published inside the api library's npm
|
|
8
8
|
* package: a consumer installs the library and has the contract. They are build output, never
|
|
9
9
|
* committed.
|
|
10
10
|
*
|
|
@@ -14,15 +14,17 @@
|
|
|
14
14
|
*
|
|
15
15
|
* "tags": ["generate:openapi"],
|
|
16
16
|
* "targets": {
|
|
17
|
-
* "
|
|
18
|
-
*
|
|
17
|
+
* "build": { "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"],
|
|
18
|
+
* "options": { "outputPath": "dist/<project>", ... } },
|
|
19
19
|
* "openapi-generate": {
|
|
20
|
-
* "dependsOn": ["
|
|
20
|
+
* "dependsOn": ["build"],
|
|
21
21
|
* "options": { "manifest": "<project>/openapi.manifest.json", "format": "both" }
|
|
22
|
-
* }
|
|
23
|
-
* "build": { "executor": "nx:noop", "dependsOn": ["compile", "openapi-generate"] }
|
|
22
|
+
* }
|
|
24
23
|
* }
|
|
25
24
|
*
|
|
25
|
+
* and dependents pull it in through nx.json: `"^openapi-generate"` in every targetDefaults entry
|
|
26
|
+
* governing a `build` or `test` (#1023 — see GenerateWiring).
|
|
27
|
+
*
|
|
26
28
|
* The output directory is the outputPath of the ONE target `dependsOn` names (GeneratedApiDocsLayout
|
|
27
29
|
* in @webpieces/core-util — the same lookup the MCP server reads with), never a hardcoded target name
|
|
28
30
|
* and never an assumed dist/. `outputs` are CHECKED against what a run wrote — see GeneratorTarget. The
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/openapi-generate/executor.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/openapi-generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * openapi-generate Executor\n *\n * Renders a project's contracts to OpenAPI, plus ONE MCP tool catalog per contract\n * (`mcp-<ContractClass>-tools.json`), INTO the outputPath of the target it dependsOn — the api\n * library's `build` (its @nx/js:tsc step) — so the documents are packed and published inside the api library's npm\n * package: a consumer installs the library and has the contract. They are build output, never\n * committed.\n *\n * Consumers do not declare this executor. The nx-webpieces-rules plugin INFERS the target on a project\n * tagged `generate:openapi` (executor, cache, inputs, outputs); project.json states only what is the\n * consumer's to decide, under the same target name:\n *\n * \"tags\": [\"generate:openapi\"],\n * \"targets\": {\n * \"build\": { \"executor\": \"@nx/js:tsc\", \"outputs\": [\"{options.outputPath}\"],\n * \"options\": { \"outputPath\": \"dist/<project>\", ... } },\n * \"openapi-generate\": {\n * \"dependsOn\": [\"build\"],\n * \"options\": { \"manifest\": \"<project>/openapi.manifest.json\", \"format\": \"both\" }\n * }\n * }\n *\n * and dependents pull it in through nx.json: `\"^openapi-generate\"` in every targetDefaults entry\n * governing a `build` or `test` (#1023 — see GenerateWiring).\n *\n * The output directory is the outputPath of the ONE target `dependsOn` names (GeneratedApiDocsLayout\n * in @webpieces/core-util — the same lookup the MCP server reads with), never a hardcoded target name\n * and never an assumed dist/. `outputs` are CHECKED against what a run wrote — see GeneratorTarget. The\n * generator itself is the CONSUMER's `@webpieces/openapi-generator`, resolved from its node_modules and\n * version-checked; this plugin bundles no copy of it (see ConsumerBinResolver).\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { GeneratedApiDocsLayout } from '@webpieces/core-util';\nimport { RepoScratchDirs, RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ExecutorResult } from '../../executor-result';\nimport { ConsumerBinRequest, ConsumerBinResolver } from '../../lib/api-docs/consumer-bin-resolver';\nimport { OPENAPI_GENERATOR } from '../../lib/api-docs/generator-package';\nimport { GeneratorRunner } from '../../lib/api-docs/generator-runner';\nimport { GeneratorTarget, StagedOutput } from '../../lib/api-docs/generator-target';\nimport { toError } from '../../toError';\n\nexport interface OpenApiGenerateOptions {\n /** Workspace-relative path to the project's `openapi.manifest.json`. Required; no default. */\n manifest?: string;\n /** `json`, `yaml` or `both`. Required; no default — it decides what the package ships. */\n format?: string;\n}\n\nconst RULE_NAME = GeneratedApiDocsLayout.OPENAPI_TARGET;\n\n/** Everything except the process-facing reporting, so the suite drives it exactly as nx does. */\nexport class OpenApiGenerate {\n constructor(\n private readonly resolver: ConsumerBinResolver = new ConsumerBinResolver(),\n private readonly runner: GeneratorRunner = new GeneratorRunner(),\n private readonly scratch: RepoScratchDirs = new RepoScratchDirs(),\n ) {}\n\n /** @returns the files written, absolute. Throws RuleFailError on every refusal. */\n run(options: OpenApiGenerateOptions, context: ExecutorContext): string[] {\n const target = GeneratorTarget.of(RULE_NAME, context);\n const outDir = target.documentsDir();\n const manifest = target.requiredOption(options.manifest, 'manifest');\n const format = target.requiredOption(options.format, 'format');\n const bin = this.resolver.resolve(new ConsumerBinRequest(\n RULE_NAME, OPENAPI_GENERATOR, [path.join(context.root, target.projectRoot), context.root]));\n\n const staging = this.scratch.make(context.root, 'wp-openapi-generate-');\n // webpieces-disable no-unmanaged-exceptions -- try/FINALLY only, nothing is caught: the staging\n // directory is removed however this ends, and every throw still reaches runExecutor below\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const run = this.runner.run(bin, [\n '--manifest', path.resolve(context.root, manifest), '--out', staging, '--format', format,\n ], context.root);\n if (!run.ok) {\n throw new RuleFailError(\n RULE_NAME,\n `${bin.packageName} ${bin.version} refused ${manifest}:\\n${run.output}`,\n );\n }\n const written = new StagedOutput().publish(staging, outDir);\n target.assertOutputsCover(written);\n return written;\n } finally {\n fs.rmSync(staging, { recursive: true, force: true });\n }\n }\n}\n\n// webpieces-disable no-function-outside-class -- nx executor module: nx resolves a default-export function here\nexport default async function runExecutor(\n options: OpenApiGenerateOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- this IS the executor's single top-level handler\n try {\n const written = new OpenApiGenerate().run(options, context);\n for (const file of written) console.log(`wrote ${path.relative(context.root, file)}`);\n return new ExecutorResult(true);\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`❌ ${RULE_NAME}: ${error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message}`);\n return new ExecutorResult(false);\n }\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/schema",
|
|
3
3
|
"title": "OpenAPI Generate Executor",
|
|
4
|
-
"description": "Inferred on a project tagged generate:openapi. Render the project's contracts to OpenAPI plus one mcp-<ContractClass>-tools.json per MCP contract, with the consumer's own @webpieces/openapi-generator, into the outputPath of the ONE target this target dependsOn (the
|
|
4
|
+
"description": "Inferred on a project tagged generate:openapi. Render the project's contracts to OpenAPI plus one mcp-<ContractClass>-tools.json per MCP contract, with the consumer's own @webpieces/openapi-generator, into the outputPath of the ONE target this target dependsOn (the api library's build — its @nx/js:tsc step).",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
7
|
"manifest": {
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
* fast compile-only step while `nx ci` runs the full gate. This executor therefore only
|
|
19
19
|
* guards build ORDER (`^build`), not the validators — those are guaranteed by the plugin.
|
|
20
20
|
*
|
|
21
|
-
* 3. Generated API documents (#
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
21
|
+
* 3. Generated API documents (#1023) — on the RESOLVED graph: a project tagged `generate:openapi` /
|
|
22
|
+
* `generate:docs-site` has `openapi-generate` dependsOn exactly its own `build` (the @nx/js:tsc
|
|
23
|
+
* target, which keeps that name), and every project depending on one — transitively — has
|
|
24
|
+
* `^openapi-generate` in the effective dependsOn of its `build` and `test`. A failure names the
|
|
25
|
+
* nx.json targetDefaults key to edit and the exact line. See GenerateWiring for why.
|
|
24
26
|
*
|
|
25
27
|
* Conservative by design: only REQUIRES wiring on compile executors actually in use
|
|
26
28
|
* (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.
|
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
* fast compile-only step while `nx ci` runs the full gate. This executor therefore only
|
|
20
20
|
* guards build ORDER (`^build`), not the validators — those are guaranteed by the plugin.
|
|
21
21
|
*
|
|
22
|
-
* 3. Generated API documents (#
|
|
23
|
-
* `
|
|
24
|
-
*
|
|
22
|
+
* 3. Generated API documents (#1023) — on the RESOLVED graph: a project tagged `generate:openapi` /
|
|
23
|
+
* `generate:docs-site` has `openapi-generate` dependsOn exactly its own `build` (the @nx/js:tsc
|
|
24
|
+
* target, which keeps that name), and every project depending on one — transitively — has
|
|
25
|
+
* `^openapi-generate` in the effective dependsOn of its `build` and `test`. A failure names the
|
|
26
|
+
* nx.json targetDefaults key to edit and the exact line. See GenerateWiring for why.
|
|
25
27
|
*
|
|
26
28
|
* Conservative by design: only REQUIRES wiring on compile executors actually in use
|
|
27
29
|
* (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.
|
|
@@ -188,7 +190,8 @@ async function runExecutor(options, context) {
|
|
|
188
190
|
console.log('\n🔌 Validating webpieces validators are wired into the build\n');
|
|
189
191
|
const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
|
|
190
192
|
const projectsConfig = (0, devkit_1.readProjectsConfigurationFromProjectGraph)(projectGraph);
|
|
191
|
-
const
|
|
193
|
+
const sources = new generate_wiring_1.WiringSourceReader(context.root);
|
|
194
|
+
const generateWiring = new generate_wiring_1.GenerateWiring(projectsConfig.projects, projectGraph.dependencies, sources.targetDefaults(), sources.declaredDependsOn(projectsConfig.projects));
|
|
192
195
|
const generateProblems = generateWiring.problems();
|
|
193
196
|
const inUse = findCompileExecutorsInUse(projectsConfig, compileExecutors);
|
|
194
197
|
const relevantExecutors = compileExecutors.filter((executorName) => inUse.has(executorName));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-nx-wiring/executor.ts"],"names":[],"mappings":"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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"]}
|
|
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 (#1023) — on the RESOLVED graph: a project tagged `generate:openapi` /\n * `generate:docs-site` has `openapi-generate` dependsOn exactly its own `build` (the @nx/js:tsc\n * target, which keeps that name), and every project depending on one — transitively — has\n * `^openapi-generate` in the effective dependsOn of its `build` and `test`. A failure names the\n * nx.json targetDefaults key to edit and the exact line. 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, WiringSourceReader } 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 sources = new WiringSourceReader(context.root);\n const generateWiring = new GenerateWiring(\n projectsConfig.projects,\n projectGraph.dependencies,\n sources.targetDefaults(),\n sources.declaredDependsOn(projectsConfig.projects),\n );\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"]}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* restores a package WITHOUT its documents). The consumer states only what is theirs to decide, in its
|
|
12
12
|
* project.json under the SAME target name, which nx merges over what is inferred here:
|
|
13
13
|
*
|
|
14
|
-
* - `openapi-generate`: `dependsOn` (the
|
|
14
|
+
* - `openapi-generate`: `dependsOn` (`["build"]` — the tsc target whose outputPath it writes into) and
|
|
15
15
|
* `options.manifest` / `options.format`;
|
|
16
16
|
* - `docs-generate`: `options.document` / `options.siteDir` (and optionally `options.prose`).
|
|
17
17
|
*
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* executor the installed plugin lacks breaks the graph load — which is why opting in is a tag and not
|
|
22
22
|
* a hand-written executor target.
|
|
23
23
|
*
|
|
24
|
-
* The
|
|
25
|
-
* `validate-nx-wiring`, not here: this runs while nx builds the graph, where a throw takes the whole
|
|
24
|
+
* The wiring (`openapi-generate` dependsOn `build`, and `^openapi-generate` in every dependent's
|
|
25
|
+
* `build` / `test`) is enforced by `validate-nx-wiring`, not here: this runs while nx builds the graph, where a throw takes the whole
|
|
26
26
|
* workspace down rather than naming one project's fix.
|
|
27
27
|
*/
|
|
28
28
|
import type { TargetConfiguration } from '@nx/devkit';
|
|
@@ -50,7 +50,7 @@ export declare class GenerateTargets {
|
|
|
50
50
|
/** The targets the project's tags opt it into — `{}` for an untagged project. */
|
|
51
51
|
infer(): Record<string, TargetConfiguration>;
|
|
52
52
|
/**
|
|
53
|
-
* `outputs` point into the
|
|
53
|
+
* `outputs` point into the build target's outputPath, read here from the SAME lookup the
|
|
54
54
|
* executor writes with. When the consumer has not stated `dependsOn` yet there is no outputPath to
|
|
55
55
|
* point at, so no outputs are inferred — and the executor's refusal names the missing `dependsOn`.
|
|
56
56
|
*/
|
package/src/generate-targets.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* restores a package WITHOUT its documents). The consumer states only what is theirs to decide, in its
|
|
13
13
|
* project.json under the SAME target name, which nx merges over what is inferred here:
|
|
14
14
|
*
|
|
15
|
-
* - `openapi-generate`: `dependsOn` (the
|
|
15
|
+
* - `openapi-generate`: `dependsOn` (`["build"]` — the tsc target whose outputPath it writes into) and
|
|
16
16
|
* `options.manifest` / `options.format`;
|
|
17
17
|
* - `docs-generate`: `options.document` / `options.siteDir` (and optionally `options.prose`).
|
|
18
18
|
*
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* executor the installed plugin lacks breaks the graph load — which is why opting in is a tag and not
|
|
23
23
|
* a hand-written executor target.
|
|
24
24
|
*
|
|
25
|
-
* The
|
|
26
|
-
* `validate-nx-wiring`, not here: this runs while nx builds the graph, where a throw takes the whole
|
|
25
|
+
* The wiring (`openapi-generate` dependsOn `build`, and `^openapi-generate` in every dependent's
|
|
26
|
+
* `build` / `test`) is enforced by `validate-nx-wiring`, not here: this runs while nx builds the graph, where a throw takes the whole
|
|
27
27
|
* workspace down rather than naming one project's fix.
|
|
28
28
|
*/
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -75,7 +75,7 @@ class GenerateTargets {
|
|
|
75
75
|
return targets;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* `outputs` point into the
|
|
78
|
+
* `outputs` point into the build target's outputPath, read here from the SAME lookup the
|
|
79
79
|
* executor writes with. When the consumer has not stated `dependsOn` yet there is no outputPath to
|
|
80
80
|
* point at, so no outputs are inferred — and the executor's refusal names the missing `dependsOn`.
|
|
81
81
|
*/
|
|
@@ -96,7 +96,7 @@ class GenerateTargets {
|
|
|
96
96
|
outputs,
|
|
97
97
|
metadata: {
|
|
98
98
|
technologies: ['nx'],
|
|
99
|
-
description: 'Generate the OpenAPI documents + one mcp-<ContractClass>-tools.json per MCP contract into the
|
|
99
|
+
description: 'Generate the OpenAPI documents + one mcp-<ContractClass>-tools.json per MCP contract into the build outputPath (tag: generate:openapi)',
|
|
100
100
|
},
|
|
101
101
|
};
|
|
102
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-targets.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/generate-targets.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;;AAGH,oDAA6E;AAC7E,2BAAkC;AAClC,+BAA4B;AAEf,QAAA,oBAAoB,GAAG,kBAAkB,CAAC;AAC1C,QAAA,sBAAsB,GAAG,oBAAoB,CAAC;AAE3D,6EAA6E;AAC7E,MAAa,cAAc;IAEV;IACA;IACA;IAHb,YACa,IAAY,EACZ,IAAuB,EACvB,OAAsB;QAFtB,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAmB;QACvB,YAAO,GAAP,OAAO,CAAe;IAChC,CAAC;CACP;AAND,wCAMC;AAKD,mGAAmG;AACnG,MAAa,oBAAoB;IAC7B,IAAI,CAAC,aAAqB,EAAE,WAAmB,EAAE,WAAmB;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,IAAA,WAAI,EAAC,aAAa,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAsB,CAAC;QACpG,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,IAAI,WAAW,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAC1F,CAAC;CACJ;AALD,oDAKC;AAED,MAAa,eAAe;IAGH;IACA;IAHrB;IACI,uCAAuC;IACtB,WAAmB,EACnB,OAAuB;QADvB,gBAAW,GAAX,WAAW,CAAQ;QACnB,YAAO,GAAP,OAAO,CAAgB;IACzC,CAAC;IAEJ,iFAAiF;IACjF,KAAK;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,8BAAsB,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,4BAAoB,CAAC;YAAE,OAAO,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAwC,EAAE,CAAC;QACxD,OAAO,CAAC,kCAAsB,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACxE,IAAI,IAAI;YAAE,OAAO,CAAC,kCAAsB,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAC5E,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACK,eAAe;QACnB,MAAM,KAAK,GAAG,IAAI,kCAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;aAC9F,YAAY,EAAE,CAAC,KAAK,CAAC;QAC1B,MAAM,OAAO,GAAG,KAAK,KAAK,SAAS;YAC/B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,mBAAmB,KAAK,CAAC,UAAU,gBAAgB;gBACnD,mBAAmB,KAAK,CAAC,UAAU,gBAAgB;gBACnD,mBAAmB,KAAK,CAAC,UAAU,mBAAmB;aACzD,CAAC;QACN,OAAO;YACH,QAAQ,EAAE,gDAAgD;YAC1D,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;YAC/B,OAAO;YACP,QAAQ,EAAE;gBACN,YAAY,EAAE,CAAC,IAAI,CAAC;gBACpB,WAAW,EACP,
|
|
1
|
+
{"version":3,"file":"generate-targets.js","sourceRoot":"","sources":["../../../../../packages/tooling/nx-webpieces-rules/src/generate-targets.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;;AAGH,oDAA6E;AAC7E,2BAAkC;AAClC,+BAA4B;AAEf,QAAA,oBAAoB,GAAG,kBAAkB,CAAC;AAC1C,QAAA,sBAAsB,GAAG,oBAAoB,CAAC;AAE3D,6EAA6E;AAC7E,MAAa,cAAc;IAEV;IACA;IACA;IAHb,YACa,IAAY,EACZ,IAAuB,EACvB,OAAsB;QAFtB,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAmB;QACvB,YAAO,GAAP,OAAO,CAAe;IAChC,CAAC;CACP;AAND,wCAMC;AAKD,mGAAmG;AACnG,MAAa,oBAAoB;IAC7B,IAAI,CAAC,aAAqB,EAAE,WAAmB,EAAE,WAAmB;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,IAAA,WAAI,EAAC,aAAa,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAsB,CAAC;QACpG,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,IAAI,WAAW,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAC1F,CAAC;CACJ;AALD,oDAKC;AAED,MAAa,eAAe;IAGH;IACA;IAHrB;IACI,uCAAuC;IACtB,WAAmB,EACnB,OAAuB;QADvB,gBAAW,GAAX,WAAW,CAAQ;QACnB,YAAO,GAAP,OAAO,CAAgB;IACzC,CAAC;IAEJ,iFAAiF;IACjF,KAAK;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,8BAAsB,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,4BAAoB,CAAC;YAAE,OAAO,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAwC,EAAE,CAAC;QACxD,OAAO,CAAC,kCAAsB,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACxE,IAAI,IAAI;YAAE,OAAO,CAAC,kCAAsB,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAC5E,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACK,eAAe;QACnB,MAAM,KAAK,GAAG,IAAI,kCAAsB,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;aAC9F,YAAY,EAAE,CAAC,KAAK,CAAC;QAC1B,MAAM,OAAO,GAAG,KAAK,KAAK,SAAS;YAC/B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,mBAAmB,KAAK,CAAC,UAAU,gBAAgB;gBACnD,mBAAmB,KAAK,CAAC,UAAU,gBAAgB;gBACnD,mBAAmB,KAAK,CAAC,UAAU,mBAAmB;aACzD,CAAC;QACN,OAAO;YACH,QAAQ,EAAE,gDAAgD;YAC1D,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;YAC/B,OAAO;YACP,QAAQ,EAAE;gBACN,YAAY,EAAE,CAAC,IAAI,CAAC;gBACpB,WAAW,EACP,wIAAwI;aAC/I;SACJ,CAAC;IACN,CAAC;IAEO,YAAY;QAChB,OAAO;YACH,QAAQ,EAAE,6CAA6C;YACvD,SAAS,EAAE,CAAC,kCAAsB,CAAC,cAAc,CAAC;YAClD,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;YAC/B,OAAO,EAAE,CAAC,iCAAiC,CAAC;YAC5C,QAAQ,EAAE;gBACN,YAAY,EAAE,CAAC,IAAI,CAAC;gBACpB,WAAW,EACP,yGAAyG;aAChH;SACJ,CAAC;IACN,CAAC;CACJ;AA5DD,0CA4DC","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` (`[\"build\"]` — the tsc 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 wiring (`openapi-generate` dependsOn `build`, and `^openapi-generate` in every dependent's\n * `build` / `test`) is enforced by `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 build 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 build 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"]}
|
|
@@ -1,47 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
2
|
+
* The wiring a project opting into generated API documents needs, checked by `validate-nx-wiring`
|
|
3
|
+
* on nx's RESOLVED project graph (#1023, correcting #1021). It is nx's own documented codegen shape
|
|
4
|
+
* (https://nx.dev/blog/dotnet-openapi-type-safety):
|
|
4
5
|
*
|
|
5
6
|
* ```
|
|
6
|
-
* <api-lib>:
|
|
7
|
-
* <api-lib>:openapi-generate dependsOn ["
|
|
8
|
-
*
|
|
7
|
+
* <api-lib>:build @nx/js:tsc → its outputPath (unchanged — still named `build`)
|
|
8
|
+
* <api-lib>:openapi-generate dependsOn ["build"] → writes into build's outputPath
|
|
9
|
+
* nx.json targetDefaults every build / test default a dependent runs: dependsOn "^openapi-generate"
|
|
9
10
|
* ```
|
|
10
11
|
*
|
|
11
|
-
* Why
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Why the api library's tsc target must stay `build`: `@nx/js:tsc` decides whether each dependency is
|
|
13
|
+
* BUILDABLE by looking for a target with the SAME NAME as the one running (nx 22,
|
|
14
|
+
* `buildable-libs-utils.js` `calculateProjectDependencies`). A library whose tsc target is renamed
|
|
15
|
+
* `compile` sees every `build`-only dependency as non-buildable, pulls its SOURCE into the compile,
|
|
16
|
+
* and tsc fails with TS6059 "rootDir is expected to contain all source files" (nrwl/nx#18257, closed
|
|
17
|
+
* as outdated, never fixed). #1021's `compile` → `openapi-generate` → noop-`build` split hit exactly
|
|
18
|
+
* that in the first real consumer, so it is no longer the design.
|
|
15
19
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
+
* Why the dependents carry `^openapi-generate`: nx `dependsOn` only points BACKWARD, so generation
|
|
21
|
+
* (which must run AFTER tsc, whose cleanup wipes the outputPath) cannot be hung off the library's own
|
|
22
|
+
* `build`. ONE repo-wide `nx.json` line per targetDefaults entry pulls it in instead — nx skips a
|
|
23
|
+
* dependency that has no `openapi-generate` and keeps walking its dependencies, so a project with no
|
|
24
|
+
* generating library upstream is unaffected, and a transitive dependent is reached through the
|
|
25
|
+
* projects between. A server spec that boots the MCP server reads the library's generated tool
|
|
26
|
+
* catalogs out of its build output, which is why `test` needs it as much as `build` does.
|
|
20
27
|
*/
|
|
21
|
-
import type { ProjectConfiguration } from '@nx/devkit';
|
|
28
|
+
import type { ProjectConfiguration, TargetDependencyConfig } from '@nx/devkit';
|
|
22
29
|
import { RuleFailError } from '@webpieces/rules-config';
|
|
23
|
-
/** One
|
|
30
|
+
/** One wiring defect, and the exact edit that fixes it. Data-only. */
|
|
24
31
|
export declare class GenerateWiringProblem {
|
|
32
|
+
/** The project — or, for an nx.json edit, every project that edit fixes. */
|
|
25
33
|
readonly project: string;
|
|
26
34
|
readonly problem: string;
|
|
27
35
|
readonly cure: string;
|
|
28
|
-
constructor(
|
|
36
|
+
constructor(
|
|
37
|
+
/** The project — or, for an nx.json edit, every project that edit fixes. */
|
|
38
|
+
project: string, problem: string, cure: string);
|
|
29
39
|
}
|
|
30
40
|
/** The one field of an nx graph dependency this check reads. Data-only. */
|
|
31
41
|
export declare class ProjectDependency {
|
|
32
42
|
readonly target: string;
|
|
33
43
|
constructor(target: string);
|
|
34
44
|
}
|
|
45
|
+
/** One `nx.json` targetDefaults entry — only the field this check reads. Data-only. */
|
|
46
|
+
export declare class TargetDefault {
|
|
47
|
+
readonly dependsOn: readonly DependsOnEntry[] | undefined;
|
|
48
|
+
constructor(dependsOn: readonly DependsOnEntry[] | undefined);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Which targets each project's OWN project.json declares a `dependsOn` for. nx does not merge
|
|
52
|
+
* `dependsOn` — a project-level one REPLACES the targetDefaults one — so this decides whether the
|
|
53
|
+
* cure is an nx.json edit or a project.json edit. Data-only.
|
|
54
|
+
*/
|
|
55
|
+
export declare class DeclaredDependsOn {
|
|
56
|
+
readonly byProject: Readonly<Record<string, readonly string[]>>;
|
|
57
|
+
constructor(byProject: Readonly<Record<string, readonly string[]>>);
|
|
58
|
+
declares(project: string, target: string): boolean;
|
|
59
|
+
}
|
|
60
|
+
type DependsOnEntry = string | TargetDependencyConfig;
|
|
61
|
+
/** Reads the two on-disk facts the resolved graph has already merged away. */
|
|
62
|
+
export declare class WiringSourceReader {
|
|
63
|
+
private readonly workspaceRoot;
|
|
64
|
+
constructor(workspaceRoot: string);
|
|
65
|
+
/** `nx.json` targetDefaults, as written. A missing or unreadable nx.json has none. */
|
|
66
|
+
targetDefaults(): Record<string, TargetDefault>;
|
|
67
|
+
/** Which targets each project's own project.json declares `dependsOn` for. */
|
|
68
|
+
declaredDependsOn(projects: Readonly<Record<string, ProjectConfiguration>>): DeclaredDependsOn;
|
|
69
|
+
/** A JSON file's contents, or undefined when it is absent — a project inferred from package.json has no project.json. */
|
|
70
|
+
private readJson;
|
|
71
|
+
}
|
|
35
72
|
export declare class GenerateWiring {
|
|
36
73
|
/** nx's RESOLVED project configurations — targetDefaults and inferred targets merged in. */
|
|
37
74
|
private readonly projects;
|
|
38
75
|
/** The project graph's dependencies, by source project. */
|
|
39
76
|
private readonly dependencies;
|
|
77
|
+
/** `nx.json` targetDefaults, as written — to name the key a cure edits. */
|
|
78
|
+
private readonly targetDefaults;
|
|
79
|
+
/** Which targets each project.json declares its own dependsOn for. */
|
|
80
|
+
private readonly declared;
|
|
40
81
|
constructor(
|
|
41
82
|
/** nx's RESOLVED project configurations — targetDefaults and inferred targets merged in. */
|
|
42
83
|
projects: Readonly<Record<string, ProjectConfiguration>>,
|
|
43
84
|
/** The project graph's dependencies, by source project. */
|
|
44
|
-
dependencies: Readonly<Record<string, readonly ProjectDependency[]
|
|
85
|
+
dependencies: Readonly<Record<string, readonly ProjectDependency[]>>,
|
|
86
|
+
/** `nx.json` targetDefaults, as written — to name the key a cure edits. */
|
|
87
|
+
targetDefaults: Readonly<Record<string, TargetDefault>>,
|
|
88
|
+
/** Which targets each project.json declares its own dependsOn for. */
|
|
89
|
+
declared: DeclaredDependsOn);
|
|
45
90
|
/** The projects tagged to generate API documents. */
|
|
46
91
|
generating(): string[];
|
|
47
92
|
/** The problems as ONE structured failure: each problem in the message, each fix as an Option. */
|
|
@@ -54,8 +99,26 @@ export declare class GenerateWiring {
|
|
|
54
99
|
* past the shape check (and breaks the graph load the day the installed plugin lacks the executor).
|
|
55
100
|
*/
|
|
56
101
|
private handWritten;
|
|
102
|
+
/** (a) `openapi-generate` dependsOn exactly the project's own `build`, the tsc target. */
|
|
57
103
|
private shapeOf;
|
|
104
|
+
/**
|
|
105
|
+
* (b) Every project that depends — transitively — on a generating library has `^openapi-generate`
|
|
106
|
+
* in the EFFECTIVE dependsOn of its `build` and `test`. One problem per EDIT, listing every project
|
|
107
|
+
* that edit fixes: an nx.json targetDefaults key usually covers the whole repo at once.
|
|
108
|
+
*/
|
|
58
109
|
private dependentsOf;
|
|
110
|
+
/** The generating libraries `name` reaches through the dependency graph, itself excluded. */
|
|
111
|
+
private generatingUpstreamOf;
|
|
112
|
+
/** Where the missing `^openapi-generate` goes: the project.json that overrides, or the nx.json key. */
|
|
113
|
+
private missingFor;
|
|
114
|
+
/**
|
|
115
|
+
* The targetDefaults key nx reads for this target — nx's own precedence
|
|
116
|
+
* (`readTargetDefaultsForTarget`): the EXECUTOR key when one exists, else the target NAME, else the
|
|
117
|
+
* longest glob key matching the name.
|
|
118
|
+
*/
|
|
119
|
+
private defaultKeyFor;
|
|
120
|
+
private render;
|
|
59
121
|
/** Whether `target` dependsOn `wanted` — `^x` for the upstream form, `x` for a sibling. */
|
|
60
122
|
private names;
|
|
61
123
|
}
|
|
124
|
+
export {};
|
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* The
|
|
4
|
-
*
|
|
3
|
+
* The wiring a project opting into generated API documents needs, checked by `validate-nx-wiring`
|
|
4
|
+
* on nx's RESOLVED project graph (#1023, correcting #1021). It is nx's own documented codegen shape
|
|
5
|
+
* (https://nx.dev/blog/dotnet-openapi-type-safety):
|
|
5
6
|
*
|
|
6
7
|
* ```
|
|
7
|
-
* <api-lib>:
|
|
8
|
-
* <api-lib>:openapi-generate dependsOn ["
|
|
9
|
-
*
|
|
8
|
+
* <api-lib>:build @nx/js:tsc → its outputPath (unchanged — still named `build`)
|
|
9
|
+
* <api-lib>:openapi-generate dependsOn ["build"] → writes into build's outputPath
|
|
10
|
+
* nx.json targetDefaults every build / test default a dependent runs: dependsOn "^openapi-generate"
|
|
10
11
|
* ```
|
|
11
12
|
*
|
|
12
|
-
* Why
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Why the api library's tsc target must stay `build`: `@nx/js:tsc` decides whether each dependency is
|
|
14
|
+
* BUILDABLE by looking for a target with the SAME NAME as the one running (nx 22,
|
|
15
|
+
* `buildable-libs-utils.js` `calculateProjectDependencies`). A library whose tsc target is renamed
|
|
16
|
+
* `compile` sees every `build`-only dependency as non-buildable, pulls its SOURCE into the compile,
|
|
17
|
+
* and tsc fails with TS6059 "rootDir is expected to contain all source files" (nrwl/nx#18257, closed
|
|
18
|
+
* as outdated, never fixed). #1021's `compile` → `openapi-generate` → noop-`build` split hit exactly
|
|
19
|
+
* that in the first real consumer, so it is no longer the design.
|
|
16
20
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
+
* Why the dependents carry `^openapi-generate`: nx `dependsOn` only points BACKWARD, so generation
|
|
22
|
+
* (which must run AFTER tsc, whose cleanup wipes the outputPath) cannot be hung off the library's own
|
|
23
|
+
* `build`. ONE repo-wide `nx.json` line per targetDefaults entry pulls it in instead — nx skips a
|
|
24
|
+
* dependency that has no `openapi-generate` and keeps walking its dependencies, so a project with no
|
|
25
|
+
* generating library upstream is unaffected, and a transitive dependent is reached through the
|
|
26
|
+
* projects between. A server spec that boots the MCP server reads the library's generated tool
|
|
27
|
+
* catalogs out of its build output, which is why `test` needs it as much as `build` does.
|
|
21
28
|
*/
|
|
22
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.GenerateWiring = exports.ProjectDependency = exports.GenerateWiringProblem = void 0;
|
|
30
|
+
exports.GenerateWiring = exports.WiringSourceReader = exports.DeclaredDependsOn = exports.TargetDefault = exports.ProjectDependency = exports.GenerateWiringProblem = void 0;
|
|
31
|
+
const tslib_1 = require("tslib");
|
|
32
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
33
|
+
const path = tslib_1.__importStar(require("path"));
|
|
24
34
|
const core_util_1 = require("@webpieces/core-util");
|
|
25
35
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
26
36
|
const generate_targets_1 = require("../../generate-targets");
|
|
27
|
-
/** One
|
|
37
|
+
/** One wiring defect, and the exact edit that fixes it. Data-only. */
|
|
28
38
|
class GenerateWiringProblem {
|
|
29
39
|
project;
|
|
30
40
|
problem;
|
|
31
41
|
cure;
|
|
32
|
-
constructor(
|
|
42
|
+
constructor(
|
|
43
|
+
/** The project — or, for an nx.json edit, every project that edit fixes. */
|
|
44
|
+
project, problem, cure) {
|
|
33
45
|
this.project = project;
|
|
34
46
|
this.problem = problem;
|
|
35
47
|
this.cure = cure;
|
|
@@ -44,21 +56,107 @@ class ProjectDependency {
|
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
exports.ProjectDependency = ProjectDependency;
|
|
59
|
+
/** One `nx.json` targetDefaults entry — only the field this check reads. Data-only. */
|
|
60
|
+
class TargetDefault {
|
|
61
|
+
dependsOn;
|
|
62
|
+
constructor(dependsOn) {
|
|
63
|
+
this.dependsOn = dependsOn;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.TargetDefault = TargetDefault;
|
|
67
|
+
/**
|
|
68
|
+
* Which targets each project's OWN project.json declares a `dependsOn` for. nx does not merge
|
|
69
|
+
* `dependsOn` — a project-level one REPLACES the targetDefaults one — so this decides whether the
|
|
70
|
+
* cure is an nx.json edit or a project.json edit. Data-only.
|
|
71
|
+
*/
|
|
72
|
+
class DeclaredDependsOn {
|
|
73
|
+
byProject;
|
|
74
|
+
constructor(byProject) {
|
|
75
|
+
this.byProject = byProject;
|
|
76
|
+
}
|
|
77
|
+
declares(project, target) {
|
|
78
|
+
return (this.byProject[project] ?? []).includes(target);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.DeclaredDependsOn = DeclaredDependsOn;
|
|
82
|
+
/** Reads the two on-disk facts the resolved graph has already merged away. */
|
|
83
|
+
class WiringSourceReader {
|
|
84
|
+
workspaceRoot;
|
|
85
|
+
constructor(workspaceRoot) {
|
|
86
|
+
this.workspaceRoot = workspaceRoot;
|
|
87
|
+
}
|
|
88
|
+
/** `nx.json` targetDefaults, as written. A missing or unreadable nx.json has none. */
|
|
89
|
+
targetDefaults() {
|
|
90
|
+
const raw = this.readJson(path.join(this.workspaceRoot, 'nx.json'));
|
|
91
|
+
const defaults = {};
|
|
92
|
+
for (const [key, entry] of Object.entries(raw?.targetDefaults ?? {})) {
|
|
93
|
+
defaults[key] = new TargetDefault(entry.dependsOn);
|
|
94
|
+
}
|
|
95
|
+
return defaults;
|
|
96
|
+
}
|
|
97
|
+
/** Which targets each project's own project.json declares `dependsOn` for. */
|
|
98
|
+
declaredDependsOn(projects) {
|
|
99
|
+
const byProject = {};
|
|
100
|
+
for (const [name, project] of Object.entries(projects)) {
|
|
101
|
+
const raw = this.readJson(path.join(this.workspaceRoot, project.root, 'project.json'));
|
|
102
|
+
const targets = raw?.targets ?? {};
|
|
103
|
+
byProject[name] = Object.keys(targets).filter((target) => targets[target].dependsOn !== undefined);
|
|
104
|
+
}
|
|
105
|
+
return new DeclaredDependsOn(byProject);
|
|
106
|
+
}
|
|
107
|
+
/** A JSON file's contents, or undefined when it is absent — a project inferred from package.json has no project.json. */
|
|
108
|
+
readJson(file) {
|
|
109
|
+
if (!fs.existsSync(file))
|
|
110
|
+
return undefined;
|
|
111
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.WiringSourceReader = WiringSourceReader;
|
|
47
115
|
/** The executors the plugin infers from a tag — a project.json naming one by hand is a second opt-in. */
|
|
48
116
|
const INFERRED_EXECUTORS = [
|
|
49
117
|
'@webpieces/nx-webpieces-rules:openapi-generate',
|
|
50
118
|
'@webpieces/nx-webpieces-rules:docs-generate',
|
|
51
119
|
];
|
|
120
|
+
/** The tsc target an api library keeps, and `openapi-generate` dependsOn. */
|
|
121
|
+
const BUILD_TARGET = 'build';
|
|
122
|
+
/** The targets a dependent runs that must pull generation in. */
|
|
123
|
+
const DEPENDENT_TARGETS = ['build', 'test'];
|
|
124
|
+
const UPSTREAM_GENERATE = `^${core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET}`;
|
|
125
|
+
/** One missing `^openapi-generate`, before grouping by the edit that fixes it. Data-only. */
|
|
126
|
+
class MissingGenerate {
|
|
127
|
+
project;
|
|
128
|
+
target;
|
|
129
|
+
upstream;
|
|
130
|
+
where;
|
|
131
|
+
cure;
|
|
132
|
+
constructor(project, target, upstream,
|
|
133
|
+
/** Where the fix goes — the grouping key. */
|
|
134
|
+
where, cure) {
|
|
135
|
+
this.project = project;
|
|
136
|
+
this.target = target;
|
|
137
|
+
this.upstream = upstream;
|
|
138
|
+
this.where = where;
|
|
139
|
+
this.cure = cure;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
52
142
|
class GenerateWiring {
|
|
53
143
|
projects;
|
|
54
144
|
dependencies;
|
|
145
|
+
targetDefaults;
|
|
146
|
+
declared;
|
|
55
147
|
constructor(
|
|
56
148
|
/** nx's RESOLVED project configurations — targetDefaults and inferred targets merged in. */
|
|
57
149
|
projects,
|
|
58
150
|
/** The project graph's dependencies, by source project. */
|
|
59
|
-
dependencies
|
|
151
|
+
dependencies,
|
|
152
|
+
/** `nx.json` targetDefaults, as written — to name the key a cure edits. */
|
|
153
|
+
targetDefaults,
|
|
154
|
+
/** Which targets each project.json declares its own dependsOn for. */
|
|
155
|
+
declared) {
|
|
60
156
|
this.projects = projects;
|
|
61
157
|
this.dependencies = dependencies;
|
|
158
|
+
this.targetDefaults = targetDefaults;
|
|
159
|
+
this.declared = declared;
|
|
62
160
|
}
|
|
63
161
|
/** The projects tagged to generate API documents. */
|
|
64
162
|
generating() {
|
|
@@ -71,8 +169,8 @@ class GenerateWiring {
|
|
|
71
169
|
}
|
|
72
170
|
/** The problems as ONE structured failure: each problem in the message, each fix as an Option. */
|
|
73
171
|
failure(problems) {
|
|
74
|
-
return new rules_config_1.RuleFailError('nx-wiring', 'A project generating API documents is not wired so that
|
|
75
|
-
problems.map((each) => ` ${each.project}: ${each.problem}`).join('\n'), undefined, undefined, problems.map((each) => new rules_config_1.Option(
|
|
172
|
+
return new rules_config_1.RuleFailError('nx-wiring', 'A project generating API documents, or one depending on it, is not wired so that build and test generate them:\n' +
|
|
173
|
+
problems.map((each) => ` ${each.project}: ${each.problem}`).join('\n'), undefined, undefined, problems.map((each) => new rules_config_1.Option(each.cure, true)));
|
|
76
174
|
}
|
|
77
175
|
/** Render {@link failure} through the one human renderer, as validate-nx-wiring's report. */
|
|
78
176
|
report(problems) {
|
|
@@ -101,53 +199,120 @@ class GenerateWiring {
|
|
|
101
199
|
continue;
|
|
102
200
|
problems.push(new GenerateWiringProblem(name, `targets.${targetName} names the executor ${target.executor} by hand, and the project has no ` +
|
|
103
201
|
`"${generate_targets_1.GENERATE_OPENAPI_TAG}" / "${generate_targets_1.GENERATE_DOCS_SITE_TAG}" tag. Opting in is the tag; the plugin ` +
|
|
104
|
-
'infers the executor.',
|
|
202
|
+
'infers the executor.', `${name}: add "${targetName === core_util_1.GeneratedApiDocsLayout.DOCS_TARGET ? generate_targets_1.GENERATE_DOCS_SITE_TAG : generate_targets_1.GENERATE_OPENAPI_TAG}" ` +
|
|
105
203
|
`to "tags" in ${project.root}/project.json and delete the "executor" line of targets.${targetName}.`));
|
|
106
204
|
}
|
|
107
205
|
}
|
|
108
206
|
return problems;
|
|
109
207
|
}
|
|
208
|
+
/** (a) `openapi-generate` dependsOn exactly the project's own `build`, the tsc target. */
|
|
110
209
|
shapeOf(name) {
|
|
111
210
|
const project = this.projects[name];
|
|
112
211
|
const where = `${project.root}/project.json`;
|
|
113
|
-
const
|
|
114
|
-
const lookup = new core_util_1.GeneratedApiDocsLayout(project.root, name, targets).outputTarget();
|
|
212
|
+
const lookup = new core_util_1.GeneratedApiDocsLayout(project.root, name, project.targets ?? {}).outputTarget();
|
|
115
213
|
if (lookup.found === undefined) {
|
|
116
|
-
return [new GenerateWiringProblem(name, lookup.problem.problem, lookup.problem.cure)];
|
|
214
|
+
return [new GenerateWiringProblem(name, lookup.problem.problem, `${name}: ${lookup.problem.cure}`)];
|
|
117
215
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (!buildOk) {
|
|
128
|
-
problems.push(new GenerateWiringProblem(name, `${name}:build must be an nx:noop over "${compileName}" and "${core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET}" ` +
|
|
129
|
-
`— it is what every consumer's ^build asks for, so it is what pulls generation in — and it is ` +
|
|
130
|
-
`${build === undefined ? 'missing' : `${build.executor ?? '(no executor)'} dependsOn ${JSON.stringify(build.dependsOn ?? [])}`}.`, `Set targets.build in ${where} to ` +
|
|
131
|
-
`{ "executor": "nx:noop", "dependsOn": ["${compileName}", "${core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET}"] }, ` +
|
|
132
|
-
`moving the @nx/js:tsc options to targets.${compileName}.`));
|
|
133
|
-
}
|
|
134
|
-
return problems;
|
|
216
|
+
if (lookup.found.targetName === BUILD_TARGET)
|
|
217
|
+
return [];
|
|
218
|
+
const other = lookup.found.targetName;
|
|
219
|
+
return [new GenerateWiringProblem(name, `${name}:${core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET} dependsOn "${other}", and must dependsOn "${BUILD_TARGET}" — ` +
|
|
220
|
+
`the api library's @nx/js:tsc target keeps the name build. @nx/js:tsc treats a dependency as buildable ` +
|
|
221
|
+
`only when it has a target of the SAME name as the one running, so a tsc target renamed "${other}" ` +
|
|
222
|
+
'compiles every build-only dependency from SOURCE and fails with TS6059 (nrwl/nx#18257).', `${name}: in ${where}, name the @nx/js:tsc target "${BUILD_TARGET}" again (delete any nx:noop "build" that ` +
|
|
223
|
+
`wrapped it) and set targets.${core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET}.dependsOn to ["${BUILD_TARGET}"]. ` +
|
|
224
|
+
`Dependents pull generation in with "${UPSTREAM_GENERATE}" in nx.json targetDefaults.`)];
|
|
135
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* (b) Every project that depends — transitively — on a generating library has `^openapi-generate`
|
|
228
|
+
* in the EFFECTIVE dependsOn of its `build` and `test`. One problem per EDIT, listing every project
|
|
229
|
+
* that edit fixes: an nx.json targetDefaults key usually covers the whole repo at once.
|
|
230
|
+
*/
|
|
136
231
|
dependentsOf(generating) {
|
|
137
|
-
const
|
|
232
|
+
const byEdit = new Map();
|
|
138
233
|
for (const name of Object.keys(this.projects).sort()) {
|
|
139
|
-
const upstream =
|
|
140
|
-
|
|
141
|
-
.filter((target) => generating.has(target) && target !== name);
|
|
142
|
-
const test = this.projects[name].targets?.['test'];
|
|
143
|
-
if (upstream.length === 0 || test === undefined || this.names(test, '^build'))
|
|
234
|
+
const upstream = this.generatingUpstreamOf(name, generating);
|
|
235
|
+
if (upstream.length === 0)
|
|
144
236
|
continue;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
237
|
+
for (const targetName of DEPENDENT_TARGETS) {
|
|
238
|
+
const target = this.projects[name].targets?.[targetName];
|
|
239
|
+
if (target === undefined || this.names(target, UPSTREAM_GENERATE))
|
|
240
|
+
continue;
|
|
241
|
+
const missing = this.missingFor(name, targetName, target, upstream);
|
|
242
|
+
const group = byEdit.get(missing.where) ?? [];
|
|
243
|
+
group.push(missing);
|
|
244
|
+
byEdit.set(missing.where, group);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const problems = [];
|
|
248
|
+
for (const group of byEdit.values()) {
|
|
249
|
+
const first = group[0];
|
|
250
|
+
const who = group.map((each) => `${each.project}:${each.target}`).join(', ');
|
|
251
|
+
const libraries = [...new Set(group.flatMap((each) => [...each.upstream]))].sort();
|
|
252
|
+
problems.push(new GenerateWiringProblem(who, `depends on ${libraries.join(', ')}, which generate${libraries.length === 1 ? 's' : ''} API documents and MCP ` +
|
|
253
|
+
`tool catalogs into ${libraries.length === 1 ? 'its' : 'their'} build output, and the effective dependsOn ` +
|
|
254
|
+
`does not name "${UPSTREAM_GENERATE}" — so nothing generates them before this runs (a spec that boots ` +
|
|
255
|
+
'the MCP server would read catalogs that were never written, or stale ones).', first.cure));
|
|
148
256
|
}
|
|
149
257
|
return problems;
|
|
150
258
|
}
|
|
259
|
+
/** The generating libraries `name` reaches through the dependency graph, itself excluded. */
|
|
260
|
+
generatingUpstreamOf(name, generating) {
|
|
261
|
+
const found = new Set();
|
|
262
|
+
const seen = new Set([name]);
|
|
263
|
+
const queue = [name];
|
|
264
|
+
while (queue.length > 0) {
|
|
265
|
+
const current = queue.shift();
|
|
266
|
+
for (const dependency of this.dependencies[current] ?? []) {
|
|
267
|
+
if (seen.has(dependency.target))
|
|
268
|
+
continue;
|
|
269
|
+
seen.add(dependency.target);
|
|
270
|
+
if (generating.has(dependency.target))
|
|
271
|
+
found.add(dependency.target);
|
|
272
|
+
queue.push(dependency.target);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return [...found].sort();
|
|
276
|
+
}
|
|
277
|
+
/** Where the missing `^openapi-generate` goes: the project.json that overrides, or the nx.json key. */
|
|
278
|
+
missingFor(name, targetName, target, upstream) {
|
|
279
|
+
const effective = (target.dependsOn ?? []);
|
|
280
|
+
if (this.declared.declares(name, targetName)) {
|
|
281
|
+
const where = `${this.projects[name].root}/project.json`;
|
|
282
|
+
return new MissingGenerate(name, targetName, upstream, `${where}#${targetName}`, `In ${where}, targets.${targetName}.dependsOn REPLACES nx.json's (nx does not merge dependsOn), so add ` +
|
|
283
|
+
`"${UPSTREAM_GENERATE}" there: "dependsOn": ${this.render([...effective, UPSTREAM_GENERATE])}`);
|
|
284
|
+
}
|
|
285
|
+
const key = this.defaultKeyFor(targetName, target.executor);
|
|
286
|
+
const existing = key === undefined ? [] : [...(this.targetDefaults[key].dependsOn ?? [])];
|
|
287
|
+
const editKey = key ?? targetName;
|
|
288
|
+
const line = `"${editKey}": { "dependsOn": ${this.render([...existing, UPSTREAM_GENERATE])} }`;
|
|
289
|
+
return new MissingGenerate(name, targetName, upstream, `nx.json#${editKey}`, key === undefined
|
|
290
|
+
? `In nx.json, add the targetDefaults entry ${line} — no targetDefaults key governs ${targetName} yet.`
|
|
291
|
+
: `In nx.json, set targetDefaults["${editKey}"].dependsOn so the entry reads ${line} ` +
|
|
292
|
+
'(keep its other fields).');
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The targetDefaults key nx reads for this target — nx's own precedence
|
|
296
|
+
* (`readTargetDefaultsForTarget`): the EXECUTOR key when one exists, else the target NAME, else the
|
|
297
|
+
* longest glob key matching the name.
|
|
298
|
+
*/
|
|
299
|
+
defaultKeyFor(targetName, executor) {
|
|
300
|
+
if (executor !== undefined && this.targetDefaults[executor] !== undefined)
|
|
301
|
+
return executor;
|
|
302
|
+
if (this.targetDefaults[targetName] !== undefined)
|
|
303
|
+
return targetName;
|
|
304
|
+
let best;
|
|
305
|
+
for (const key of Object.keys(this.targetDefaults)) {
|
|
306
|
+
if (!/[*?[\]{}!]/.test(key) || !(0, rules_config_1.matchesAnyGlob)(targetName, [key]))
|
|
307
|
+
continue;
|
|
308
|
+
if (best === undefined || key.length > best.length)
|
|
309
|
+
best = key;
|
|
310
|
+
}
|
|
311
|
+
return best;
|
|
312
|
+
}
|
|
313
|
+
render(entries) {
|
|
314
|
+
return `[${entries.map((entry) => JSON.stringify(entry)).join(', ')}]`;
|
|
315
|
+
}
|
|
151
316
|
/** Whether `target` dependsOn `wanted` — `^x` for the upstream form, `x` for a sibling. */
|
|
152
317
|
names(target, wanted) {
|
|
153
318
|
const upstream = wanted.startsWith('^');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-wiring.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-docs/generate-wiring.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The build SHAPE a project opting into generated API documents must have, checked by\n * `validate-nx-wiring` (#1021):\n *\n * ```\n * <api-lib>:compile @nx/js:tsc → its outputPath (dependsOn ^build)\n * <api-lib>:openapi-generate dependsOn [\"compile\"] → writes into compile's outputPath\n * <api-lib>:build nx:noop, dependsOn [\"compile\", \"openapi-generate\"]\n * ```\n *\n * Why that shape and no other: nx `dependsOn` only points BACKWARD, generation must run AFTER tsc\n * (whose cleanup wipes the outputPath), and every consumer only ever asks for `^build` — servers,\n * Dockerfiles (`nx run-many -t build -p lang-server`). With `build` as a noop over both, every existing\n * `^build` pulls generation in with zero consumer change.\n *\n * And one rule for the projects that DEPEND on such a library: their `test` must dependsOn `^build`.\n * A server spec that boots the MCP server reads the library's generated tool catalogs out of its build\n * output (`McpToolCatalog.fromPackages` on a workspace source directory), so a test run that did not\n * build the library first fails on a missing file — or, worse, passes against a stale one.\n */\n\nimport type { ProjectConfiguration, TargetConfiguration, TargetDependencyConfig } from '@nx/devkit';\nimport { GeneratedApiDocsLayout } from '@webpieces/core-util';\nimport { Option, RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { GENERATE_DOCS_SITE_TAG, GENERATE_OPENAPI_TAG } from '../../generate-targets';\n\n/** One project's wiring defect, and the exact project.json edit that fixes it. Data-only. */\nexport class GenerateWiringProblem {\n constructor(\n readonly project: string,\n readonly problem: string,\n readonly cure: string,\n ) {}\n}\n\n/** The one field of an nx graph dependency this check reads. Data-only. */\nexport class ProjectDependency {\n constructor(readonly target: string) {}\n}\n\ntype DependsOnEntry = string | TargetDependencyConfig;\n\n/** The executors the plugin infers from a tag — a project.json naming one by hand is a second opt-in. */\nconst INFERRED_EXECUTORS: readonly string[] = [\n '@webpieces/nx-webpieces-rules:openapi-generate',\n '@webpieces/nx-webpieces-rules:docs-generate',\n];\n\nexport class GenerateWiring {\n constructor(\n /** nx's RESOLVED project configurations — targetDefaults and inferred targets merged in. */\n private readonly projects: Readonly<Record<string, ProjectConfiguration>>,\n /** The project graph's dependencies, by source project. */\n private readonly dependencies: Readonly<Record<string, readonly ProjectDependency[]>>,\n ) {}\n\n /** The projects tagged to generate API documents. */\n generating(): string[] {\n return Object.keys(this.projects)\n .filter((name: string) => {\n const tags = this.projects[name]!.tags ?? [];\n return tags.includes(GENERATE_OPENAPI_TAG) || tags.includes(GENERATE_DOCS_SITE_TAG);\n })\n .sort();\n }\n\n /** The problems as ONE structured failure: each problem in the message, each fix as an Option. */\n failure(problems: readonly GenerateWiringProblem[]): RuleFailError {\n return new RuleFailError(\n 'nx-wiring',\n 'A project generating API documents is not wired so that ^build generates them:\\n' +\n problems.map((each: GenerateWiringProblem) => ` ${each.project}: ${each.problem}`).join('\\n'),\n undefined,\n undefined,\n problems.map((each: GenerateWiringProblem) => new Option(`${each.project}: ${each.cure}`, true)),\n );\n }\n\n /** Render {@link failure} through the one human renderer, as validate-nx-wiring's report. */\n report(problems: readonly GenerateWiringProblem[]): void {\n console.error(`\\n❌ ${renderRuleFailForHuman(this.failure(problems))}\\n`);\n }\n\n problems(): GenerateWiringProblem[] {\n const generating = this.generating();\n const problems: GenerateWiringProblem[] = this.handWritten(new Set(generating));\n for (const name of generating) problems.push(...this.shapeOf(name));\n problems.push(...this.dependentsOf(new Set(generating)));\n return problems;\n }\n\n /**\n * An UNTAGGED project naming an inferred executor by hand: a second way to opt in, which also slips\n * past the shape check (and breaks the graph load the day the installed plugin lacks the executor).\n */\n private handWritten(generating: ReadonlySet<string>): GenerateWiringProblem[] {\n const problems: GenerateWiringProblem[] = [];\n for (const name of Object.keys(this.projects).sort()) {\n if (generating.has(name)) continue;\n const project = this.projects[name]!;\n for (const [targetName, target] of Object.entries(project.targets ?? {})) {\n if (target.executor === undefined || !INFERRED_EXECUTORS.includes(target.executor)) continue;\n problems.push(new GenerateWiringProblem(\n name,\n `targets.${targetName} names the executor ${target.executor} by hand, and the project has no ` +\n `\"${GENERATE_OPENAPI_TAG}\" / \"${GENERATE_DOCS_SITE_TAG}\" tag. Opting in is the tag; the plugin ` +\n 'infers the executor.',\n `Add \"${targetName === GeneratedApiDocsLayout.DOCS_TARGET ? GENERATE_DOCS_SITE_TAG : GENERATE_OPENAPI_TAG}\" ` +\n `to \"tags\" in ${project.root}/project.json and delete the \"executor\" line of targets.${targetName}.`,\n ));\n }\n }\n return problems;\n }\n\n private shapeOf(name: string): GenerateWiringProblem[] {\n const project = this.projects[name]!;\n const where = `${project.root}/project.json`;\n const targets = project.targets ?? {};\n const lookup = new GeneratedApiDocsLayout(project.root, name, targets).outputTarget();\n if (lookup.found === undefined) {\n return [new GenerateWiringProblem(name, lookup.problem!.problem, lookup.problem!.cure)];\n }\n const compileName = lookup.found.targetName;\n const problems: GenerateWiringProblem[] = [];\n if (!this.names(targets[compileName], '^build')) {\n problems.push(new GenerateWiringProblem(\n name,\n `${name}:${compileName} (the target ${GeneratedApiDocsLayout.OPENAPI_TARGET} writes into) does ` +\n 'not dependsOn \"^build\", so upstream libraries are not built before it compiles.',\n `Add \"dependsOn\": [\"^build\"] to targets.${compileName} in ${where}.`,\n ));\n }\n const build = targets['build'];\n const buildOk = build !== undefined && build.executor === 'nx:noop' &&\n this.names(build, compileName) && this.names(build, GeneratedApiDocsLayout.OPENAPI_TARGET);\n if (!buildOk) {\n problems.push(new GenerateWiringProblem(\n name,\n `${name}:build must be an nx:noop over \"${compileName}\" and \"${GeneratedApiDocsLayout.OPENAPI_TARGET}\" ` +\n `— it is what every consumer's ^build asks for, so it is what pulls generation in — and it is ` +\n `${build === undefined ? 'missing' : `${build.executor ?? '(no executor)'} dependsOn ${JSON.stringify(build.dependsOn ?? [])}`}.`,\n `Set targets.build in ${where} to ` +\n `{ \"executor\": \"nx:noop\", \"dependsOn\": [\"${compileName}\", \"${GeneratedApiDocsLayout.OPENAPI_TARGET}\"] }, ` +\n `moving the @nx/js:tsc options to targets.${compileName}.`,\n ));\n }\n return problems;\n }\n\n private dependentsOf(generating: ReadonlySet<string>): GenerateWiringProblem[] {\n const problems: GenerateWiringProblem[] = [];\n for (const name of Object.keys(this.projects).sort()) {\n const upstream = (this.dependencies[name] ?? [])\n .map((dependency: ProjectDependency) => dependency.target)\n .filter((target: string) => generating.has(target) && target !== name);\n const test = this.projects[name]!.targets?.['test'];\n if (upstream.length === 0 || test === undefined || this.names(test, '^build')) continue;\n problems.push(new GenerateWiringProblem(\n name,\n `${name} depends on ${[...new Set(upstream)].sort().join(', ')}, which generate${upstream.length === 1 ? 's' : ''} ` +\n 'API documents and MCP tool catalogs into their build output, and its test target does not ' +\n 'dependsOn \"^build\" — a spec that boots the MCP server would read catalogs that were never built.',\n `Add \"dependsOn\": [\"^build\"] to targets.test in ${this.projects[name]!.root}/project.json.`,\n ));\n }\n return problems;\n }\n\n /** Whether `target` dependsOn `wanted` — `^x` for the upstream form, `x` for a sibling. */\n private names(target: TargetConfiguration | undefined, wanted: string): boolean {\n const upstream = wanted.startsWith('^');\n const name = upstream ? wanted.slice(1) : wanted;\n return (target?.dependsOn ?? []).some((entry: DependsOnEntry) => {\n if (typeof entry === 'string') return entry === wanted;\n return entry.target === name && (entry.dependencies === true) === upstream;\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"generate-wiring.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-docs/generate-wiring.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The wiring a project opting into generated API documents needs, checked by `validate-nx-wiring`\n * on nx's RESOLVED project graph (#1023, correcting #1021). It is nx's own documented codegen shape\n * (https://nx.dev/blog/dotnet-openapi-type-safety):\n *\n * ```\n * <api-lib>:build @nx/js:tsc → its outputPath (unchanged — still named `build`)\n * <api-lib>:openapi-generate dependsOn [\"build\"] → writes into build's outputPath\n * nx.json targetDefaults every build / test default a dependent runs: dependsOn \"^openapi-generate\"\n * ```\n *\n * Why the api library's tsc target must stay `build`: `@nx/js:tsc` decides whether each dependency is\n * BUILDABLE by looking for a target with the SAME NAME as the one running (nx 22,\n * `buildable-libs-utils.js` `calculateProjectDependencies`). A library whose tsc target is renamed\n * `compile` sees every `build`-only dependency as non-buildable, pulls its SOURCE into the compile,\n * and tsc fails with TS6059 \"rootDir is expected to contain all source files\" (nrwl/nx#18257, closed\n * as outdated, never fixed). #1021's `compile` → `openapi-generate` → noop-`build` split hit exactly\n * that in the first real consumer, so it is no longer the design.\n *\n * Why the dependents carry `^openapi-generate`: nx `dependsOn` only points BACKWARD, so generation\n * (which must run AFTER tsc, whose cleanup wipes the outputPath) cannot be hung off the library's own\n * `build`. ONE repo-wide `nx.json` line per targetDefaults entry pulls it in instead — nx skips a\n * dependency that has no `openapi-generate` and keeps walking its dependencies, so a project with no\n * generating library upstream is unaffected, and a transitive dependent is reached through the\n * projects between. A server spec that boots the MCP server reads the library's generated tool\n * catalogs out of its build output, which is why `test` needs it as much as `build` does.\n */\n\nimport type { ProjectConfiguration, TargetConfiguration, TargetDependencyConfig } from '@nx/devkit';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { GeneratedApiDocsLayout } from '@webpieces/core-util';\nimport { Option, RuleFailError, matchesAnyGlob, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { GENERATE_DOCS_SITE_TAG, GENERATE_OPENAPI_TAG } from '../../generate-targets';\n\n/** One wiring defect, and the exact edit that fixes it. Data-only. */\nexport class GenerateWiringProblem {\n constructor(\n /** The project — or, for an nx.json edit, every project that edit fixes. */\n readonly project: string,\n readonly problem: string,\n readonly cure: string,\n ) {}\n}\n\n/** The one field of an nx graph dependency this check reads. Data-only. */\nexport class ProjectDependency {\n constructor(readonly target: string) {}\n}\n\n/** One `nx.json` targetDefaults entry — only the field this check reads. Data-only. */\nexport class TargetDefault {\n constructor(readonly dependsOn: readonly DependsOnEntry[] | undefined) {}\n}\n\n/**\n * Which targets each project's OWN project.json declares a `dependsOn` for. nx does not merge\n * `dependsOn` — a project-level one REPLACES the targetDefaults one — so this decides whether the\n * cure is an nx.json edit or a project.json edit. Data-only.\n */\nexport class DeclaredDependsOn {\n constructor(readonly byProject: Readonly<Record<string, readonly string[]>>) {}\n\n declares(project: string, target: string): boolean {\n return (this.byProject[project] ?? []).includes(target);\n }\n}\n\ntype DependsOnEntry = string | TargetDependencyConfig;\n\n/** The fields of a project.json / nx.json this check reads, as they sit in the file. */\ntype RawTarget = { dependsOn?: DependsOnEntry[] };\ntype RawTargets = { targets?: Record<string, RawTarget> };\ntype RawNxJson = { targetDefaults?: Record<string, RawTarget> };\n\n/** Reads the two on-disk facts the resolved graph has already merged away. */\nexport class WiringSourceReader {\n constructor(private readonly workspaceRoot: string) {}\n\n /** `nx.json` targetDefaults, as written. A missing or unreadable nx.json has none. */\n targetDefaults(): Record<string, TargetDefault> {\n const raw = this.readJson(path.join(this.workspaceRoot, 'nx.json')) as RawNxJson | undefined;\n const defaults: Record<string, TargetDefault> = {};\n for (const [key, entry] of Object.entries(raw?.targetDefaults ?? {})) {\n defaults[key] = new TargetDefault(entry.dependsOn);\n }\n return defaults;\n }\n\n /** Which targets each project's own project.json declares `dependsOn` for. */\n declaredDependsOn(projects: Readonly<Record<string, ProjectConfiguration>>): DeclaredDependsOn {\n const byProject: Record<string, string[]> = {};\n for (const [name, project] of Object.entries(projects)) {\n const raw = this.readJson(path.join(this.workspaceRoot, project.root, 'project.json')) as RawTargets | undefined;\n const targets = raw?.targets ?? {};\n byProject[name] = Object.keys(targets).filter((target: string) => targets[target]!.dependsOn !== undefined);\n }\n return new DeclaredDependsOn(byProject);\n }\n\n /** A JSON file's contents, or undefined when it is absent — a project inferred from package.json has no project.json. */\n private readJson(file: string): RawTargets | RawNxJson | undefined {\n if (!fs.existsSync(file)) return undefined;\n return JSON.parse(fs.readFileSync(file, 'utf8')) as RawTargets | RawNxJson;\n }\n}\n\n/** The executors the plugin infers from a tag — a project.json naming one by hand is a second opt-in. */\nconst INFERRED_EXECUTORS: readonly string[] = [\n '@webpieces/nx-webpieces-rules:openapi-generate',\n '@webpieces/nx-webpieces-rules:docs-generate',\n];\n\n/** The tsc target an api library keeps, and `openapi-generate` dependsOn. */\nconst BUILD_TARGET = 'build';\n/** The targets a dependent runs that must pull generation in. */\nconst DEPENDENT_TARGETS: readonly string[] = ['build', 'test'];\nconst UPSTREAM_GENERATE = `^${GeneratedApiDocsLayout.OPENAPI_TARGET}`;\n\n/** One missing `^openapi-generate`, before grouping by the edit that fixes it. Data-only. */\nclass MissingGenerate {\n constructor(\n readonly project: string,\n readonly target: string,\n readonly upstream: readonly string[],\n /** Where the fix goes — the grouping key. */\n readonly where: string,\n readonly cure: string,\n ) {}\n}\n\nexport class GenerateWiring {\n constructor(\n /** nx's RESOLVED project configurations — targetDefaults and inferred targets merged in. */\n private readonly projects: Readonly<Record<string, ProjectConfiguration>>,\n /** The project graph's dependencies, by source project. */\n private readonly dependencies: Readonly<Record<string, readonly ProjectDependency[]>>,\n /** `nx.json` targetDefaults, as written — to name the key a cure edits. */\n private readonly targetDefaults: Readonly<Record<string, TargetDefault>>,\n /** Which targets each project.json declares its own dependsOn for. */\n private readonly declared: DeclaredDependsOn,\n ) {}\n\n /** The projects tagged to generate API documents. */\n generating(): string[] {\n return Object.keys(this.projects)\n .filter((name: string) => {\n const tags = this.projects[name]!.tags ?? [];\n return tags.includes(GENERATE_OPENAPI_TAG) || tags.includes(GENERATE_DOCS_SITE_TAG);\n })\n .sort();\n }\n\n /** The problems as ONE structured failure: each problem in the message, each fix as an Option. */\n failure(problems: readonly GenerateWiringProblem[]): RuleFailError {\n return new RuleFailError(\n 'nx-wiring',\n 'A project generating API documents, or one depending on it, is not wired so that build and test generate them:\\n' +\n problems.map((each: GenerateWiringProblem) => ` ${each.project}: ${each.problem}`).join('\\n'),\n undefined,\n undefined,\n problems.map((each: GenerateWiringProblem) => new Option(each.cure, true)),\n );\n }\n\n /** Render {@link failure} through the one human renderer, as validate-nx-wiring's report. */\n report(problems: readonly GenerateWiringProblem[]): void {\n console.error(`\\n❌ ${renderRuleFailForHuman(this.failure(problems))}\\n`);\n }\n\n problems(): GenerateWiringProblem[] {\n const generating = this.generating();\n const problems: GenerateWiringProblem[] = this.handWritten(new Set(generating));\n for (const name of generating) problems.push(...this.shapeOf(name));\n problems.push(...this.dependentsOf(new Set(generating)));\n return problems;\n }\n\n /**\n * An UNTAGGED project naming an inferred executor by hand: a second way to opt in, which also slips\n * past the shape check (and breaks the graph load the day the installed plugin lacks the executor).\n */\n private handWritten(generating: ReadonlySet<string>): GenerateWiringProblem[] {\n const problems: GenerateWiringProblem[] = [];\n for (const name of Object.keys(this.projects).sort()) {\n if (generating.has(name)) continue;\n const project = this.projects[name]!;\n for (const [targetName, target] of Object.entries(project.targets ?? {})) {\n if (target.executor === undefined || !INFERRED_EXECUTORS.includes(target.executor)) continue;\n problems.push(new GenerateWiringProblem(\n name,\n `targets.${targetName} names the executor ${target.executor} by hand, and the project has no ` +\n `\"${GENERATE_OPENAPI_TAG}\" / \"${GENERATE_DOCS_SITE_TAG}\" tag. Opting in is the tag; the plugin ` +\n 'infers the executor.',\n `${name}: add \"${targetName === GeneratedApiDocsLayout.DOCS_TARGET ? GENERATE_DOCS_SITE_TAG : GENERATE_OPENAPI_TAG}\" ` +\n `to \"tags\" in ${project.root}/project.json and delete the \"executor\" line of targets.${targetName}.`,\n ));\n }\n }\n return problems;\n }\n\n /** (a) `openapi-generate` dependsOn exactly the project's own `build`, the tsc target. */\n private shapeOf(name: string): GenerateWiringProblem[] {\n const project = this.projects[name]!;\n const where = `${project.root}/project.json`;\n const lookup = new GeneratedApiDocsLayout(project.root, name, project.targets ?? {}).outputTarget();\n if (lookup.found === undefined) {\n return [new GenerateWiringProblem(name, lookup.problem!.problem, `${name}: ${lookup.problem!.cure}`)];\n }\n if (lookup.found.targetName === BUILD_TARGET) return [];\n const other = lookup.found.targetName;\n return [new GenerateWiringProblem(\n name,\n `${name}:${GeneratedApiDocsLayout.OPENAPI_TARGET} dependsOn \"${other}\", and must dependsOn \"${BUILD_TARGET}\" — ` +\n `the api library's @nx/js:tsc target keeps the name build. @nx/js:tsc treats a dependency as buildable ` +\n `only when it has a target of the SAME name as the one running, so a tsc target renamed \"${other}\" ` +\n 'compiles every build-only dependency from SOURCE and fails with TS6059 (nrwl/nx#18257).',\n `${name}: in ${where}, name the @nx/js:tsc target \"${BUILD_TARGET}\" again (delete any nx:noop \"build\" that ` +\n `wrapped it) and set targets.${GeneratedApiDocsLayout.OPENAPI_TARGET}.dependsOn to [\"${BUILD_TARGET}\"]. ` +\n `Dependents pull generation in with \"${UPSTREAM_GENERATE}\" in nx.json targetDefaults.`,\n )];\n }\n\n /**\n * (b) Every project that depends — transitively — on a generating library has `^openapi-generate`\n * in the EFFECTIVE dependsOn of its `build` and `test`. One problem per EDIT, listing every project\n * that edit fixes: an nx.json targetDefaults key usually covers the whole repo at once.\n */\n private dependentsOf(generating: ReadonlySet<string>): GenerateWiringProblem[] {\n const byEdit = new Map<string, MissingGenerate[]>();\n for (const name of Object.keys(this.projects).sort()) {\n const upstream = this.generatingUpstreamOf(name, generating);\n if (upstream.length === 0) continue;\n for (const targetName of DEPENDENT_TARGETS) {\n const target = this.projects[name]!.targets?.[targetName];\n if (target === undefined || this.names(target, UPSTREAM_GENERATE)) continue;\n const missing = this.missingFor(name, targetName, target, upstream);\n const group = byEdit.get(missing.where) ?? [];\n group.push(missing);\n byEdit.set(missing.where, group);\n }\n }\n const problems: GenerateWiringProblem[] = [];\n for (const group of byEdit.values()) {\n const first = group[0]!;\n const who = group.map((each: MissingGenerate) => `${each.project}:${each.target}`).join(', ');\n const libraries = [...new Set(group.flatMap((each: MissingGenerate) => [...each.upstream]))].sort();\n problems.push(new GenerateWiringProblem(\n who,\n `depends on ${libraries.join(', ')}, which generate${libraries.length === 1 ? 's' : ''} API documents and MCP ` +\n `tool catalogs into ${libraries.length === 1 ? 'its' : 'their'} build output, and the effective dependsOn ` +\n `does not name \"${UPSTREAM_GENERATE}\" — so nothing generates them before this runs (a spec that boots ` +\n 'the MCP server would read catalogs that were never written, or stale ones).',\n first.cure,\n ));\n }\n return problems;\n }\n\n /** The generating libraries `name` reaches through the dependency graph, itself excluded. */\n private generatingUpstreamOf(name: string, generating: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n const seen = new Set<string>([name]);\n const queue: string[] = [name];\n while (queue.length > 0) {\n const current = queue.shift()!;\n for (const dependency of this.dependencies[current] ?? []) {\n if (seen.has(dependency.target)) continue;\n seen.add(dependency.target);\n if (generating.has(dependency.target)) found.add(dependency.target);\n queue.push(dependency.target);\n }\n }\n return [...found].sort();\n }\n\n /** Where the missing `^openapi-generate` goes: the project.json that overrides, or the nx.json key. */\n private missingFor(\n name: string,\n targetName: string,\n target: TargetConfiguration,\n upstream: readonly string[],\n ): MissingGenerate {\n const effective = (target.dependsOn ?? []) as DependsOnEntry[];\n if (this.declared.declares(name, targetName)) {\n const where = `${this.projects[name]!.root}/project.json`;\n return new MissingGenerate(\n name, targetName, upstream, `${where}#${targetName}`,\n `In ${where}, targets.${targetName}.dependsOn REPLACES nx.json's (nx does not merge dependsOn), so add ` +\n `\"${UPSTREAM_GENERATE}\" there: \"dependsOn\": ${this.render([...effective, UPSTREAM_GENERATE])}`,\n );\n }\n const key = this.defaultKeyFor(targetName, target.executor);\n const existing = key === undefined ? [] : [...(this.targetDefaults[key]!.dependsOn ?? [])];\n const editKey = key ?? targetName;\n const line = `\"${editKey}\": { \"dependsOn\": ${this.render([...existing, UPSTREAM_GENERATE])} }`;\n return new MissingGenerate(\n name, targetName, upstream, `nx.json#${editKey}`,\n key === undefined\n ? `In nx.json, add the targetDefaults entry ${line} — no targetDefaults key governs ${targetName} yet.`\n : `In nx.json, set targetDefaults[\"${editKey}\"].dependsOn so the entry reads ${line} ` +\n '(keep its other fields).',\n );\n }\n\n /**\n * The targetDefaults key nx reads for this target — nx's own precedence\n * (`readTargetDefaultsForTarget`): the EXECUTOR key when one exists, else the target NAME, else the\n * longest glob key matching the name.\n */\n private defaultKeyFor(targetName: string, executor: string | undefined): string | undefined {\n if (executor !== undefined && this.targetDefaults[executor] !== undefined) return executor;\n if (this.targetDefaults[targetName] !== undefined) return targetName;\n let best: string | undefined;\n for (const key of Object.keys(this.targetDefaults)) {\n if (!/[*?[\\]{}!]/.test(key) || !matchesAnyGlob(targetName, [key])) continue;\n if (best === undefined || key.length > best.length) best = key;\n }\n return best;\n }\n\n private render(entries: readonly DependsOnEntry[]): string {\n return `[${entries.map((entry: DependsOnEntry) => JSON.stringify(entry)).join(', ')}]`;\n }\n\n /** Whether `target` dependsOn `wanted` — `^x` for the upstream form, `x` for a sibling. */\n private names(target: TargetConfiguration | undefined, wanted: string): boolean {\n const upstream = wanted.startsWith('^');\n const name = upstream ? wanted.slice(1) : wanted;\n return (target?.dependsOn ?? []).some((entry: DependsOnEntry) => {\n if (typeof entry === 'string') return entry === wanted;\n return entry.target === name && (entry.dependencies === true) === upstream;\n });\n }\n}\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Both halves read the project's own declarations and never supply a value of their own:
|
|
6
6
|
*
|
|
7
7
|
* - the documents' directory is the `outputPath` of the target `openapi-generate` dependsOn — the api
|
|
8
|
-
* library's `
|
|
8
|
+
* library's `build` (tsc) step, which writes the directory the package is packed from, so the
|
|
9
9
|
* documents ship INSIDE the published package. It is ASKED of nx through `GeneratedApiDocsLayout`
|
|
10
10
|
* (`@webpieces/core-util`), the same lookup `McpToolCatalog.fromPackages` reads with, and never
|
|
11
11
|
* assumed: this repo builds into a workspace-root `dist/apps/...`, another consumer builds into a
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Both halves read the project's own declarations and never supply a value of their own:
|
|
7
7
|
*
|
|
8
8
|
* - the documents' directory is the `outputPath` of the target `openapi-generate` dependsOn — the api
|
|
9
|
-
* library's `
|
|
9
|
+
* library's `build` (tsc) step, which writes the directory the package is packed from, so the
|
|
10
10
|
* documents ship INSIDE the published package. It is ASKED of nx through `GeneratedApiDocsLayout`
|
|
11
11
|
* (`@webpieces/core-util`), the same lookup `McpToolCatalog.fromPackages` reads with, and never
|
|
12
12
|
* assumed: this repo builds into a workspace-root `dist/apps/...`, another consumer builds into a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator-target.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-docs/generator-target.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared half of the `openapi-generate` and `docs-generate` executors: WHERE a generated document\n * goes, and the proof that the target is wired so that nx both orders and caches it correctly.\n *\n * Both halves read the project's own declarations and never supply a value of their own:\n *\n * - the documents' directory is the `outputPath` of the target `openapi-generate` dependsOn — the api\n * library's `compile` (tsc) step, which writes the directory the package is packed from, so the\n * documents ship INSIDE the published package. It is ASKED of nx through `GeneratedApiDocsLayout`\n * (`@webpieces/core-util`), the same lookup `McpToolCatalog.fromPackages` reads with, and never\n * assumed: this repo builds into a workspace-root `dist/apps/...`, another consumer builds into a\n * project-local `<project>/dist`, and hardcoding either one — or the target's NAME — generates the\n * document somewhere the package is not packed from;\n * - the ordering and the cache are the target's own `dependsOn` and `outputs`, which the executor\n * checks rather than trusts, because both failures are silent: without the `dependsOn` edge, tsc's\n * clean of `outputPath` races the write (the document vanishes and a dependent test dies on ENOENT,\n * intermittently, only in CI), and `outputs` that miss a written file make every cache hit restore a\n * package without that file.\n */\n\nimport type { ExecutorContext, TargetConfiguration, TargetDependencyConfig } from '@nx/devkit';\nimport { GeneratedApiDocsLayout } from '@webpieces/core-util';\nimport { Option, RuleFailError, matchesAnyGlob } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\n/** What `dependsOn` may name: a sibling target by name, or nx's object form of the same. */\ntype DependsOnEntry = string | TargetDependencyConfig;\n\nexport class GeneratorTarget {\n constructor(\n /** The executor's name, which is what every refusal is reported under. */\n readonly ruleName: string,\n readonly workspaceRoot: string,\n readonly projectName: string,\n /** Workspace-relative. */\n readonly projectRoot: string,\n readonly targetName: string,\n readonly target: TargetConfiguration,\n readonly targets: Record<string, TargetConfiguration>,\n ) {}\n\n // webpieces-disable no-function-outside-class -- static factory of this class\n static of(ruleName: string, context: ExecutorContext): GeneratorTarget {\n const projectName = context.projectName ?? '';\n const project = context.projectsConfigurations?.projects[projectName];\n const targetName = context.targetName ?? '';\n const target = project?.targets?.[targetName];\n if (project === undefined || target === undefined) {\n // nx hands every executor its own project and target; their absence is our bug, not the consumer's.\n throw new Error(`${ruleName}: nx did not describe project '${projectName}' target '${targetName}'`);\n }\n return new GeneratorTarget(\n ruleName, context.root, projectName, project.root, targetName, target, project.targets ?? {});\n }\n\n /**\n * Absolute path to the directory the documents live in: the `outputPath` of the target\n * `openapi-generate` dependsOn — the directory the package is packed from.\n */\n documentsDir(): string {\n const lookup = new GeneratedApiDocsLayout(this.projectRoot, this.projectName, this.targets).outputTarget();\n if (lookup.found === undefined) {\n throw new RuleFailError(\n this.ruleName,\n `${lookup.problem!.problem} The documents go into the build's own output directory — the ` +\n 'one the package is packed from — and are never assumed to be ./dist.',\n undefined,\n undefined,\n [new Option(lookup.problem!.cure, true)],\n );\n }\n return path.resolve(this.workspaceRoot, lookup.found.outputPath);\n }\n\n /** Absolute `<projectRoot>/<dir>`, refusing anything that is not strictly inside the project. */\n insideProject(dir: string, optionName: string): string {\n const projectAbs = path.resolve(this.workspaceRoot, this.projectRoot);\n const resolved = path.resolve(projectAbs, dir);\n const relative = path.relative(projectAbs, resolved);\n if (relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)) return resolved;\n throw new RuleFailError(\n this.ruleName,\n `${this.projectName}:${this.targetName} options.${optionName} is '${dir}', which is not a directory ` +\n `strictly inside ${this.projectRoot}. It is emptied and rewritten on every run, so it may only ` +\n 'name a directory of the project\\'s own.',\n undefined,\n undefined,\n [new Option(`Set options.${optionName} to a project-relative directory, e.g. \"generated-docs\"`, true)],\n );\n }\n\n /** Refuse unless this target `dependsOn` the sibling `required` target. */\n assertDependsOn(required: string): void {\n const entries: readonly DependsOnEntry[] = this.target.dependsOn ?? [];\n if (entries.some((entry: DependsOnEntry) => this.namesSibling(entry, required))) return;\n throw new RuleFailError(\n this.ruleName,\n `${this.projectName}:${this.targetName} does not declare dependsOn \"${required}\". It reads what ` +\n `${required} writes, so without that edge nx may run it before or alongside ${required} and ` +\n `render a stale or missing document. That fails intermittently and mostly in CI, which is ` +\n `why it is refused here instead.`,\n undefined,\n undefined,\n [new Option(`Add \"dependsOn\": [\"${required}\"] to the ${this.targetName} target in ${this.projectRoot}/project.json`, true)],\n );\n }\n\n /** Refuse unless every written file is covered by the target's declared `outputs`, so a cache hit restores it. */\n assertOutputsCover(writtenAbs: readonly string[]): void {\n const patterns = (this.target.outputs ?? []).map((output: string) => this.interpolate(output));\n const uncovered = writtenAbs\n .map((file: string) => path.relative(this.workspaceRoot, file).split(path.sep).join('/'))\n .filter((file: string) => !matchesAnyGlob(file, patterns));\n if (uncovered.length === 0) return;\n throw new RuleFailError(\n this.ruleName,\n `${this.projectName}:${this.targetName} wrote files its declared outputs do not cover, so a ` +\n `cache hit would restore a package WITHOUT them:\\n` +\n uncovered.map((file: string) => ` ${file}`).join('\\n'),\n undefined,\n undefined,\n [new Option(\n `Cover them in the ${this.targetName} target's \"outputs\" in ${this.projectRoot}/project.json, ` +\n `e.g. \"{workspaceRoot}/${path.posix.dirname(uncovered[0]!)}/<the files>\"`,\n true,\n )],\n );\n }\n\n /** A workspace-relative path the consumer stated, from `options`, required and never defaulted. */\n requiredOption(value: string | undefined, name: string): string {\n if (typeof value === 'string' && value.trim() !== '') return value;\n throw new RuleFailError(\n this.ruleName,\n `${this.projectName}:${this.targetName} has no options.${name}. It is required and has no default.`,\n undefined,\n undefined,\n [new Option(`Set options.${name} on the ${this.targetName} target in ${this.projectRoot}/project.json`, true)],\n );\n }\n\n private namesSibling(entry: DependsOnEntry, required: string): boolean {\n if (typeof entry === 'string') return entry === required;\n const projects = entry.projects;\n const self = projects === undefined || projects === 'self' ||\n (Array.isArray(projects) && projects.length === 1 && projects[0] === this.projectName);\n return entry.target === required && entry.dependencies !== true && self;\n }\n\n /** nx's own tokens, so a path written the way nx accepts it resolves the way nx resolves it. */\n private interpolate(text: string): string {\n return text\n .replace(/\\{workspaceRoot\\}\\/?/g, '')\n .replace(/\\{projectRoot\\}/g, this.projectRoot)\n .replace(/\\{projectName\\}/g, this.projectName)\n .replace(/\\{options\\.([A-Za-z0-9_]+)\\}/g, (whole: string, key: string): string => {\n const value = this.target.options?.[key];\n return typeof value === 'string' ? value : whole;\n });\n }\n}\n\n/**\n * Generate into a STAGING directory, then publish into the output directory — so the executor knows\n * exactly which files the run produced (for {@link GeneratorTarget.assertOutputsCover}) without\n * parsing a generator's console output, and a failed run leaves the output directory untouched.\n */\nexport class StagedOutput {\n publish(stagingDir: string, destDir: string): string[] {\n const written: string[] = [];\n const copy = (from: string, to: string): void => {\n fs.mkdirSync(to, { recursive: true });\n for (const entry of fs.readdirSync(from, { withFileTypes: true })) {\n const source = path.join(from, entry.name);\n const target = path.join(to, entry.name);\n if (entry.isDirectory()) {\n copy(source, target);\n } else {\n fs.copyFileSync(source, target);\n written.push(target);\n }\n }\n };\n copy(stagingDir, destDir);\n return written.sort();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"generator-target.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-docs/generator-target.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared half of the `openapi-generate` and `docs-generate` executors: WHERE a generated document\n * goes, and the proof that the target is wired so that nx both orders and caches it correctly.\n *\n * Both halves read the project's own declarations and never supply a value of their own:\n *\n * - the documents' directory is the `outputPath` of the target `openapi-generate` dependsOn — the api\n * library's `build` (tsc) step, which writes the directory the package is packed from, so the\n * documents ship INSIDE the published package. It is ASKED of nx through `GeneratedApiDocsLayout`\n * (`@webpieces/core-util`), the same lookup `McpToolCatalog.fromPackages` reads with, and never\n * assumed: this repo builds into a workspace-root `dist/apps/...`, another consumer builds into a\n * project-local `<project>/dist`, and hardcoding either one — or the target's NAME — generates the\n * document somewhere the package is not packed from;\n * - the ordering and the cache are the target's own `dependsOn` and `outputs`, which the executor\n * checks rather than trusts, because both failures are silent: without the `dependsOn` edge, tsc's\n * clean of `outputPath` races the write (the document vanishes and a dependent test dies on ENOENT,\n * intermittently, only in CI), and `outputs` that miss a written file make every cache hit restore a\n * package without that file.\n */\n\nimport type { ExecutorContext, TargetConfiguration, TargetDependencyConfig } from '@nx/devkit';\nimport { GeneratedApiDocsLayout } from '@webpieces/core-util';\nimport { Option, RuleFailError, matchesAnyGlob } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\n/** What `dependsOn` may name: a sibling target by name, or nx's object form of the same. */\ntype DependsOnEntry = string | TargetDependencyConfig;\n\nexport class GeneratorTarget {\n constructor(\n /** The executor's name, which is what every refusal is reported under. */\n readonly ruleName: string,\n readonly workspaceRoot: string,\n readonly projectName: string,\n /** Workspace-relative. */\n readonly projectRoot: string,\n readonly targetName: string,\n readonly target: TargetConfiguration,\n readonly targets: Record<string, TargetConfiguration>,\n ) {}\n\n // webpieces-disable no-function-outside-class -- static factory of this class\n static of(ruleName: string, context: ExecutorContext): GeneratorTarget {\n const projectName = context.projectName ?? '';\n const project = context.projectsConfigurations?.projects[projectName];\n const targetName = context.targetName ?? '';\n const target = project?.targets?.[targetName];\n if (project === undefined || target === undefined) {\n // nx hands every executor its own project and target; their absence is our bug, not the consumer's.\n throw new Error(`${ruleName}: nx did not describe project '${projectName}' target '${targetName}'`);\n }\n return new GeneratorTarget(\n ruleName, context.root, projectName, project.root, targetName, target, project.targets ?? {});\n }\n\n /**\n * Absolute path to the directory the documents live in: the `outputPath` of the target\n * `openapi-generate` dependsOn — the directory the package is packed from.\n */\n documentsDir(): string {\n const lookup = new GeneratedApiDocsLayout(this.projectRoot, this.projectName, this.targets).outputTarget();\n if (lookup.found === undefined) {\n throw new RuleFailError(\n this.ruleName,\n `${lookup.problem!.problem} The documents go into the build's own output directory — the ` +\n 'one the package is packed from — and are never assumed to be ./dist.',\n undefined,\n undefined,\n [new Option(lookup.problem!.cure, true)],\n );\n }\n return path.resolve(this.workspaceRoot, lookup.found.outputPath);\n }\n\n /** Absolute `<projectRoot>/<dir>`, refusing anything that is not strictly inside the project. */\n insideProject(dir: string, optionName: string): string {\n const projectAbs = path.resolve(this.workspaceRoot, this.projectRoot);\n const resolved = path.resolve(projectAbs, dir);\n const relative = path.relative(projectAbs, resolved);\n if (relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)) return resolved;\n throw new RuleFailError(\n this.ruleName,\n `${this.projectName}:${this.targetName} options.${optionName} is '${dir}', which is not a directory ` +\n `strictly inside ${this.projectRoot}. It is emptied and rewritten on every run, so it may only ` +\n 'name a directory of the project\\'s own.',\n undefined,\n undefined,\n [new Option(`Set options.${optionName} to a project-relative directory, e.g. \"generated-docs\"`, true)],\n );\n }\n\n /** Refuse unless this target `dependsOn` the sibling `required` target. */\n assertDependsOn(required: string): void {\n const entries: readonly DependsOnEntry[] = this.target.dependsOn ?? [];\n if (entries.some((entry: DependsOnEntry) => this.namesSibling(entry, required))) return;\n throw new RuleFailError(\n this.ruleName,\n `${this.projectName}:${this.targetName} does not declare dependsOn \"${required}\". It reads what ` +\n `${required} writes, so without that edge nx may run it before or alongside ${required} and ` +\n `render a stale or missing document. That fails intermittently and mostly in CI, which is ` +\n `why it is refused here instead.`,\n undefined,\n undefined,\n [new Option(`Add \"dependsOn\": [\"${required}\"] to the ${this.targetName} target in ${this.projectRoot}/project.json`, true)],\n );\n }\n\n /** Refuse unless every written file is covered by the target's declared `outputs`, so a cache hit restores it. */\n assertOutputsCover(writtenAbs: readonly string[]): void {\n const patterns = (this.target.outputs ?? []).map((output: string) => this.interpolate(output));\n const uncovered = writtenAbs\n .map((file: string) => path.relative(this.workspaceRoot, file).split(path.sep).join('/'))\n .filter((file: string) => !matchesAnyGlob(file, patterns));\n if (uncovered.length === 0) return;\n throw new RuleFailError(\n this.ruleName,\n `${this.projectName}:${this.targetName} wrote files its declared outputs do not cover, so a ` +\n `cache hit would restore a package WITHOUT them:\\n` +\n uncovered.map((file: string) => ` ${file}`).join('\\n'),\n undefined,\n undefined,\n [new Option(\n `Cover them in the ${this.targetName} target's \"outputs\" in ${this.projectRoot}/project.json, ` +\n `e.g. \"{workspaceRoot}/${path.posix.dirname(uncovered[0]!)}/<the files>\"`,\n true,\n )],\n );\n }\n\n /** A workspace-relative path the consumer stated, from `options`, required and never defaulted. */\n requiredOption(value: string | undefined, name: string): string {\n if (typeof value === 'string' && value.trim() !== '') return value;\n throw new RuleFailError(\n this.ruleName,\n `${this.projectName}:${this.targetName} has no options.${name}. It is required and has no default.`,\n undefined,\n undefined,\n [new Option(`Set options.${name} on the ${this.targetName} target in ${this.projectRoot}/project.json`, true)],\n );\n }\n\n private namesSibling(entry: DependsOnEntry, required: string): boolean {\n if (typeof entry === 'string') return entry === required;\n const projects = entry.projects;\n const self = projects === undefined || projects === 'self' ||\n (Array.isArray(projects) && projects.length === 1 && projects[0] === this.projectName);\n return entry.target === required && entry.dependencies !== true && self;\n }\n\n /** nx's own tokens, so a path written the way nx accepts it resolves the way nx resolves it. */\n private interpolate(text: string): string {\n return text\n .replace(/\\{workspaceRoot\\}\\/?/g, '')\n .replace(/\\{projectRoot\\}/g, this.projectRoot)\n .replace(/\\{projectName\\}/g, this.projectName)\n .replace(/\\{options\\.([A-Za-z0-9_]+)\\}/g, (whole: string, key: string): string => {\n const value = this.target.options?.[key];\n return typeof value === 'string' ? value : whole;\n });\n }\n}\n\n/**\n * Generate into a STAGING directory, then publish into the output directory — so the executor knows\n * exactly which files the run produced (for {@link GeneratorTarget.assertOutputsCover}) without\n * parsing a generator's console output, and a failed run leaves the output directory untouched.\n */\nexport class StagedOutput {\n publish(stagingDir: string, destDir: string): string[] {\n const written: string[] = [];\n const copy = (from: string, to: string): void => {\n fs.mkdirSync(to, { recursive: true });\n for (const entry of fs.readdirSync(from, { withFileTypes: true })) {\n const source = path.join(from, entry.name);\n const target = path.join(to, entry.name);\n if (entry.isDirectory()) {\n copy(source, target);\n } else {\n fs.copyFileSync(source, target);\n written.push(target);\n }\n }\n };\n copy(stagingDir, destDir);\n return written.sort();\n }\n}\n"]}
|