@webpieces/openapi-generator 0.4.805 → 0.4.807

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/README.md CHANGED
@@ -95,11 +95,16 @@ cannot.
95
95
  `MCP` in `@ApiType` requires `@WpMcpTool` on at least one method, and `@WpMcpTool` on a contract that
96
96
  does not declare `MCP` is an error. Membership has one spelling.
97
97
 
98
- The agent reads the SAME `description` a human does — the method's JSDoc, byte for byte.
99
- `@WpMcpTool`'s own `description` field is never read: two authored copies of one paragraph drift the
100
- first time somebody edits one. The tool carries only what JSDoc cannot say the stable protocol name.
101
- `readOnlyHint`, `destructiveHint` and `idempotentHint` are computed from the endpoint's declared
102
- `operation`; `openWorldHint` comes from `{ openWorld: true }` on the endpoint.
98
+ The agent reads the SAME `description` a human does — the method's JSDoc, byte for byte. Two authored
99
+ copies of one paragraph drift the first time somebody edits one, so `@WpMcpTool('search_stores')`
100
+ carries only what JSDoc cannot say: the stable protocol name. `readOnlyHint`, `destructiveHint` and
101
+ `idempotentHint` are computed from the endpoint's declared `operation`; `openWorldHint` comes from
102
+ `{ openWorld: true }` on the endpoint.
103
+
104
+ Alongside `mcp-openapi.json`, a run writes **`mcp-tools.json`** whenever some contract declares `MCP`.
105
+ That one is not a document: it is the RUNTIME catalog `WpMcpServer` is constructed with, so the schema
106
+ an agent is shown in `tools/list` and the schema the server validates a call against are the same
107
+ bytes. `--format` does not apply to it.
103
108
 
104
109
  ## It refuses to publish a field it has no schema for
105
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/openapi-generator",
3
- "version": "0.4.805",
3
+ "version": "0.4.807",
4
4
  "description": "Render an ApiDocModel to OpenAPI 3.1.0 — the canonical internal document and the customer-published one, JSON and YAML, from one generation pass. Ships the wp-openapi bin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -23,7 +23,8 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@webpieces/api-doc-model": "0.4.805",
26
+ "@webpieces/api-doc-model": "0.4.807",
27
+ "@webpieces/core-util": "0.4.807",
27
28
  "typescript": "5.9.3"
28
29
  },
29
30
  "bin": {
@@ -1,10 +1,15 @@
1
+ import { SkippedMcpTool } from '@webpieces/api-doc-model';
1
2
  import { GeneratedArtifact } from '../emit/ArtifactWriter';
2
3
  export declare const USAGE: string;
3
4
  /** The result of one run: what was written, so a caller can print it or assert on it. */
4
5
  export declare class CliResult {
5
6
  readonly written: readonly string[];
6
7
  readonly artifacts: readonly GeneratedArtifact[];
7
- constructor(written: readonly string[], artifacts: readonly GeneratedArtifact[]);
8
+ /** `@WpMcpTool`s the MCP catalog could not carry, with the reason. Never silent. */
9
+ readonly skippedMcpTools: readonly SkippedMcpTool[];
10
+ constructor(written: readonly string[], artifacts: readonly GeneratedArtifact[],
11
+ /** `@WpMcpTool`s the MCP catalog could not carry, with the reason. Never silent. */
12
+ skippedMcpTools?: readonly SkippedMcpTool[]);
8
13
  }
9
14
  /**
10
15
  * `wp-openapi`'s argument parsing and composition, with NO process-level concerns in it — no
@@ -22,6 +27,18 @@ export declare class OpenApiCli {
22
27
  /** @param argv the arguments AFTER the program name. @param cwd what relative paths resolve against. */
23
28
  run(argv: readonly string[], cwd: string): CliResult;
24
29
  wantsHelp(argv: readonly string[]): boolean;
30
+ /**
31
+ * `mcp-tools.json` — the RUNTIME artifact, beside the three documents.
32
+ *
33
+ * It is not a document and is not serialized by `--format`: `WpMcpServer` is constructed with it,
34
+ * and `McpToolRegistry` fails fast at boot when a registered `@WpMcpTool` is missing from it. That
35
+ * is what took the MCP runtime off reflect-metadata (#984) — the schema an agent is shown and the
36
+ * schema the server accepts are now the same bytes, rather than two derivations of one contract.
37
+ *
38
+ * Written only when some contract declares `MCP`, which is the same rule `mcp-openapi.json`
39
+ * follows, for the same reason: an empty file is a claim that there are no tools.
40
+ */
41
+ private static mcpCatalog;
25
42
  /** `both` unless told otherwise: the second serialization comes free from the same document. */
26
43
  private formatOf;
27
44
  /**
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OpenApiCli = exports.CliResult = exports.USAGE = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const path = tslib_1.__importStar(require("node:path"));
6
+ const core_util_1 = require("@webpieces/core-util");
7
+ const api_doc_model_1 = require("@webpieces/api-doc-model");
6
8
  const ArtifactWriter_1 = require("../emit/ArtifactWriter");
7
9
  const OpenApiGenerator_1 = require("../generate/OpenApiGenerator");
8
10
  const InputsLoader_1 = require("../load/InputsLoader");
@@ -23,6 +25,9 @@ exports.USAGE = [
23
25
  ' public-openapi contracts declaring EXTERNAL_CUSTOMER, minus every { hidden: true } method',
24
26
  ' mcp-openapi contracts declaring MCP, carrying the x-mcp-* extensions',
25
27
  '',
28
+ 'Alongside them, mcp-tools.json is written whenever some contract declares MCP: the RUNTIME',
29
+ 'artifact WpMcpServer is constructed with. It is not a document, so --format does not apply.',
30
+ '',
26
31
  'A document no contract asked for is not written. --format chooses the serialization of',
27
32
  'whichever documents were written, and defaults to both.',
28
33
  '',
@@ -33,12 +38,25 @@ exports.USAGE = [
33
38
  class CliResult {
34
39
  written;
35
40
  artifacts;
36
- constructor(written, artifacts) {
41
+ skippedMcpTools;
42
+ constructor(written, artifacts,
43
+ /** `@WpMcpTool`s the MCP catalog could not carry, with the reason. Never silent. */
44
+ skippedMcpTools = []) {
37
45
  this.written = written;
38
46
  this.artifacts = artifacts;
47
+ this.skippedMcpTools = skippedMcpTools;
39
48
  }
40
49
  }
41
50
  exports.CliResult = CliResult;
51
+ /** The MCP half of one run: the artifact to write, if any, and what it could not carry. */
52
+ class McpArtifacts {
53
+ artifacts;
54
+ skipped;
55
+ constructor(artifacts, skipped) {
56
+ this.artifacts = artifacts;
57
+ this.skipped = skipped;
58
+ }
59
+ }
42
60
  /**
43
61
  * `wp-openapi`'s argument parsing and composition, with NO process-level concerns in it — no
44
62
  * `process.exit`, no `console` — so the whole command is exercised by the suite exactly as a user
@@ -61,12 +79,41 @@ class OpenApiCli {
61
79
  }
62
80
  const inputs = this.loader.load(path.resolve(cwd, manifest));
63
81
  const documents = this.generator.generate(inputs);
64
- const artifacts = this.writer.artifacts(documents, this.formatOf(argv));
65
- return new CliResult(this.writer.write(path.resolve(cwd, out), artifacts), artifacts);
82
+ const mcp = OpenApiCli.mcpCatalog(inputs);
83
+ const artifacts = [
84
+ ...this.writer.artifacts(documents, this.formatOf(argv)),
85
+ ...mcp.artifacts,
86
+ ];
87
+ return new CliResult(this.writer.write(path.resolve(cwd, out), artifacts), artifacts, mcp.skipped);
66
88
  }
67
89
  wantsHelp(argv) {
68
90
  return argv.includes(HELP);
69
91
  }
92
+ /**
93
+ * `mcp-tools.json` — the RUNTIME artifact, beside the three documents.
94
+ *
95
+ * It is not a document and is not serialized by `--format`: `WpMcpServer` is constructed with it,
96
+ * and `McpToolRegistry` fails fast at boot when a registered `@WpMcpTool` is missing from it. That
97
+ * is what took the MCP runtime off reflect-metadata (#984) — the schema an agent is shown and the
98
+ * schema the server accepts are now the same bytes, rather than two derivations of one contract.
99
+ *
100
+ * Written only when some contract declares `MCP`, which is the same rule `mcp-openapi.json`
101
+ * follows, for the same reason: an empty file is a claim that there are no tools.
102
+ */
103
+ // webpieces-disable no-function-outside-class -- private static composition step of this class
104
+ static mcpCatalog(inputs) {
105
+ const models = inputs.contracts
106
+ .filter((contract) => contract.model.apiTypes.includes(core_util_1.MCP))
107
+ .map((contract) => contract.model);
108
+ if (models.length === 0) {
109
+ return new McpArtifacts([], []);
110
+ }
111
+ const rendered = api_doc_model_1.McpSchemaRenderer.catalogOf(models);
112
+ const artifacts = rendered.catalog.tools.length === 0
113
+ ? []
114
+ : [new ArtifactWriter_1.GeneratedArtifact('mcp-tools.json', rendered.catalog.toJsonText())];
115
+ return new McpArtifacts(artifacts, rendered.skipped);
116
+ }
70
117
  /** `both` unless told otherwise: the second serialization comes free from the same document. */
71
118
  formatOf(argv) {
72
119
  const declared = this.valueOf(argv, FORMAT);
@@ -1 +1 @@
1
- {"version":3,"file":"OpenApiCli.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/cli/OpenApiCli.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'node:path';\nimport { ArtifactWriter, GeneratedArtifact, OutputFormat } from '../emit/ArtifactWriter';\nimport { OpenApiGenerator } from '../generate/OpenApiGenerator';\nimport { InputsLoader } from '../load/InputsLoader';\nimport { OpenApiGenerationError } from '../OpenApiGenerationError';\n\nconst MANIFEST = '--manifest';\nconst OUT = '--out';\nconst FORMAT = '--format';\nconst HELP = '--help';\n\nconst FLAGS = [MANIFEST, OUT, FORMAT];\nconst FORMATS: readonly string[] = ['json', 'yaml', 'both'];\n\nexport const USAGE = [\n 'wp-openapi --manifest <openapi.manifest.json> --out <dir> [--format json|yaml|both]',\n '',\n 'Renders the contracts a manifest names into OpenAPI 3.1.0. WHICH documents are written is a',\n \"property of the contracts themselves — each one's @ApiType(...) names the documents it feeds:\",\n '',\n ' full-private-openapi every contract declaring SVC_TO_SVC, hidden methods included',\n ' public-openapi contracts declaring EXTERNAL_CUSTOMER, minus every { hidden: true } method',\n ' mcp-openapi contracts declaring MCP, carrying the x-mcp-* extensions',\n '',\n 'A document no contract asked for is not written. --format chooses the serialization of',\n 'whichever documents were written, and defaults to both.',\n '',\n 'It exits non-zero, naming the JSON pointer of every offending field, rather than writing a',\n 'document containing a field it has no schema for. There is no flag to switch that off.',\n].join('\\n');\n\n/** The result of one run: what was written, so a caller can print it or assert on it. */\nexport class CliResult {\n constructor(\n readonly written: readonly string[],\n readonly artifacts: readonly GeneratedArtifact[],\n ) {}\n}\n\n/**\n * `wp-openapi`'s argument parsing and composition, with NO process-level concerns in it — no\n * `process.exit`, no `console` — so the whole command is exercised by the suite exactly as a user\n * runs it. The bin is the only thing that knows about the process.\n *\n * `--manifest` and `--out` are REQUIRED and have no defaults. A defaulted `--out` writes generated\n * documents somewhere the caller did not name, and a defaulted `--manifest` picks a service out of\n * whatever directory the command happened to start in.\n */\nexport class OpenApiCli {\n private readonly loader = new InputsLoader();\n private readonly generator = new OpenApiGenerator();\n private readonly writer = new ArtifactWriter();\n\n /** @param argv the arguments AFTER the program name. @param cwd what relative paths resolve against. */\n run(argv: readonly string[], cwd: string): CliResult {\n const manifest = this.valueOf(argv, MANIFEST);\n const out = this.valueOf(argv, OUT);\n if (manifest === undefined || out === undefined) {\n throw new OpenApiGenerationError(\n `wp-openapi needs both ${MANIFEST} and ${OUT}`,\n 'wp-openapi',\n `Run: wp-openapi ${MANIFEST} <openapi.manifest.json> ${OUT} <dir>`,\n );\n }\n const inputs = this.loader.load(path.resolve(cwd, manifest));\n const documents = this.generator.generate(inputs);\n const artifacts = this.writer.artifacts(documents, this.formatOf(argv));\n return new CliResult(this.writer.write(path.resolve(cwd, out), artifacts), artifacts);\n }\n\n wantsHelp(argv: readonly string[]): boolean {\n return argv.includes(HELP);\n }\n\n /** `both` unless told otherwise: the second serialization comes free from the same document. */\n private formatOf(argv: readonly string[]): OutputFormat {\n const declared = this.valueOf(argv, FORMAT);\n if (declared === undefined) {\n return 'both';\n }\n if (!FORMATS.includes(declared)) {\n throw new OpenApiGenerationError(\n `unknown ${FORMAT} '${declared}'`,\n 'wp-openapi',\n `${FORMAT} takes one of: ${FORMATS.join(', ')}.`,\n );\n }\n return declared as OutputFormat;\n }\n\n /**\n * The value after a flag. An UNKNOWN flag is refused rather than ignored: a mistyped `--manifets`\n * would otherwise fall through to \"needs both flags\", which sends the reader looking at the wrong\n * thing.\n */\n private valueOf(argv: readonly string[], flag: string): string | undefined {\n for (let i = 0; i < argv.length; i++) {\n const argument = argv[i]!;\n if (argument === flag) {\n return argv[i + 1];\n }\n if (argument.startsWith('--') && !FLAGS.includes(argument)) {\n throw new OpenApiGenerationError(\n `unknown flag '${argument}'`,\n 'wp-openapi',\n `wp-openapi takes ${FLAGS.join(', ')}, and nothing else.`,\n );\n }\n }\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"OpenApiCli.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/cli/OpenApiCli.ts"],"names":[],"mappings":";;;;AAAA,wDAAkC;AAClC,oDAA2C;AAC3C,4DAA+F;AAC/F,2DAAyF;AAEzF,mEAAgE;AAChE,uDAAoD;AACpD,sEAAmE;AAEnE,MAAM,QAAQ,GAAG,YAAY,CAAC;AAC9B,MAAM,GAAG,GAAG,OAAO,CAAC;AACpB,MAAM,MAAM,GAAG,UAAU,CAAC;AAC1B,MAAM,IAAI,GAAG,QAAQ,CAAC;AAEtB,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;AACtC,MAAM,OAAO,GAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,QAAA,KAAK,GAAG;IACjB,qFAAqF;IACrF,EAAE;IACF,6FAA6F;IAC7F,+FAA+F;IAC/F,EAAE;IACF,uFAAuF;IACvF,qGAAqG;IACrG,mFAAmF;IACnF,EAAE;IACF,4FAA4F;IAC5F,6FAA6F;IAC7F,EAAE;IACF,wFAAwF;IACxF,yDAAyD;IACzD,EAAE;IACF,4FAA4F;IAC5F,wFAAwF;CAC3F,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,yFAAyF;AACzF,MAAa,SAAS;IAEL;IACA;IAEA;IAJb,YACa,OAA0B,EAC1B,SAAuC;IAChD,oFAAoF;IAC3E,kBAA6C,EAAE;QAH/C,YAAO,GAAP,OAAO,CAAmB;QAC1B,cAAS,GAAT,SAAS,CAA8B;QAEvC,oBAAe,GAAf,eAAe,CAAgC;IACzD,CAAC;CACP;AAPD,8BAOC;AAED,2FAA2F;AAC3F,MAAM,YAAY;IAED;IACA;IAFb,YACa,SAAuC,EACvC,OAAkC;QADlC,cAAS,GAAT,SAAS,CAA8B;QACvC,YAAO,GAAP,OAAO,CAA2B;IAC5C,CAAC;CACP;AAED;;;;;;;;GAQG;AACH,MAAa,UAAU;IACF,MAAM,GAAG,IAAI,2BAAY,EAAE,CAAC;IAC5B,SAAS,GAAG,IAAI,mCAAgB,EAAE,CAAC;IACnC,MAAM,GAAG,IAAI,+BAAc,EAAE,CAAC;IAE/C,wGAAwG;IACxG,GAAG,CAAC,IAAuB,EAAE,GAAW;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACpC,IAAI,QAAQ,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAC9C,MAAM,IAAI,+CAAsB,CAC5B,yBAAyB,QAAQ,QAAQ,GAAG,EAAE,EAC9C,YAAY,EACZ,mBAAmB,QAAQ,4BAA4B,GAAG,QAAQ,CACrE,CAAC;QACN,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG;YACd,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACxD,GAAG,GAAG,CAAC,SAAS;SACnB,CAAC;QACF,OAAO,IAAI,SAAS,CAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,SAAS,CAAC,EACpD,SAAS,EACT,GAAG,CAAC,OAAO,CACd,CAAC;IACN,CAAC;IAED,SAAS,CAAC,IAAuB;QAC7B,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;;;;;OAUG;IACH,+FAA+F;IACvF,MAAM,CAAC,UAAU,CAAC,MAAwB;QAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS;aAC1B,MAAM,CAAC,CAAC,QAAuB,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAG,CAAC,CAAC;aAC1E,GAAG,CAAC,CAAC,QAAuB,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACtD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,QAAQ,GAAqB,iCAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACvE,MAAM,SAAS,GACX,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAC/B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,CAAC,IAAI,kCAAiB,CAAC,gBAAgB,EAAE,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACnF,OAAO,IAAI,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,gGAAgG;IACxF,QAAQ,CAAC,IAAuB;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,MAAM,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,+CAAsB,CAC5B,WAAW,MAAM,KAAK,QAAQ,GAAG,EACjC,YAAY,EACZ,GAAG,MAAM,kBAAkB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACnD,CAAC;QACN,CAAC;QACD,OAAO,QAAwB,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACK,OAAO,CAAC,IAAuB,EAAE,IAAY;QACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;YAC1B,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACpB,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACvB,CAAC;YACD,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACzD,MAAM,IAAI,+CAAsB,CAC5B,iBAAiB,QAAQ,GAAG,EAC5B,YAAY,EACZ,oBAAoB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAC5D,CAAC;YACN,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;CACJ;AAlGD,gCAkGC","sourcesContent":["import * as path from 'node:path';\nimport { MCP } from '@webpieces/core-util';\nimport { McpCatalogRender, McpSchemaRenderer, SkippedMcpTool } from '@webpieces/api-doc-model';\nimport { ArtifactWriter, GeneratedArtifact, OutputFormat } from '../emit/ArtifactWriter';\nimport { ContractModel, GenerationInputs } from '../generate/GenerationInputs';\nimport { OpenApiGenerator } from '../generate/OpenApiGenerator';\nimport { InputsLoader } from '../load/InputsLoader';\nimport { OpenApiGenerationError } from '../OpenApiGenerationError';\n\nconst MANIFEST = '--manifest';\nconst OUT = '--out';\nconst FORMAT = '--format';\nconst HELP = '--help';\n\nconst FLAGS = [MANIFEST, OUT, FORMAT];\nconst FORMATS: readonly string[] = ['json', 'yaml', 'both'];\n\nexport const USAGE = [\n 'wp-openapi --manifest <openapi.manifest.json> --out <dir> [--format json|yaml|both]',\n '',\n 'Renders the contracts a manifest names into OpenAPI 3.1.0. WHICH documents are written is a',\n \"property of the contracts themselves — each one's @ApiType(...) names the documents it feeds:\",\n '',\n ' full-private-openapi every contract declaring SVC_TO_SVC, hidden methods included',\n ' public-openapi contracts declaring EXTERNAL_CUSTOMER, minus every { hidden: true } method',\n ' mcp-openapi contracts declaring MCP, carrying the x-mcp-* extensions',\n '',\n 'Alongside them, mcp-tools.json is written whenever some contract declares MCP: the RUNTIME',\n 'artifact WpMcpServer is constructed with. It is not a document, so --format does not apply.',\n '',\n 'A document no contract asked for is not written. --format chooses the serialization of',\n 'whichever documents were written, and defaults to both.',\n '',\n 'It exits non-zero, naming the JSON pointer of every offending field, rather than writing a',\n 'document containing a field it has no schema for. There is no flag to switch that off.',\n].join('\\n');\n\n/** The result of one run: what was written, so a caller can print it or assert on it. */\nexport class CliResult {\n constructor(\n readonly written: readonly string[],\n readonly artifacts: readonly GeneratedArtifact[],\n /** `@WpMcpTool`s the MCP catalog could not carry, with the reason. Never silent. */\n readonly skippedMcpTools: readonly SkippedMcpTool[] = [],\n ) {}\n}\n\n/** The MCP half of one run: the artifact to write, if any, and what it could not carry. */\nclass McpArtifacts {\n constructor(\n readonly artifacts: readonly GeneratedArtifact[],\n readonly skipped: readonly SkippedMcpTool[],\n ) {}\n}\n\n/**\n * `wp-openapi`'s argument parsing and composition, with NO process-level concerns in it — no\n * `process.exit`, no `console` — so the whole command is exercised by the suite exactly as a user\n * runs it. The bin is the only thing that knows about the process.\n *\n * `--manifest` and `--out` are REQUIRED and have no defaults. A defaulted `--out` writes generated\n * documents somewhere the caller did not name, and a defaulted `--manifest` picks a service out of\n * whatever directory the command happened to start in.\n */\nexport class OpenApiCli {\n private readonly loader = new InputsLoader();\n private readonly generator = new OpenApiGenerator();\n private readonly writer = new ArtifactWriter();\n\n /** @param argv the arguments AFTER the program name. @param cwd what relative paths resolve against. */\n run(argv: readonly string[], cwd: string): CliResult {\n const manifest = this.valueOf(argv, MANIFEST);\n const out = this.valueOf(argv, OUT);\n if (manifest === undefined || out === undefined) {\n throw new OpenApiGenerationError(\n `wp-openapi needs both ${MANIFEST} and ${OUT}`,\n 'wp-openapi',\n `Run: wp-openapi ${MANIFEST} <openapi.manifest.json> ${OUT} <dir>`,\n );\n }\n const inputs = this.loader.load(path.resolve(cwd, manifest));\n const documents = this.generator.generate(inputs);\n const mcp = OpenApiCli.mcpCatalog(inputs);\n const artifacts = [\n ...this.writer.artifacts(documents, this.formatOf(argv)),\n ...mcp.artifacts,\n ];\n return new CliResult(\n this.writer.write(path.resolve(cwd, out), artifacts),\n artifacts,\n mcp.skipped,\n );\n }\n\n wantsHelp(argv: readonly string[]): boolean {\n return argv.includes(HELP);\n }\n\n /**\n * `mcp-tools.json` — the RUNTIME artifact, beside the three documents.\n *\n * It is not a document and is not serialized by `--format`: `WpMcpServer` is constructed with it,\n * and `McpToolRegistry` fails fast at boot when a registered `@WpMcpTool` is missing from it. That\n * is what took the MCP runtime off reflect-metadata (#984) — the schema an agent is shown and the\n * schema the server accepts are now the same bytes, rather than two derivations of one contract.\n *\n * Written only when some contract declares `MCP`, which is the same rule `mcp-openapi.json`\n * follows, for the same reason: an empty file is a claim that there are no tools.\n */\n // webpieces-disable no-function-outside-class -- private static composition step of this class\n private static mcpCatalog(inputs: GenerationInputs): McpArtifacts {\n const models = inputs.contracts\n .filter((contract: ContractModel) => contract.model.apiTypes.includes(MCP))\n .map((contract: ContractModel) => contract.model);\n if (models.length === 0) {\n return new McpArtifacts([], []);\n }\n const rendered: McpCatalogRender = McpSchemaRenderer.catalogOf(models);\n const artifacts =\n rendered.catalog.tools.length === 0\n ? []\n : [new GeneratedArtifact('mcp-tools.json', rendered.catalog.toJsonText())];\n return new McpArtifacts(artifacts, rendered.skipped);\n }\n\n /** `both` unless told otherwise: the second serialization comes free from the same document. */\n private formatOf(argv: readonly string[]): OutputFormat {\n const declared = this.valueOf(argv, FORMAT);\n if (declared === undefined) {\n return 'both';\n }\n if (!FORMATS.includes(declared)) {\n throw new OpenApiGenerationError(\n `unknown ${FORMAT} '${declared}'`,\n 'wp-openapi',\n `${FORMAT} takes one of: ${FORMATS.join(', ')}.`,\n );\n }\n return declared as OutputFormat;\n }\n\n /**\n * The value after a flag. An UNKNOWN flag is refused rather than ignored: a mistyped `--manifets`\n * would otherwise fall through to \"needs both flags\", which sends the reader looking at the wrong\n * thing.\n */\n private valueOf(argv: readonly string[], flag: string): string | undefined {\n for (let i = 0; i < argv.length; i++) {\n const argument = argv[i]!;\n if (argument === flag) {\n return argv[i + 1];\n }\n if (argument.startsWith('--') && !FLAGS.includes(argument)) {\n throw new OpenApiGenerationError(\n `unknown flag '${argument}'`,\n 'wp-openapi',\n `wp-openapi takes ${FLAGS.join(', ')}, and nothing else.`,\n );\n }\n }\n return undefined;\n }\n}\n"]}
@@ -36,6 +36,12 @@ class WpOpenApiMain {
36
36
  for (const file of result.written) {
37
37
  out.write(`wrote ${file}\n`);
38
38
  }
39
+ // NAMED, never silent. A tool with no MCP schema is one no server can carry, and
40
+ // `McpToolRegistry` refuses to boot while it is still declared — see
41
+ // `McpSchemaRenderer.catalogOf` for why that is the right place for the failure.
42
+ for (const skipped of result.skippedMcpTools) {
43
+ out.write(`no MCP schema, left out of mcp-tools.json: ${skipped}\n`);
44
+ }
39
45
  return 0;
40
46
  // webpieces-disable no-any-unknown -- the catch binding, which TypeScript types for us
41
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WpOpenApiMain.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/cli/WpOpenApiMain.ts"],"names":[],"mappings":";;;AAAA,6CAA4D;AAC5D,sEAAmE;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,aAAa;IACL,GAAG,GAAG,IAAI,uBAAU,EAAE,CAAC;IAExC,2EAA2E;IAC3E,GAAG,CAAC,IAAuB,EAAE,GAAW,EAAE,GAAuB;QAC7D,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,GAAG,CAAC,KAAK,CAAC,GAAG,kBAAK,IAAI,CAAC,CAAC;YACxB,OAAO,CAAC,CAAC;QACb,CAAC;QACD,sGAAsG;QACtG,IAAI,CAAC;YACD,MAAM,MAAM,GAAc,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAClD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBAChC,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;YACjC,CAAC;YACD,OAAO,CAAC,CAAC;YACT,uFAAuF;QAC3F,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,6BAA6B;YAC7B,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnC,OAAO,CAAC,CAAC;QACb,CAAC;IACL,CAAC;IAED,yGAAyG;IACjG,MAAM,CAAC,GAAY;QACvB,IAAI,CAAC,CAAC,GAAG,YAAY,+CAAsB,CAAC,EAAE,CAAC;YAC3C,OAAO,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACpF,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACrD,KAAK,MAAM,OAAO,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;QAC/B,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;CACJ;AApCD,sCAoCC","sourcesContent":["import { CliResult, OpenApiCli, USAGE } from './OpenApiCli';\nimport { OpenApiGenerationError } from '../OpenApiGenerationError';\n\n/**\n * The body of the `wp-openapi` bin: argument handling, the SINGLE top-level handler, and the exit\n * code — with nothing that touches the process itself, so the suite runs the whole command.\n *\n * ## The single top-level handler\n *\n * Everything below here THROWS {@link OpenApiGenerationError} and prints nothing, so this is the only\n * renderer of a failure and the only writer of an exit code (`.claude/review/error-output.md`). It\n * renders from the error's FIELDS — the message, the location, the cure, the pointers — rather than\n * from a string somebody baked a cure into, so one audience's formatting never freezes into a thrower.\n *\n * ## Why an unmapped field exits NON-ZERO\n *\n * A field with no schema publishes as \"anything\", so a document containing one is a green build\n * handing a partner a field with no shape. That is worse than no document, because nobody reads a\n * published contract looking for the field that was quietly left undefined. There is deliberately no\n * flag to downgrade it — the cure is at the contract, by naming the type, and every pointer needed to\n * do that is printed.\n */\nexport class WpOpenApiMain {\n private readonly cli = new OpenApiCli();\n\n /** @returns the process exit code. 0 on success, 1 on a stated failure. */\n run(argv: readonly string[], cwd: string, out: NodeJS.WriteStream): number {\n if (this.cli.wantsHelp(argv)) {\n out.write(`${USAGE}\\n`);\n return 0;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- this IS the single top-level handler\n try {\n const result: CliResult = this.cli.run(argv, cwd);\n for (const file of result.written) {\n out.write(`wrote ${file}\\n`);\n }\n return 0;\n // webpieces-disable no-any-unknown -- the catch binding, which TypeScript types for us\n } catch (err: unknown) {\n //const error = toError(err);\n out.write(`${this.render(err)}\\n`);\n return 1;\n }\n }\n\n // webpieces-disable no-any-unknown -- a caught value; this method is the audience-facing narrowing of it\n private render(err: unknown): string {\n if (!(err instanceof OpenApiGenerationError)) {\n return `wp-openapi failed: ${err instanceof Error ? err.message : String(err)}`;\n }\n const lines = [`wp-openapi refused: ${err.message}`];\n for (const pointer of err.pointers) {\n lines.push(` ${pointer}`);\n }\n lines.push(` ${err.cure}`);\n return lines.join('\\n');\n }\n}\n"]}
1
+ {"version":3,"file":"WpOpenApiMain.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/cli/WpOpenApiMain.ts"],"names":[],"mappings":";;;AAAA,6CAA4D;AAC5D,sEAAmE;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,aAAa;IACL,GAAG,GAAG,IAAI,uBAAU,EAAE,CAAC;IAExC,2EAA2E;IAC3E,GAAG,CAAC,IAAuB,EAAE,GAAW,EAAE,GAAuB;QAC7D,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,GAAG,CAAC,KAAK,CAAC,GAAG,kBAAK,IAAI,CAAC,CAAC;YACxB,OAAO,CAAC,CAAC;QACb,CAAC;QACD,sGAAsG;QACtG,IAAI,CAAC;YACD,MAAM,MAAM,GAAc,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAClD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBAChC,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;YACjC,CAAC;YACD,iFAAiF;YACjF,qEAAqE;YACrE,iFAAiF;YACjF,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;gBAC3C,GAAG,CAAC,KAAK,CAAC,8CAA8C,OAAO,IAAI,CAAC,CAAC;YACzE,CAAC;YACD,OAAO,CAAC,CAAC;YACT,uFAAuF;QAC3F,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,6BAA6B;YAC7B,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnC,OAAO,CAAC,CAAC;QACb,CAAC;IACL,CAAC;IAED,yGAAyG;IACjG,MAAM,CAAC,GAAY;QACvB,IAAI,CAAC,CAAC,GAAG,YAAY,+CAAsB,CAAC,EAAE,CAAC;YAC3C,OAAO,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACpF,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACrD,KAAK,MAAM,OAAO,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;QAC/B,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;CACJ;AA1CD,sCA0CC","sourcesContent":["import { CliResult, OpenApiCli, USAGE } from './OpenApiCli';\nimport { OpenApiGenerationError } from '../OpenApiGenerationError';\n\n/**\n * The body of the `wp-openapi` bin: argument handling, the SINGLE top-level handler, and the exit\n * code — with nothing that touches the process itself, so the suite runs the whole command.\n *\n * ## The single top-level handler\n *\n * Everything below here THROWS {@link OpenApiGenerationError} and prints nothing, so this is the only\n * renderer of a failure and the only writer of an exit code (`.claude/review/error-output.md`). It\n * renders from the error's FIELDS — the message, the location, the cure, the pointers — rather than\n * from a string somebody baked a cure into, so one audience's formatting never freezes into a thrower.\n *\n * ## Why an unmapped field exits NON-ZERO\n *\n * A field with no schema publishes as \"anything\", so a document containing one is a green build\n * handing a partner a field with no shape. That is worse than no document, because nobody reads a\n * published contract looking for the field that was quietly left undefined. There is deliberately no\n * flag to downgrade it — the cure is at the contract, by naming the type, and every pointer needed to\n * do that is printed.\n */\nexport class WpOpenApiMain {\n private readonly cli = new OpenApiCli();\n\n /** @returns the process exit code. 0 on success, 1 on a stated failure. */\n run(argv: readonly string[], cwd: string, out: NodeJS.WriteStream): number {\n if (this.cli.wantsHelp(argv)) {\n out.write(`${USAGE}\\n`);\n return 0;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- this IS the single top-level handler\n try {\n const result: CliResult = this.cli.run(argv, cwd);\n for (const file of result.written) {\n out.write(`wrote ${file}\\n`);\n }\n // NAMED, never silent. A tool with no MCP schema is one no server can carry, and\n // `McpToolRegistry` refuses to boot while it is still declared — see\n // `McpSchemaRenderer.catalogOf` for why that is the right place for the failure.\n for (const skipped of result.skippedMcpTools) {\n out.write(`no MCP schema, left out of mcp-tools.json: ${skipped}\\n`);\n }\n return 0;\n // webpieces-disable no-any-unknown -- the catch binding, which TypeScript types for us\n } catch (err: unknown) {\n //const error = toError(err);\n out.write(`${this.render(err)}\\n`);\n return 1;\n }\n }\n\n // webpieces-disable no-any-unknown -- a caught value; this method is the audience-facing narrowing of it\n private render(err: unknown): string {\n if (!(err instanceof OpenApiGenerationError)) {\n return `wp-openapi failed: ${err instanceof Error ? err.message : String(err)}`;\n }\n const lines = [`wp-openapi refused: ${err.message}`];\n for (const pointer of err.pointers) {\n lines.push(` ${pointer}`);\n }\n lines.push(` ${err.cure}`);\n return lines.join('\\n');\n }\n}\n"]}