@webpieces/nx-webpieces-rules 0.4.810 → 0.4.812

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/executors.json +10 -0
  2. package/package.json +8 -7
  3. package/src/executors/docs-generate/executor.d.ts +48 -0
  4. package/src/executors/docs-generate/executor.js +104 -0
  5. package/src/executors/docs-generate/executor.js.map +1 -0
  6. package/src/executors/docs-generate/schema.json +21 -0
  7. package/src/executors/openapi-generate/executor.d.ts +51 -0
  8. package/src/executors/openapi-generate/executor.js +100 -0
  9. package/src/executors/openapi-generate/executor.js.map +1 -0
  10. package/src/executors/openapi-generate/schema.json +18 -0
  11. package/src/executors/validate-nx-wiring/executor.d.ts +4 -0
  12. package/src/executors/validate-nx-wiring/executor.js +14 -1
  13. package/src/executors/validate-nx-wiring/executor.js.map +1 -1
  14. package/src/generate-targets.d.ts +59 -0
  15. package/src/generate-targets.js +118 -0
  16. package/src/generate-targets.js.map +1 -0
  17. package/src/lib/api-docs/consumer-bin-resolver.d.ts +62 -0
  18. package/src/lib/api-docs/consumer-bin-resolver.js +148 -0
  19. package/src/lib/api-docs/consumer-bin-resolver.js.map +1 -0
  20. package/src/lib/api-docs/generate-wiring.d.ts +61 -0
  21. package/src/lib/api-docs/generate-wiring.js +163 -0
  22. package/src/lib/api-docs/generate-wiring.js.map +1 -0
  23. package/src/lib/api-docs/generator-package.d.ts +23 -0
  24. package/src/lib/api-docs/generator-package.js +32 -0
  25. package/src/lib/api-docs/generator-package.js.map +1 -0
  26. package/src/lib/api-docs/generator-runner.d.ts +21 -0
  27. package/src/lib/api-docs/generator-runner.js +38 -0
  28. package/src/lib/api-docs/generator-runner.js.map +1 -0
  29. package/src/lib/api-docs/generator-target.d.ts +61 -0
  30. package/src/lib/api-docs/generator-target.js +161 -0
  31. package/src/lib/api-docs/generator-target.js.map +1 -0
  32. package/src/lib/api-docs/json-value.d.ts +6 -0
  33. package/src/lib/api-docs/json-value.js +3 -0
  34. package/src/lib/api-docs/json-value.js.map +1 -0
  35. package/src/plugin.d.ts +2 -0
  36. package/src/plugin.js +15 -2
  37. package/src/plugin.js.map +1 -1
package/executors.json CHANGED
@@ -150,6 +150,16 @@
150
150
  "schema": "./src/executors/di-graph-generate/schema.json",
151
151
  "description": "Per-project: generate the Inversify DI dependency DAG into design.json + design.md at the project root"
152
152
  },
153
+ "openapi-generate": {
154
+ "implementation": "./src/executors/openapi-generate/executor",
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 compile target outputPath, with the consumer's own @webpieces/openapi-generator"
157
+ },
158
+ "docs-generate": {
159
+ "implementation": "./src/executors/docs-generate/executor",
160
+ "schema": "./src/executors/docs-generate/schema.json",
161
+ "description": "Per-project, inferred from the generate:docs-site tag: render the static API reference site from a generated document into <projectRoot>/<siteDir>, with the consumer's own @webpieces/docs-site"
162
+ },
153
163
  "validate-nx-wiring": {
154
164
  "implementation": "./src/executors/validate-nx-wiring/executor",
155
165
  "schema": "./src/executors/validate-nx-wiring/schema.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.4.810",
3
+ "version": "0.4.812",
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,12 +18,13 @@
18
18
  "README.md"
19
19
  ],
20
20
  "dependencies": {
21
- "@webpieces/ai-hook-rules": "0.4.810",
22
- "@webpieces/api-doc-model": "0.4.810",
23
- "@webpieces/code-rules": "0.4.810",
24
- "@webpieces/eslint-rules": "0.4.810",
25
- "@webpieces/pr-gate": "0.4.810",
26
- "@webpieces/rules-config": "0.4.810",
21
+ "@webpieces/ai-hook-rules": "0.4.812",
22
+ "@webpieces/api-doc-model": "0.4.812",
23
+ "@webpieces/code-rules": "0.4.812",
24
+ "@webpieces/core-util": "0.4.812",
25
+ "@webpieces/eslint-rules": "0.4.812",
26
+ "@webpieces/pr-gate": "0.4.812",
27
+ "@webpieces/rules-config": "0.4.812",
27
28
  "madge": "8.0.0"
28
29
  },
29
30
  "peerDependencies": {
@@ -0,0 +1,48 @@
1
+ /**
2
+ * docs-generate Executor
3
+ *
4
+ * Renders the static API reference site from a document `openapi-generate` already wrote into the api
5
+ * library's build output, and writes the site into `<projectRoot>/<siteDir>` — a gitignored directory
6
+ * of the project, for HOSTING. It is deliberately not written into the package: a docs site is
7
+ * something you deploy, not something an npm consumer installs.
8
+ *
9
+ * Consumers do not declare this executor. The nx-webpieces-rules plugin INFERS the target on a project
10
+ * tagged `generate:docs-site` (which implies `generate:openapi`), with `dependsOn: ["openapi-generate"]`;
11
+ * project.json states only the options, under the same target name:
12
+ *
13
+ * "docs-generate": {
14
+ * "options": { "document": "public-openapi.json", "siteDir": "generated-docs", "prose": "<project>/docs" }
15
+ * }
16
+ *
17
+ * and the repo's .gitignore carries `generated-docs/`.
18
+ *
19
+ * `document` should be the PARTNER-facing document: a site built from `full-private-openapi.json`
20
+ * publishes exactly the operations somebody decided not to publish. `prose` is optional because a site
21
+ * with no prose pages is a legal site; `document` and `siteDir` are required and have no defaults.
22
+ *
23
+ * The renderer is the CONSUMER's `@webpieces/docs-site`, resolved from its node_modules and
24
+ * version-checked; this plugin bundles no copy of it.
25
+ */
26
+ import type { ExecutorContext } from '@nx/devkit';
27
+ import { RepoScratchDirs } from '@webpieces/rules-config';
28
+ import { ExecutorResult } from '../../executor-result';
29
+ import { ConsumerBinResolver } from '../../lib/api-docs/consumer-bin-resolver';
30
+ import { GeneratorRunner } from '../../lib/api-docs/generator-runner';
31
+ export interface DocsGenerateOptions {
32
+ /** A document name inside the api library's build output, e.g. `public-openapi.json`. Required. */
33
+ document?: string;
34
+ /** The site's directory, relative to the project root, e.g. `generated-docs`. Required. */
35
+ siteDir?: string;
36
+ /** Workspace-relative directory holding `docs.manifest.json` and its markdown. Optional. */
37
+ prose?: string;
38
+ }
39
+ /** Everything except the process-facing reporting, so the suite drives it exactly as nx does. */
40
+ export declare class DocsGenerate {
41
+ private readonly resolver;
42
+ private readonly runner;
43
+ private readonly scratch;
44
+ constructor(resolver?: ConsumerBinResolver, runner?: GeneratorRunner, scratch?: RepoScratchDirs);
45
+ /** @returns the files written, absolute. Throws RuleFailError on every refusal. */
46
+ run(options: DocsGenerateOptions, context: ExecutorContext): string[];
47
+ }
48
+ export default function runExecutor(options: DocsGenerateOptions, context: ExecutorContext): Promise<ExecutorResult>;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * docs-generate Executor
4
+ *
5
+ * Renders the static API reference site from a document `openapi-generate` already wrote into the api
6
+ * library's build output, and writes the site into `<projectRoot>/<siteDir>` — a gitignored directory
7
+ * of the project, for HOSTING. It is deliberately not written into the package: a docs site is
8
+ * something you deploy, not something an npm consumer installs.
9
+ *
10
+ * Consumers do not declare this executor. The nx-webpieces-rules plugin INFERS the target on a project
11
+ * tagged `generate:docs-site` (which implies `generate:openapi`), with `dependsOn: ["openapi-generate"]`;
12
+ * project.json states only the options, under the same target name:
13
+ *
14
+ * "docs-generate": {
15
+ * "options": { "document": "public-openapi.json", "siteDir": "generated-docs", "prose": "<project>/docs" }
16
+ * }
17
+ *
18
+ * and the repo's .gitignore carries `generated-docs/`.
19
+ *
20
+ * `document` should be the PARTNER-facing document: a site built from `full-private-openapi.json`
21
+ * publishes exactly the operations somebody decided not to publish. `prose` is optional because a site
22
+ * with no prose pages is a legal site; `document` and `siteDir` are required and have no defaults.
23
+ *
24
+ * The renderer is the CONSUMER's `@webpieces/docs-site`, resolved from its node_modules and
25
+ * version-checked; this plugin bundles no copy of it.
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.DocsGenerate = void 0;
29
+ exports.default = runExecutor;
30
+ const tslib_1 = require("tslib");
31
+ const core_util_1 = require("@webpieces/core-util");
32
+ const rules_config_1 = require("@webpieces/rules-config");
33
+ const fs = tslib_1.__importStar(require("fs"));
34
+ const path = tslib_1.__importStar(require("path"));
35
+ const executor_result_1 = require("../../executor-result");
36
+ const consumer_bin_resolver_1 = require("../../lib/api-docs/consumer-bin-resolver");
37
+ const generator_package_1 = require("../../lib/api-docs/generator-package");
38
+ const generator_runner_1 = require("../../lib/api-docs/generator-runner");
39
+ const generator_target_1 = require("../../lib/api-docs/generator-target");
40
+ const toError_1 = require("../../toError");
41
+ const RULE_NAME = core_util_1.GeneratedApiDocsLayout.DOCS_TARGET;
42
+ /** Everything except the process-facing reporting, so the suite drives it exactly as nx does. */
43
+ class DocsGenerate {
44
+ resolver;
45
+ runner;
46
+ scratch;
47
+ constructor(resolver = new consumer_bin_resolver_1.ConsumerBinResolver(), runner = new generator_runner_1.GeneratorRunner(), scratch = new rules_config_1.RepoScratchDirs()) {
48
+ this.resolver = resolver;
49
+ this.runner = runner;
50
+ this.scratch = scratch;
51
+ }
52
+ /** @returns the files written, absolute. Throws RuleFailError on every refusal. */
53
+ run(options, context) {
54
+ const target = generator_target_1.GeneratorTarget.of(RULE_NAME, context);
55
+ target.assertDependsOn(core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET);
56
+ const document = target.requiredOption(options.document, 'document');
57
+ const siteOut = target.insideProject(target.requiredOption(options.siteDir, 'siteDir'), 'siteDir');
58
+ const documentsDir = target.documentsDir();
59
+ const spec = path.join(documentsDir, document);
60
+ if (!fs.existsSync(spec)) {
61
+ throw new rules_config_1.RuleFailError(RULE_NAME, `${path.relative(context.root, spec)} does not exist, so there is nothing to render. ` +
62
+ `openapi-generate writes only the documents the contracts' @ApiType(...) ask for.`, undefined, undefined, [new rules_config_1.Option(`Set options.document to a file openapi-generate writes into ${path.relative(context.root, documentsDir)}`, true)]);
63
+ }
64
+ const bin = this.resolver.resolve(new consumer_bin_resolver_1.ConsumerBinRequest(RULE_NAME, generator_package_1.DOCS_SITE, [path.join(context.root, target.projectRoot), context.root]));
65
+ const staging = this.scratch.make(context.root, 'wp-docs-generate-');
66
+ // webpieces-disable no-unmanaged-exceptions -- try/FINALLY only, nothing is caught: the staging
67
+ // directory is removed however this ends, and every throw still reaches runExecutor below
68
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
69
+ try {
70
+ const args = ['--spec', spec, '--out', staging];
71
+ if (options.prose !== undefined)
72
+ args.push('--prose', path.resolve(context.root, options.prose));
73
+ const run = this.runner.run(bin, args, context.root);
74
+ if (!run.ok) {
75
+ throw new rules_config_1.RuleFailError(RULE_NAME, `${bin.packageName} ${bin.version} refused ${document}:\n${run.output}`);
76
+ }
77
+ // The site REPLACES the previous one: a page for an operation that no longer exists must not
78
+ // survive into what gets hosted.
79
+ fs.rmSync(siteOut, { recursive: true, force: true });
80
+ const written = new generator_target_1.StagedOutput().publish(staging, siteOut);
81
+ target.assertOutputsCover(written);
82
+ return written;
83
+ }
84
+ finally {
85
+ fs.rmSync(staging, { recursive: true, force: true });
86
+ }
87
+ }
88
+ }
89
+ exports.DocsGenerate = DocsGenerate;
90
+ // webpieces-disable no-function-outside-class -- nx executor module: nx resolves a default-export function here
91
+ async function runExecutor(options, context) {
92
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- this IS the executor's single top-level handler
93
+ try {
94
+ const written = new DocsGenerate().run(options, context);
95
+ console.log(`wrote ${written.length} file(s) of the docs site for ${context.projectName ?? ''}`);
96
+ return new executor_result_1.ExecutorResult(true);
97
+ }
98
+ catch (err) {
99
+ const error = (0, toError_1.toError)(err);
100
+ console.error(`❌ ${RULE_NAME}: ${error instanceof rules_config_1.RuleFailError ? (0, rules_config_1.renderRuleFailForHuman)(error) : error.message}`);
101
+ return new executor_result_1.ExecutorResult(false);
102
+ }
103
+ }
104
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/docs-generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * docs-generate Executor\n *\n * Renders the static API reference site from a document `openapi-generate` already wrote into the api\n * library's build output, and writes the site into `<projectRoot>/<siteDir>` — a gitignored directory\n * of the project, for HOSTING. It is deliberately not written into the package: a docs site is\n * something you deploy, not something an npm consumer installs.\n *\n * Consumers do not declare this executor. The nx-webpieces-rules plugin INFERS the target on a project\n * tagged `generate:docs-site` (which implies `generate:openapi`), with `dependsOn: [\"openapi-generate\"]`;\n * project.json states only the options, under the same target name:\n *\n * \"docs-generate\": {\n * \"options\": { \"document\": \"public-openapi.json\", \"siteDir\": \"generated-docs\", \"prose\": \"<project>/docs\" }\n * }\n *\n * and the repo's .gitignore carries `generated-docs/`.\n *\n * `document` should be the PARTNER-facing document: a site built from `full-private-openapi.json`\n * publishes exactly the operations somebody decided not to publish. `prose` is optional because a site\n * with no prose pages is a legal site; `document` and `siteDir` are required and have no defaults.\n *\n * The renderer is the CONSUMER's `@webpieces/docs-site`, resolved from its node_modules and\n * version-checked; this plugin bundles no copy of it.\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { GeneratedApiDocsLayout } from '@webpieces/core-util';\nimport { Option, 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 { DOCS_SITE } 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 DocsGenerateOptions {\n /** A document name inside the api library's build output, e.g. `public-openapi.json`. Required. */\n document?: string;\n /** The site's directory, relative to the project root, e.g. `generated-docs`. Required. */\n siteDir?: string;\n /** Workspace-relative directory holding `docs.manifest.json` and its markdown. Optional. */\n prose?: string;\n}\n\nconst RULE_NAME = GeneratedApiDocsLayout.DOCS_TARGET;\n\n/** Everything except the process-facing reporting, so the suite drives it exactly as nx does. */\nexport class DocsGenerate {\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: DocsGenerateOptions, context: ExecutorContext): string[] {\n const target = GeneratorTarget.of(RULE_NAME, context);\n target.assertDependsOn(GeneratedApiDocsLayout.OPENAPI_TARGET);\n const document = target.requiredOption(options.document, 'document');\n const siteOut = target.insideProject(target.requiredOption(options.siteDir, 'siteDir'), 'siteDir');\n const documentsDir = target.documentsDir();\n const spec = path.join(documentsDir, document);\n if (!fs.existsSync(spec)) {\n throw new RuleFailError(\n RULE_NAME,\n `${path.relative(context.root, spec)} does not exist, so there is nothing to render. ` +\n `openapi-generate writes only the documents the contracts' @ApiType(...) ask for.`,\n undefined,\n undefined,\n [new Option(`Set options.document to a file openapi-generate writes into ${path.relative(context.root, documentsDir)}`, true)],\n );\n }\n const bin = this.resolver.resolve(new ConsumerBinRequest(\n RULE_NAME, DOCS_SITE, [path.join(context.root, target.projectRoot), context.root]));\n\n const staging = this.scratch.make(context.root, 'wp-docs-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 args = ['--spec', spec, '--out', staging];\n if (options.prose !== undefined) args.push('--prose', path.resolve(context.root, options.prose));\n const run = this.runner.run(bin, args, context.root);\n if (!run.ok) {\n throw new RuleFailError(RULE_NAME, `${bin.packageName} ${bin.version} refused ${document}:\\n${run.output}`);\n }\n // The site REPLACES the previous one: a page for an operation that no longer exists must not\n // survive into what gets hosted.\n fs.rmSync(siteOut, { recursive: true, force: true });\n const written = new StagedOutput().publish(staging, siteOut);\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: DocsGenerateOptions,\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 DocsGenerate().run(options, context);\n console.log(`wrote ${written.length} file(s) of the docs site for ${context.projectName ?? ''}`);\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"]}
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "title": "Docs Generate Executor",
4
+ "description": "Inferred on a project tagged generate:docs-site. Render the static API reference site from a document openapi-generate wrote, with the consumer's own @webpieces/docs-site, into <projectRoot>/<siteDir> (gitignored, for hosting — not packed into the package).",
5
+ "type": "object",
6
+ "properties": {
7
+ "document": {
8
+ "type": "string",
9
+ "description": "The document openapi-generate wrote to render, e.g. public-openapi.json. No default."
10
+ },
11
+ "siteDir": {
12
+ "type": "string",
13
+ "description": "The site's directory relative to the project root, e.g. generated-docs. Emptied and rewritten on every run; gitignore it. No default."
14
+ },
15
+ "prose": {
16
+ "type": "string",
17
+ "description": "Workspace-relative directory holding docs.manifest.json and the markdown it names. Optional: a site with no prose pages is legal."
18
+ }
19
+ },
20
+ "required": ["document", "siteDir"]
21
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * openapi-generate Executor
3
+ *
4
+ * Renders a project's contracts to OpenAPI, plus ONE MCP tool catalog per contract
5
+ * (`mcp-<ContractClass>-tools.json`), INTO the outputPath of the target it dependsOn — the api
6
+ * library's compile step — so the documents are packed and published inside the api library's npm
7
+ * package: a consumer installs the library and has the contract. They are build output, never
8
+ * committed.
9
+ *
10
+ * Consumers do not declare this executor. The nx-webpieces-rules plugin INFERS the target on a project
11
+ * tagged `generate:openapi` (executor, cache, inputs, outputs); project.json states only what is the
12
+ * consumer's to decide, under the same target name:
13
+ *
14
+ * "tags": ["generate:openapi"],
15
+ * "targets": {
16
+ * "compile": { "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"],
17
+ * "options": { "outputPath": "dist/<project>", ... } },
18
+ * "openapi-generate": {
19
+ * "dependsOn": ["compile"],
20
+ * "options": { "manifest": "<project>/openapi.manifest.json", "format": "both" }
21
+ * },
22
+ * "build": { "executor": "nx:noop", "dependsOn": ["compile", "openapi-generate"] }
23
+ * }
24
+ *
25
+ * The output directory is the outputPath of the ONE target `dependsOn` names (GeneratedApiDocsLayout
26
+ * in @webpieces/core-util — the same lookup the MCP server reads with), never a hardcoded target name
27
+ * and never an assumed dist/. `outputs` are CHECKED against what a run wrote — see GeneratorTarget. The
28
+ * generator itself is the CONSUMER's `@webpieces/openapi-generator`, resolved from its node_modules and
29
+ * version-checked; this plugin bundles no copy of it (see ConsumerBinResolver).
30
+ */
31
+ import type { ExecutorContext } from '@nx/devkit';
32
+ import { RepoScratchDirs } from '@webpieces/rules-config';
33
+ import { ExecutorResult } from '../../executor-result';
34
+ import { ConsumerBinResolver } from '../../lib/api-docs/consumer-bin-resolver';
35
+ import { GeneratorRunner } from '../../lib/api-docs/generator-runner';
36
+ export interface OpenApiGenerateOptions {
37
+ /** Workspace-relative path to the project's `openapi.manifest.json`. Required; no default. */
38
+ manifest?: string;
39
+ /** `json`, `yaml` or `both`. Required; no default — it decides what the package ships. */
40
+ format?: string;
41
+ }
42
+ /** Everything except the process-facing reporting, so the suite drives it exactly as nx does. */
43
+ export declare class OpenApiGenerate {
44
+ private readonly resolver;
45
+ private readonly runner;
46
+ private readonly scratch;
47
+ constructor(resolver?: ConsumerBinResolver, runner?: GeneratorRunner, scratch?: RepoScratchDirs);
48
+ /** @returns the files written, absolute. Throws RuleFailError on every refusal. */
49
+ run(options: OpenApiGenerateOptions, context: ExecutorContext): string[];
50
+ }
51
+ export default function runExecutor(options: OpenApiGenerateOptions, context: ExecutorContext): Promise<ExecutorResult>;
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ /**
3
+ * openapi-generate Executor
4
+ *
5
+ * Renders a project's contracts to OpenAPI, plus ONE MCP tool catalog per contract
6
+ * (`mcp-<ContractClass>-tools.json`), INTO the outputPath of the target it dependsOn — the api
7
+ * library's compile step — so the documents are packed and published inside the api library's npm
8
+ * package: a consumer installs the library and has the contract. They are build output, never
9
+ * committed.
10
+ *
11
+ * Consumers do not declare this executor. The nx-webpieces-rules plugin INFERS the target on a project
12
+ * tagged `generate:openapi` (executor, cache, inputs, outputs); project.json states only what is the
13
+ * consumer's to decide, under the same target name:
14
+ *
15
+ * "tags": ["generate:openapi"],
16
+ * "targets": {
17
+ * "compile": { "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"],
18
+ * "options": { "outputPath": "dist/<project>", ... } },
19
+ * "openapi-generate": {
20
+ * "dependsOn": ["compile"],
21
+ * "options": { "manifest": "<project>/openapi.manifest.json", "format": "both" }
22
+ * },
23
+ * "build": { "executor": "nx:noop", "dependsOn": ["compile", "openapi-generate"] }
24
+ * }
25
+ *
26
+ * The output directory is the outputPath of the ONE target `dependsOn` names (GeneratedApiDocsLayout
27
+ * in @webpieces/core-util — the same lookup the MCP server reads with), never a hardcoded target name
28
+ * and never an assumed dist/. `outputs` are CHECKED against what a run wrote — see GeneratorTarget. The
29
+ * generator itself is the CONSUMER's `@webpieces/openapi-generator`, resolved from its node_modules and
30
+ * version-checked; this plugin bundles no copy of it (see ConsumerBinResolver).
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.OpenApiGenerate = void 0;
34
+ exports.default = runExecutor;
35
+ const tslib_1 = require("tslib");
36
+ const core_util_1 = require("@webpieces/core-util");
37
+ const rules_config_1 = require("@webpieces/rules-config");
38
+ const fs = tslib_1.__importStar(require("fs"));
39
+ const path = tslib_1.__importStar(require("path"));
40
+ const executor_result_1 = require("../../executor-result");
41
+ const consumer_bin_resolver_1 = require("../../lib/api-docs/consumer-bin-resolver");
42
+ const generator_package_1 = require("../../lib/api-docs/generator-package");
43
+ const generator_runner_1 = require("../../lib/api-docs/generator-runner");
44
+ const generator_target_1 = require("../../lib/api-docs/generator-target");
45
+ const toError_1 = require("../../toError");
46
+ const RULE_NAME = core_util_1.GeneratedApiDocsLayout.OPENAPI_TARGET;
47
+ /** Everything except the process-facing reporting, so the suite drives it exactly as nx does. */
48
+ class OpenApiGenerate {
49
+ resolver;
50
+ runner;
51
+ scratch;
52
+ constructor(resolver = new consumer_bin_resolver_1.ConsumerBinResolver(), runner = new generator_runner_1.GeneratorRunner(), scratch = new rules_config_1.RepoScratchDirs()) {
53
+ this.resolver = resolver;
54
+ this.runner = runner;
55
+ this.scratch = scratch;
56
+ }
57
+ /** @returns the files written, absolute. Throws RuleFailError on every refusal. */
58
+ run(options, context) {
59
+ const target = generator_target_1.GeneratorTarget.of(RULE_NAME, context);
60
+ const outDir = target.documentsDir();
61
+ const manifest = target.requiredOption(options.manifest, 'manifest');
62
+ const format = target.requiredOption(options.format, 'format');
63
+ const bin = this.resolver.resolve(new consumer_bin_resolver_1.ConsumerBinRequest(RULE_NAME, generator_package_1.OPENAPI_GENERATOR, [path.join(context.root, target.projectRoot), context.root]));
64
+ const staging = this.scratch.make(context.root, 'wp-openapi-generate-');
65
+ // webpieces-disable no-unmanaged-exceptions -- try/FINALLY only, nothing is caught: the staging
66
+ // directory is removed however this ends, and every throw still reaches runExecutor below
67
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
68
+ try {
69
+ const run = this.runner.run(bin, [
70
+ '--manifest', path.resolve(context.root, manifest), '--out', staging, '--format', format,
71
+ ], context.root);
72
+ if (!run.ok) {
73
+ throw new rules_config_1.RuleFailError(RULE_NAME, `${bin.packageName} ${bin.version} refused ${manifest}:\n${run.output}`);
74
+ }
75
+ const written = new generator_target_1.StagedOutput().publish(staging, outDir);
76
+ target.assertOutputsCover(written);
77
+ return written;
78
+ }
79
+ finally {
80
+ fs.rmSync(staging, { recursive: true, force: true });
81
+ }
82
+ }
83
+ }
84
+ exports.OpenApiGenerate = OpenApiGenerate;
85
+ // webpieces-disable no-function-outside-class -- nx executor module: nx resolves a default-export function here
86
+ async function runExecutor(options, context) {
87
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- this IS the executor's single top-level handler
88
+ try {
89
+ const written = new OpenApiGenerate().run(options, context);
90
+ for (const file of written)
91
+ console.log(`wrote ${path.relative(context.root, file)}`);
92
+ return new executor_result_1.ExecutorResult(true);
93
+ }
94
+ catch (err) {
95
+ const error = (0, toError_1.toError)(err);
96
+ console.error(`❌ ${RULE_NAME}: ${error instanceof rules_config_1.RuleFailError ? (0, rules_config_1.renderRuleFailForHuman)(error) : error.message}`);
97
+ return new executor_result_1.ExecutorResult(false);
98
+ }
99
+ }
100
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
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 compile 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 * \"compile\": { \"executor\": \"@nx/js:tsc\", \"outputs\": [\"{options.outputPath}\"],\n * \"options\": { \"outputPath\": \"dist/<project>\", ... } },\n * \"openapi-generate\": {\n * \"dependsOn\": [\"compile\"],\n * \"options\": { \"manifest\": \"<project>/openapi.manifest.json\", \"format\": \"both\" }\n * },\n * \"build\": { \"executor\": \"nx:noop\", \"dependsOn\": [\"compile\", \"openapi-generate\"] }\n * }\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"]}
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
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 compile step).",
5
+ "type": "object",
6
+ "properties": {
7
+ "manifest": {
8
+ "type": "string",
9
+ "description": "Workspace-relative path to the project's openapi.manifest.json. No default."
10
+ },
11
+ "format": {
12
+ "type": "string",
13
+ "enum": ["json", "yaml", "both"],
14
+ "description": "Which serializations of the OpenAPI documents to write, and therefore publish. No default."
15
+ }
16
+ },
17
+ "required": ["manifest", "format"]
18
+ }
@@ -18,6 +18,10 @@
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 (#1021) — a project tagged `generate:openapi` / `generate:docs-site` has the
22
+ * `compile` → `openapi-generate` → `build` (nx:noop) shape, and every project depending on one has
23
+ * `test` dependsOn `^build`. See GenerateWiring for why.
24
+ *
21
25
  * Conservative by design: only REQUIRES wiring on compile executors actually in use
22
26
  * (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.
23
27
  *
@@ -19,6 +19,10 @@
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 (#1021) — a project tagged `generate:openapi` / `generate:docs-site` has the
23
+ * `compile` → `openapi-generate` → `build` (nx:noop) shape, and every project depending on one has
24
+ * `test` dependsOn `^build`. See GenerateWiring for why.
25
+ *
22
26
  * Conservative by design: only REQUIRES wiring on compile executors actually in use
23
27
  * (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.
24
28
  *
@@ -31,6 +35,7 @@ exports.default = runExecutor;
31
35
  const tslib_1 = require("tslib");
32
36
  const devkit_1 = require("@nx/devkit");
33
37
  const rules_config_1 = require("@webpieces/rules-config");
38
+ const generate_wiring_1 = require("../../lib/api-docs/generate-wiring");
34
39
  const fs = tslib_1.__importStar(require("fs"));
35
40
  const path = tslib_1.__importStar(require("path"));
36
41
  // Compile executors (and any per-project build override) must keep `^build` so build order
@@ -183,16 +188,22 @@ async function runExecutor(options, context) {
183
188
  console.log('\n🔌 Validating webpieces validators are wired into the build\n');
184
189
  const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
185
190
  const projectsConfig = (0, devkit_1.readProjectsConfigurationFromProjectGraph)(projectGraph);
191
+ const generateWiring = new generate_wiring_1.GenerateWiring(projectsConfig.projects, projectGraph.dependencies);
192
+ const generateProblems = generateWiring.problems();
186
193
  const inUse = findCompileExecutorsInUse(projectsConfig, compileExecutors);
187
194
  const relevantExecutors = compileExecutors.filter((executorName) => inUse.has(executorName));
188
195
  if (relevantExecutors.length === 0) {
196
+ if (generateProblems.length > 0) {
197
+ generateWiring.report(generateProblems);
198
+ return { success: false };
199
+ }
189
200
  console.log('✅ No known compile executors in use — nothing to gate\n');
190
201
  return { success: true };
191
202
  }
192
203
  const targetDefaults = readTargetDefaults(context.root);
193
204
  const wiringProblems = findProblems(relevantExecutors, targetDefaults, requiredDeps);
194
205
  const gateProblems = findProjectGateProblems(projectsConfig, inUse, requiredDeps);
195
- if (wiringProblems.length === 0 && gateProblems.length === 0) {
206
+ if (wiringProblems.length === 0 && gateProblems.length === 0 && generateProblems.length === 0) {
196
207
  console.log('✅ Validators are wired into the build (targetDefaults + every project)\n');
197
208
  return { success: true };
198
209
  }
@@ -200,6 +211,8 @@ async function runExecutor(options, context) {
200
211
  reportFailure(wiringProblems, requiredDeps);
201
212
  if (gateProblems.length > 0)
202
213
  reportProjectGateFailure(gateProblems);
214
+ if (generateProblems.length > 0)
215
+ generateWiring.report(generateProblems);
203
216
  return { success: false };
204
217
  }
205
218
  //# sourceMappingURL=executor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-nx-wiring/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Nx Wiring Executor\n *\n * Enforces that the build graph is wired for correct build ORDER. Two checks:\n *\n * 1. nx.json targetDefaults — the compile executors (@nx/js:tsc, @angular/build:application)\n * must carry the load-bearing dependsOn:\n * [\"^build\"]\n *\n * 2. Per-project override guard — a project that declares its OWN build.dependsOn OVERRIDES\n * the targetDefaults entirely (nx does not merge dependsOn). That silently drops `^build`\n * (→ upstream libs not built first, builds against an empty dist). So we check each compile\n * project's RESOLVED build.dependsOn and fail if `^build` is missing.\n *\n * NOTE: the webpieces validators (architecture:validate-complete, per-project file-import\n * cycles, DI-graph unchanged) NO LONGER ride on the compile target. They live on the `ci`\n * composite target (synthesized by this plugin's createCiTarget), so `build`/compile is a\n * fast compile-only step while `nx ci` runs the full gate. This executor therefore only\n * guards build ORDER (`^build`), not the validators — those are guaranteed by the plugin.\n *\n * Conservative by design: only REQUIRES wiring on compile executors actually in use\n * (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.\n *\n * Disable via webpieces.config.json rules[\"nx-wiring\"].mode=\"OFF\".\n *\n * Usage: nx run architecture:validate-nx-wiring\n */\n\nimport type {\n ExecutorContext,\n ProjectsConfigurations,\n TargetDependencyConfig,\n} from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadAndValidate } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateNxWiringOptions {\n requiredDeps?: string[];\n compileExecutors?: string[];\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// Compile executors (and any per-project build override) must keep `^build` so build order\n// follows nx's full graph — upstreams are built into dist before dependents compile.\n// Validators no longer belong here; they ride on the `ci` composite target instead.\nconst DEFAULT_REQUIRED_DEPS: string[] = ['^build'];\n\nconst DEFAULT_COMPILE_EXECUTORS: string[] = [\n '@nx/js:tsc',\n '@angular/build:application',\n];\n\nclass WiringProblem {\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(executorName: string, missing: string[], found: string[]) {\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\nclass ProjectGateProblem {\n project: string;\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(project: string, executorName: string, missing: string[], found: string[]) {\n this.project = project;\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\ninterface TargetDefaultEntry {\n dependsOn?: string[];\n}\n\ninterface RawNxJson {\n targetDefaults?: Record<string, TargetDefaultEntry>;\n}\n\nfunction readTargetDefaults(workspaceRoot: string): Record<string, TargetDefaultEntry> {\n const nxJsonPath = path.join(workspaceRoot, 'nx.json');\n if (!fs.existsSync(nxJsonPath)) return {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(nxJsonPath, 'utf8');\n const parsed = JSON.parse(raw) as RawNxJson;\n return parsed.targetDefaults ?? {};\n // webpieces-disable catch-error-pattern -- malformed nx.json fails open so the check does not crash the build\n } catch (err: unknown) {\n //const error = toError(err); -- malformed nx.json fails open\n void err;\n return {};\n }\n}\n\nfunction findCompileExecutorsInUse(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: string[],\n): Set<string> {\n const known = new Set(compileExecutors);\n const inUse = new Set<string>();\n for (const cfg of Object.values(projectsConfig.projects)) {\n const targets = cfg.targets ?? {};\n for (const target of Object.values(targets)) {\n const executorName = target.executor;\n if (executorName && known.has(executorName)) {\n inUse.add(executorName);\n }\n }\n }\n return inUse;\n}\n\nfunction findProblems(\n relevantExecutors: string[],\n targetDefaults: Record<string, TargetDefaultEntry>,\n requiredDeps: string[],\n): WiringProblem[] {\n const problems: WiringProblem[] = [];\n for (const executorName of relevantExecutors) {\n const entry = targetDefaults[executorName];\n const dependsOn = entry?.dependsOn ?? [];\n const missing = requiredDeps.filter((dep: string) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new WiringProblem(executorName, missing, dependsOn));\n }\n }\n return problems;\n}\n\n/**\n * Normalize an nx dependsOn entry to a comparable string. The string form (\"^build\",\n * \"validate-complete\") passes through; the object form is rendered the same way\n * ({target:\"build\", dependencies:true} → \"^build\", {target:\"build\"} → \"build\").\n */\nfunction normalizeDeps(deps: (string | TargetDependencyConfig)[]): string[] {\n return deps.map((dep) => {\n if (typeof dep === 'string') return dep;\n const target = dep.target ?? '';\n return dep.dependencies ? `^${target}` : target;\n });\n}\n\n/**\n * Check each compile project's RESOLVED build.dependsOn (targetDefaults already merged in)\n * for `^build`. A project that overrode it away is flagged.\n */\nfunction findProjectGateProblems(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: Set<string>,\n requiredPerProject: string[],\n): ProjectGateProblem[] {\n const problems: ProjectGateProblem[] = [];\n for (const [name, cfg] of Object.entries(projectsConfig.projects)) {\n // Tooling/foundation packages (type:tooling) BUILD the validators, so gating their own\n // build on validate-complete would create a bootstrap cycle. They are exempt: they keep\n // build-order (\"^build\") only. Application/library code is still fully gated.\n if ((cfg.tags ?? []).includes('type:tooling')) continue;\n const build = cfg.targets?.['build'];\n if (!build || !build.executor || !compileExecutors.has(build.executor)) continue;\n const dependsOn = normalizeDeps(build.dependsOn ?? []);\n const missing = requiredPerProject.filter((dep) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new ProjectGateProblem(name, build.executor, missing, dependsOn));\n }\n }\n return problems;\n}\n\nfunction reportFailure(problems: WiringProblem[], requiredDeps: string[]): void {\n console.error('\\n❌ Build order is not wired in nx.json targetDefaults.\\n');\n console.error('Compile executors must keep \"^build\" so upstream libs build into dist first.');\n console.error('Add the missing dependsOn entries to nx.json targetDefaults:\\n');\n const depsList = requiredDeps.map((dep: string) => `\"${dep}\"`).join(', ');\n for (const problem of problems) {\n const missingList = problem.missing.map((dep: string) => `\"${dep}\"`).join(', ');\n console.error(` \"${problem.executorName}\": {`);\n console.error(` \"dependsOn\": [${depsList}]`);\n console.error(' }');\n console.error(` missing: ${missingList}\\n`);\n }\n console.error('To turn this wiring check off, set rules[\"nx-wiring\"].mode=\"OFF\" in');\n console.error('webpieces.config.json.\\n');\n}\n\nfunction reportProjectGateFailure(problems: ProjectGateProblem[]): void {\n console.error('\\n❌ Some projects override away the build order in their project.json.\\n');\n console.error('A project-level build.dependsOn OVERRIDES nx.json targetDefaults (nx does not');\n console.error('merge dependsOn). That silently drops \"^build\" (upstreams not built first →');\n console.error('builds against an empty dist).\\n');\n console.error('Fix: remove build.dependsOn from these project.json files so targetDefaults');\n console.error('apply, OR include \"^build\" explicitly:\\n');\n for (const p of problems) {\n const missingList = p.missing.map((dep) => `\"${dep}\"`).join(', ');\n console.error(` ${p.project} (${p.executorName}):`);\n console.error(` resolved build.dependsOn: ${JSON.stringify(p.found)}`);\n console.error(` missing: ${missingList}\\n`);\n }\n}\n\nexport default async function runExecutor(\n options: ValidateNxWiringOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get('nx-wiring');\n if (rule && rule.isOff) {\n console.log('\\n⏭️ Skipping validate-nx-wiring (mode: OFF)\\n');\n return { success: true };\n }\n\n const requiredDeps = options.requiredDeps ?? DEFAULT_REQUIRED_DEPS;\n const compileExecutors = options.compileExecutors ?? DEFAULT_COMPILE_EXECUTORS;\n\n console.log('\\n🔌 Validating webpieces validators are wired into the build\\n');\n\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const inUse = findCompileExecutorsInUse(projectsConfig, compileExecutors);\n const relevantExecutors = compileExecutors.filter((executorName: string) =>\n inUse.has(executorName),\n );\n\n if (relevantExecutors.length === 0) {\n console.log('✅ No known compile executors in use — nothing to gate\\n');\n return { success: true };\n }\n\n const targetDefaults = readTargetDefaults(context.root);\n const wiringProblems = findProblems(relevantExecutors, targetDefaults, requiredDeps);\n const gateProblems = findProjectGateProblems(projectsConfig, inUse, requiredDeps);\n\n if (wiringProblems.length === 0 && gateProblems.length === 0) {\n console.log('✅ Validators are wired into the build (targetDefaults + every project)\\n');\n return { success: true };\n }\n\n if (wiringProblems.length > 0) reportFailure(wiringProblems, requiredDeps);\n if (gateProblems.length > 0) reportProjectGateFailure(gateProblems);\n return { success: false };\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-nx-wiring/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Nx Wiring Executor\n *\n * Enforces that the build graph is wired for correct build ORDER. Two checks:\n *\n * 1. nx.json targetDefaults — the compile executors (@nx/js:tsc, @angular/build:application)\n * must carry the load-bearing dependsOn:\n * [\"^build\"]\n *\n * 2. Per-project override guard — a project that declares its OWN build.dependsOn OVERRIDES\n * the targetDefaults entirely (nx does not merge dependsOn). That silently drops `^build`\n * (→ upstream libs not built first, builds against an empty dist). So we check each compile\n * project's RESOLVED build.dependsOn and fail if `^build` is missing.\n *\n * NOTE: the webpieces validators (architecture:validate-complete, per-project file-import\n * cycles, DI-graph unchanged) NO LONGER ride on the compile target. They live on the `ci`\n * composite target (synthesized by this plugin's createCiTarget), so `build`/compile is a\n * fast compile-only step while `nx ci` runs the full gate. This executor therefore only\n * guards build ORDER (`^build`), not the validators — those are guaranteed by the plugin.\n *\n * 3. Generated API documents (#1021) — a project tagged `generate:openapi` / `generate:docs-site` has the\n * `compile` → `openapi-generate` → `build` (nx:noop) shape, and every project depending on one has\n * `test` dependsOn `^build`. See GenerateWiring for why.\n *\n * Conservative by design: only REQUIRES wiring on compile executors actually in use\n * (@nx/js:tsc, @angular/build:application). A repo that uses neither passes.\n *\n * Disable via webpieces.config.json rules[\"nx-wiring\"].mode=\"OFF\".\n *\n * Usage: nx run architecture:validate-nx-wiring\n */\n\nimport type {\n ExecutorContext,\n ProjectsConfigurations,\n TargetDependencyConfig,\n} from '@nx/devkit';\nimport { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from '@nx/devkit';\nimport { loadAndValidate } from '@webpieces/rules-config';\nimport { GenerateWiring } from '../../lib/api-docs/generate-wiring';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateNxWiringOptions {\n requiredDeps?: string[];\n compileExecutors?: string[];\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// Compile executors (and any per-project build override) must keep `^build` so build order\n// follows nx's full graph — upstreams are built into dist before dependents compile.\n// Validators no longer belong here; they ride on the `ci` composite target instead.\nconst DEFAULT_REQUIRED_DEPS: string[] = ['^build'];\n\nconst DEFAULT_COMPILE_EXECUTORS: string[] = [\n '@nx/js:tsc',\n '@angular/build:application',\n];\n\nclass WiringProblem {\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(executorName: string, missing: string[], found: string[]) {\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\nclass ProjectGateProblem {\n project: string;\n executorName: string;\n missing: string[];\n found: string[];\n\n constructor(project: string, executorName: string, missing: string[], found: string[]) {\n this.project = project;\n this.executorName = executorName;\n this.missing = missing;\n this.found = found;\n }\n}\n\ninterface TargetDefaultEntry {\n dependsOn?: string[];\n}\n\ninterface RawNxJson {\n targetDefaults?: Record<string, TargetDefaultEntry>;\n}\n\nfunction readTargetDefaults(workspaceRoot: string): Record<string, TargetDefaultEntry> {\n const nxJsonPath = path.join(workspaceRoot, 'nx.json');\n if (!fs.existsSync(nxJsonPath)) return {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(nxJsonPath, 'utf8');\n const parsed = JSON.parse(raw) as RawNxJson;\n return parsed.targetDefaults ?? {};\n // webpieces-disable catch-error-pattern -- malformed nx.json fails open so the check does not crash the build\n } catch (err: unknown) {\n //const error = toError(err); -- malformed nx.json fails open\n void err;\n return {};\n }\n}\n\nfunction findCompileExecutorsInUse(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: string[],\n): Set<string> {\n const known = new Set(compileExecutors);\n const inUse = new Set<string>();\n for (const cfg of Object.values(projectsConfig.projects)) {\n const targets = cfg.targets ?? {};\n for (const target of Object.values(targets)) {\n const executorName = target.executor;\n if (executorName && known.has(executorName)) {\n inUse.add(executorName);\n }\n }\n }\n return inUse;\n}\n\nfunction findProblems(\n relevantExecutors: string[],\n targetDefaults: Record<string, TargetDefaultEntry>,\n requiredDeps: string[],\n): WiringProblem[] {\n const problems: WiringProblem[] = [];\n for (const executorName of relevantExecutors) {\n const entry = targetDefaults[executorName];\n const dependsOn = entry?.dependsOn ?? [];\n const missing = requiredDeps.filter((dep: string) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new WiringProblem(executorName, missing, dependsOn));\n }\n }\n return problems;\n}\n\n/**\n * Normalize an nx dependsOn entry to a comparable string. The string form (\"^build\",\n * \"validate-complete\") passes through; the object form is rendered the same way\n * ({target:\"build\", dependencies:true} → \"^build\", {target:\"build\"} → \"build\").\n */\nfunction normalizeDeps(deps: (string | TargetDependencyConfig)[]): string[] {\n return deps.map((dep) => {\n if (typeof dep === 'string') return dep;\n const target = dep.target ?? '';\n return dep.dependencies ? `^${target}` : target;\n });\n}\n\n/**\n * Check each compile project's RESOLVED build.dependsOn (targetDefaults already merged in)\n * for `^build`. A project that overrode it away is flagged.\n */\nfunction findProjectGateProblems(\n projectsConfig: ProjectsConfigurations,\n compileExecutors: Set<string>,\n requiredPerProject: string[],\n): ProjectGateProblem[] {\n const problems: ProjectGateProblem[] = [];\n for (const [name, cfg] of Object.entries(projectsConfig.projects)) {\n // Tooling/foundation packages (type:tooling) BUILD the validators, so gating their own\n // build on validate-complete would create a bootstrap cycle. They are exempt: they keep\n // build-order (\"^build\") only. Application/library code is still fully gated.\n if ((cfg.tags ?? []).includes('type:tooling')) continue;\n const build = cfg.targets?.['build'];\n if (!build || !build.executor || !compileExecutors.has(build.executor)) continue;\n const dependsOn = normalizeDeps(build.dependsOn ?? []);\n const missing = requiredPerProject.filter((dep) => !dependsOn.includes(dep));\n if (missing.length > 0) {\n problems.push(new ProjectGateProblem(name, build.executor, missing, dependsOn));\n }\n }\n return problems;\n}\n\nfunction reportFailure(problems: WiringProblem[], requiredDeps: string[]): void {\n console.error('\\n❌ Build order is not wired in nx.json targetDefaults.\\n');\n console.error('Compile executors must keep \"^build\" so upstream libs build into dist first.');\n console.error('Add the missing dependsOn entries to nx.json targetDefaults:\\n');\n const depsList = requiredDeps.map((dep: string) => `\"${dep}\"`).join(', ');\n for (const problem of problems) {\n const missingList = problem.missing.map((dep: string) => `\"${dep}\"`).join(', ');\n console.error(` \"${problem.executorName}\": {`);\n console.error(` \"dependsOn\": [${depsList}]`);\n console.error(' }');\n console.error(` missing: ${missingList}\\n`);\n }\n console.error('To turn this wiring check off, set rules[\"nx-wiring\"].mode=\"OFF\" in');\n console.error('webpieces.config.json.\\n');\n}\n\nfunction reportProjectGateFailure(problems: ProjectGateProblem[]): void {\n console.error('\\n❌ Some projects override away the build order in their project.json.\\n');\n console.error('A project-level build.dependsOn OVERRIDES nx.json targetDefaults (nx does not');\n console.error('merge dependsOn). That silently drops \"^build\" (upstreams not built first →');\n console.error('builds against an empty dist).\\n');\n console.error('Fix: remove build.dependsOn from these project.json files so targetDefaults');\n console.error('apply, OR include \"^build\" explicitly:\\n');\n for (const p of problems) {\n const missingList = p.missing.map((dep) => `\"${dep}\"`).join(', ');\n console.error(` ${p.project} (${p.executorName}):`);\n console.error(` resolved build.dependsOn: ${JSON.stringify(p.found)}`);\n console.error(` missing: ${missingList}\\n`);\n }\n}\n\nexport default async function runExecutor(\n options: ValidateNxWiringOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get('nx-wiring');\n if (rule && rule.isOff) {\n console.log('\\n⏭️ Skipping validate-nx-wiring (mode: OFF)\\n');\n return { success: true };\n }\n\n const requiredDeps = options.requiredDeps ?? DEFAULT_REQUIRED_DEPS;\n const compileExecutors = options.compileExecutors ?? DEFAULT_COMPILE_EXECUTORS;\n\n console.log('\\n🔌 Validating webpieces validators are wired into the build\\n');\n\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const generateWiring = new GenerateWiring(projectsConfig.projects, projectGraph.dependencies);\n const generateProblems = generateWiring.problems();\n\n const inUse = findCompileExecutorsInUse(projectsConfig, compileExecutors);\n const relevantExecutors = compileExecutors.filter((executorName: string) =>\n inUse.has(executorName),\n );\n\n if (relevantExecutors.length === 0) {\n if (generateProblems.length > 0) {\n generateWiring.report(generateProblems);\n return { success: false };\n }\n console.log('✅ No known compile executors in use — nothing to gate\\n');\n return { success: true };\n }\n\n const targetDefaults = readTargetDefaults(context.root);\n const wiringProblems = findProblems(relevantExecutors, targetDefaults, requiredDeps);\n const gateProblems = findProjectGateProblems(projectsConfig, inUse, requiredDeps);\n\n if (wiringProblems.length === 0 && gateProblems.length === 0 && generateProblems.length === 0) {\n console.log('✅ Validators are wired into the build (targetDefaults + every project)\\n');\n return { success: true };\n }\n\n if (wiringProblems.length > 0) reportFailure(wiringProblems, requiredDeps);\n if (gateProblems.length > 0) reportProjectGateFailure(gateProblems);\n if (generateProblems.length > 0) generateWiring.report(generateProblems);\n return { success: false };\n}\n"]}