@webpieces/openapi-generator 0.0.1

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 (68) hide show
  1. package/README.md +118 -0
  2. package/package.json +32 -0
  3. package/src/OpenApiGenerationError.d.ts +41 -0
  4. package/src/OpenApiGenerationError.js +45 -0
  5. package/src/OpenApiGenerationError.js.map +1 -0
  6. package/src/cli/OpenApiCli.d.ts +33 -0
  7. package/src/cli/OpenApiCli.js +100 -0
  8. package/src/cli/OpenApiCli.js.map +1 -0
  9. package/src/cli/WpOpenApiMain.d.ts +25 -0
  10. package/src/cli/WpOpenApiMain.js +62 -0
  11. package/src/cli/WpOpenApiMain.js.map +1 -0
  12. package/src/cli/wp-openapi.d.ts +2 -0
  13. package/src/cli/wp-openapi.js +18 -0
  14. package/src/cli/wp-openapi.js.map +1 -0
  15. package/src/emit/ArtifactWriter.d.ts +44 -0
  16. package/src/emit/ArtifactWriter.js +75 -0
  17. package/src/emit/ArtifactWriter.js.map +1 -0
  18. package/src/generate/DocumentSelection.d.ts +96 -0
  19. package/src/generate/DocumentSelection.js +153 -0
  20. package/src/generate/DocumentSelection.js.map +1 -0
  21. package/src/generate/GenerationInputs.d.ts +67 -0
  22. package/src/generate/GenerationInputs.js +88 -0
  23. package/src/generate/GenerationInputs.js.map +1 -0
  24. package/src/generate/OpenApiGenerator.d.ts +113 -0
  25. package/src/generate/OpenApiGenerator.js +306 -0
  26. package/src/generate/OpenApiGenerator.js.map +1 -0
  27. package/src/generate/OperationRenderer.d.ts +129 -0
  28. package/src/generate/OperationRenderer.js +256 -0
  29. package/src/generate/OperationRenderer.js.map +1 -0
  30. package/src/generate/SchemaRenderer.d.ts +89 -0
  31. package/src/generate/SchemaRenderer.js +236 -0
  32. package/src/generate/SchemaRenderer.js.map +1 -0
  33. package/src/generate/SecurityDeriver.d.ts +39 -0
  34. package/src/generate/SecurityDeriver.js +81 -0
  35. package/src/generate/SecurityDeriver.js.map +1 -0
  36. package/src/index.d.ts +32 -0
  37. package/src/index.js +70 -0
  38. package/src/index.js.map +1 -0
  39. package/src/json/JsonObject.d.ts +35 -0
  40. package/src/json/JsonObject.js +32 -0
  41. package/src/json/JsonObject.js.map +1 -0
  42. package/src/json/JsonWriter.d.ts +18 -0
  43. package/src/json/JsonWriter.js +48 -0
  44. package/src/json/JsonWriter.js.map +1 -0
  45. package/src/json/YamlReader.d.ts +35 -0
  46. package/src/json/YamlReader.js +111 -0
  47. package/src/json/YamlReader.js.map +1 -0
  48. package/src/json/YamlWriter.d.ts +35 -0
  49. package/src/json/YamlWriter.js +88 -0
  50. package/src/json/YamlWriter.js.map +1 -0
  51. package/src/load/ExportedConstantFolder.d.ts +21 -0
  52. package/src/load/ExportedConstantFolder.js +57 -0
  53. package/src/load/ExportedConstantFolder.js.map +1 -0
  54. package/src/load/ForeignFailure.d.ts +24 -0
  55. package/src/load/ForeignFailure.js +41 -0
  56. package/src/load/ForeignFailure.js.map +1 -0
  57. package/src/load/InputsLoader.d.ts +43 -0
  58. package/src/load/InputsLoader.js +148 -0
  59. package/src/load/InputsLoader.js.map +1 -0
  60. package/src/manifest/JsonReader.d.ts +37 -0
  61. package/src/manifest/JsonReader.js +109 -0
  62. package/src/manifest/JsonReader.js.map +1 -0
  63. package/src/manifest/ManifestLoader.d.ts +20 -0
  64. package/src/manifest/ManifestLoader.js +69 -0
  65. package/src/manifest/ManifestLoader.js.map +1 -0
  66. package/src/manifest/OpenApiManifest.d.ts +116 -0
  67. package/src/manifest/OpenApiManifest.js +142 -0
  68. package/src/manifest/OpenApiManifest.js.map +1 -0
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @webpieces/openapi-generator
2
+
3
+ Render a webpieces API contract to **OpenAPI 3.1.0**.
4
+
5
+ ```bash
6
+ wp-openapi --manifest path/to/openapi.manifest.json --out path/to/generated [--format json|yaml|both]
7
+ ```
8
+
9
+ ## Which documents you get is a property of your CONTRACTS
10
+
11
+ ```typescript
12
+ @ApiPath('/stores')
13
+ @ApiType(SVC_TO_SVC, EXTERNAL_CUSTOMER) // which documents this contract feeds
14
+ export abstract class StoreApi { ... }
15
+ ```
16
+
17
+ | `@ApiType` value | file | contents |
18
+ |---|---|---|
19
+ | `SVC_TO_SVC` | `full-private-openapi.json` | every method, hidden ones included, plus an internal-use-only line in `info.description`. Nothing renders it for humans |
20
+ | `EXTERNAL_CUSTOMER` | `public-openapi.json` | the customer contract, minus every `{ hidden: true }` method |
21
+ | `MCP` | `mcp-openapi.json` | the same contracts with the `x-mcp-*` extensions an MCP generator reads |
22
+
23
+ **A document no contract declares is not written.** No `@ApiType` at all means `SVC_TO_SVC` only —
24
+ fail-closed: a contract reaches customers by SAYING SO, so `grep -rn EXTERNAL_CUSTOMER` is your entire
25
+ customer-facing surface on one screen.
26
+
27
+ `--format` picks the serialization of whichever documents exist, and defaults to `both`.
28
+
29
+ `diff full-private-openapi.json public-openapi.json` is the complete list of what you do not show a
30
+ customer. Commit both, and hiding a method shows up in the PR that hides it.
31
+
32
+ ## Hiding ONE method
33
+
34
+ ```typescript
35
+ /**
36
+ * Rebuilds a store's menu cache.
37
+ *
38
+ * Not published: an operator tool, and the customer contract has no concept of our cache.
39
+ */
40
+ @Endpoint(POST, '/reindex', WRITE, RPC, { hidden: true })
41
+ ```
42
+
43
+ The reason goes in the JSDoc — where the rest of the endpoint's documentation already lives. The
44
+ method is absent from `public-openapi.json`: no path, no operation, no schema, no prose, and nothing
45
+ announcing that anything was withheld.
46
+
47
+ **Hiding is a documentation decision and never an access-control one.** The route is still served and
48
+ still demands whatever credential it declares.
49
+
50
+ ## The manifest
51
+
52
+ It carries exactly what is **not a property of the code**:
53
+
54
+ ```json
55
+ {
56
+ "title": "Example Orders API",
57
+ "version": "1.0.0",
58
+ "descriptionFile": "description.md",
59
+ "servers": [{ "url": "https://api.example.com", "description": "Production." }],
60
+ "apis": [
61
+ { "entry": "src/OrdersApi.ts", "tag": "Orders" },
62
+ { "entry": "src/EventsApi.ts", "tag": "Webhooks", "kind": "webhook" }
63
+ ],
64
+ "securitySchemeNames": ["PartnerApiKey", "PartnerOrganization"],
65
+ "errors": {
66
+ "entry": "src/ApiErrors.ts",
67
+ "type": "ApiErrorResponse",
68
+ "responses": [{ "status": "400", "description": "The request failed validation." }]
69
+ },
70
+ "responseHeaders": [
71
+ {
72
+ "entry": "src/ResponseHeaders.ts",
73
+ "nameConstant": "REQUEST_ID_HEADER",
74
+ "description": "Correlates this response with our logs."
75
+ }
76
+ ]
77
+ }
78
+ ```
79
+
80
+ Every path is relative to the manifest's own directory.
81
+
82
+ **The ORDER of `apis[]` is the published sidebar order.** Alphabetising it is not a cleanup.
83
+
84
+ **`securitySchemeNames` holds the published KEYS only.** The schemes themselves, and the AND-ed
85
+ `security` requirement, are DERIVED from the contract's `@WpAuthApiKey(regime, credentials)`. A
86
+ `securitySchemes` block in the manifest would be a second copy of header names the running server
87
+ never reads, and nothing could contradict it.
88
+
89
+ **`responseHeaders` names a CONSTANT, never a header string.** JSON cannot import, so a literal there
90
+ is a copy that a rename leaves silently stale. The generator folds the const and hard-fails if it
91
+ cannot.
92
+
93
+ ## MCP
94
+
95
+ `MCP` in `@ApiType` requires `@WpMcpTool` on at least one method, and `@WpMcpTool` on a contract that
96
+ does not declare `MCP` is an error. Membership has one spelling.
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.
103
+
104
+ ## It refuses to publish a field it has no schema for
105
+
106
+ A field the model could not map renders as an empty schema, which in JSON Schema means "anything".
107
+ `wp-openapi` exits non-zero instead, naming the JSON pointer of every one, and writes nothing. There
108
+ is no flag to switch that off — the cure is at the contract, by naming the type.
109
+
110
+ ## Worked example
111
+
112
+ `apps/app-example/partner-api` in this repo: a real contract, its manifest, the committed documents,
113
+ and a spec that regenerates and diffs them.
114
+
115
+ ## Dependencies
116
+
117
+ `typescript`, `@webpieces/api-doc-model` and `@webpieces/core-util`. No nx, no repo paths, no app
118
+ types.
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@webpieces/openapi-generator",
3
+ "version": "0.0.1",
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
+ "type": "commonjs",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
+ "author": "Dean Hiller",
9
+ "license": "Apache-2.0",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/deanhiller/webpieces-ts.git",
13
+ "directory": "packages/docs/openapi-generator"
14
+ },
15
+ "keywords": [
16
+ "webpieces",
17
+ "openapi",
18
+ "mcp",
19
+ "docs",
20
+ "typescript"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "dependencies": {
26
+ "@webpieces/api-doc-model": "0.4.803",
27
+ "typescript": "5.9.3"
28
+ },
29
+ "bin": {
30
+ "wp-openapi": "./src/cli/wp-openapi.js"
31
+ }
32
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The ONE failure type this package throws. Generation either produces a complete pair of documents
3
+ * or throws this — it never prints a warning, never writes a partial file, and never guesses.
4
+ *
5
+ * It is a structured throw to the single top-level handler in `wp-openapi`, per
6
+ * `.claude/review/error-output.md`: `location` and `cure` are carried as FIELDS rather than baked
7
+ * into `message`, and `pointers` carries the JSON pointers of an unmapped-type refusal, so the CLI
8
+ * renders them per audience and this class hand-numbers nothing.
9
+ *
10
+ * It is NOT `RuleFailError`. That type lives in `@webpieces/rules-config` — the TOOLING stream, which
11
+ * a repo pins one release behind on purpose — and this package publishes with the SERVER libs so an
12
+ * app's `@webpieces/core-util` pin pins a generator that understands that app's decorators (see
13
+ * `responsibilities.md`). Depending across the two streams to reuse an error class would couple the
14
+ * generator's release to the rule engine's, which is the coupling the epic exists to avoid. This is
15
+ * the same call `@webpieces/api-doc-model` made, and for a reason of the same size.
16
+ */
17
+ export declare class OpenApiGenerationError extends Error {
18
+ /** Where the problem IS — a manifest path, a contract file, or `<manifest>#/apis/0`. */
19
+ readonly location: string;
20
+ /** What to do instead, in one sentence. No numbering — the CLI owns that. */
21
+ readonly cure: string;
22
+ /**
23
+ * JSON pointers into the document that WOULD have been written, one per offending field.
24
+ * Empty for every failure that is not the unmapped-type refusal. It is a separate field
25
+ * rather than a list glued into `message` because the list is the actionable part: an agent
26
+ * fixes the contract one pointer at a time.
27
+ */
28
+ readonly pointers: readonly string[];
29
+ constructor(message: string,
30
+ /** Where the problem IS — a manifest path, a contract file, or `<manifest>#/apis/0`. */
31
+ location: string,
32
+ /** What to do instead, in one sentence. No numbering — the CLI owns that. */
33
+ cure: string,
34
+ /**
35
+ * JSON pointers into the document that WOULD have been written, one per offending field.
36
+ * Empty for every failure that is not the unmapped-type refusal. It is a separate field
37
+ * rather than a list glued into `message` because the list is the actionable part: an agent
38
+ * fixes the contract one pointer at a time.
39
+ */
40
+ pointers?: readonly string[]);
41
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenApiGenerationError = void 0;
4
+ /**
5
+ * The ONE failure type this package throws. Generation either produces a complete pair of documents
6
+ * or throws this — it never prints a warning, never writes a partial file, and never guesses.
7
+ *
8
+ * It is a structured throw to the single top-level handler in `wp-openapi`, per
9
+ * `.claude/review/error-output.md`: `location` and `cure` are carried as FIELDS rather than baked
10
+ * into `message`, and `pointers` carries the JSON pointers of an unmapped-type refusal, so the CLI
11
+ * renders them per audience and this class hand-numbers nothing.
12
+ *
13
+ * It is NOT `RuleFailError`. That type lives in `@webpieces/rules-config` — the TOOLING stream, which
14
+ * a repo pins one release behind on purpose — and this package publishes with the SERVER libs so an
15
+ * app's `@webpieces/core-util` pin pins a generator that understands that app's decorators (see
16
+ * `responsibilities.md`). Depending across the two streams to reuse an error class would couple the
17
+ * generator's release to the rule engine's, which is the coupling the epic exists to avoid. This is
18
+ * the same call `@webpieces/api-doc-model` made, and for a reason of the same size.
19
+ */
20
+ class OpenApiGenerationError extends Error {
21
+ location;
22
+ cure;
23
+ pointers;
24
+ constructor(message,
25
+ /** Where the problem IS — a manifest path, a contract file, or `<manifest>#/apis/0`. */
26
+ location,
27
+ /** What to do instead, in one sentence. No numbering — the CLI owns that. */
28
+ cure,
29
+ /**
30
+ * JSON pointers into the document that WOULD have been written, one per offending field.
31
+ * Empty for every failure that is not the unmapped-type refusal. It is a separate field
32
+ * rather than a list glued into `message` because the list is the actionable part: an agent
33
+ * fixes the contract one pointer at a time.
34
+ */
35
+ pointers = []) {
36
+ super(`${message} (${location})`);
37
+ this.location = location;
38
+ this.cure = cure;
39
+ this.pointers = pointers;
40
+ this.name = 'OpenApiGenerationError';
41
+ Object.setPrototypeOf(this, new.target.prototype);
42
+ }
43
+ }
44
+ exports.OpenApiGenerationError = OpenApiGenerationError;
45
+ //# sourceMappingURL=OpenApiGenerationError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenApiGenerationError.js","sourceRoot":"","sources":["../../../../../packages/docs/openapi-generator/src/OpenApiGenerationError.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAa,sBAAuB,SAAQ,KAAK;IAIhC;IAEA;IAOA;IAZb,YACI,OAAe;IACf,wFAAwF;IAC/E,QAAgB;IACzB,6EAA6E;IACpE,IAAY;IACrB;;;;;OAKG;IACM,WAA8B,EAAE;QAEzC,KAAK,CAAC,GAAG,OAAO,KAAK,QAAQ,GAAG,CAAC,CAAC;QAXzB,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,SAAI,GAAJ,IAAI,CAAQ;QAOZ,aAAQ,GAAR,QAAQ,CAAwB;QAGzC,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;CACJ;AAnBD,wDAmBC","sourcesContent":["/**\n * The ONE failure type this package throws. Generation either produces a complete pair of documents\n * or throws this — it never prints a warning, never writes a partial file, and never guesses.\n *\n * It is a structured throw to the single top-level handler in `wp-openapi`, per\n * `.claude/review/error-output.md`: `location` and `cure` are carried as FIELDS rather than baked\n * into `message`, and `pointers` carries the JSON pointers of an unmapped-type refusal, so the CLI\n * renders them per audience and this class hand-numbers nothing.\n *\n * It is NOT `RuleFailError`. That type lives in `@webpieces/rules-config` — the TOOLING stream, which\n * a repo pins one release behind on purpose — and this package publishes with the SERVER libs so an\n * app's `@webpieces/core-util` pin pins a generator that understands that app's decorators (see\n * `responsibilities.md`). Depending across the two streams to reuse an error class would couple the\n * generator's release to the rule engine's, which is the coupling the epic exists to avoid. This is\n * the same call `@webpieces/api-doc-model` made, and for a reason of the same size.\n */\nexport class OpenApiGenerationError extends Error {\n constructor(\n message: string,\n /** Where the problem IS — a manifest path, a contract file, or `<manifest>#/apis/0`. */\n readonly location: string,\n /** What to do instead, in one sentence. No numbering — the CLI owns that. */\n readonly cure: string,\n /**\n * JSON pointers into the document that WOULD have been written, one per offending field.\n * Empty for every failure that is not the unmapped-type refusal. It is a separate field\n * rather than a list glued into `message` because the list is the actionable part: an agent\n * fixes the contract one pointer at a time.\n */\n readonly pointers: readonly string[] = [],\n ) {\n super(`${message} (${location})`);\n this.name = 'OpenApiGenerationError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { GeneratedArtifact } from '../emit/ArtifactWriter';
2
+ export declare const USAGE: string;
3
+ /** The result of one run: what was written, so a caller can print it or assert on it. */
4
+ export declare class CliResult {
5
+ readonly written: readonly string[];
6
+ readonly artifacts: readonly GeneratedArtifact[];
7
+ constructor(written: readonly string[], artifacts: readonly GeneratedArtifact[]);
8
+ }
9
+ /**
10
+ * `wp-openapi`'s argument parsing and composition, with NO process-level concerns in it — no
11
+ * `process.exit`, no `console` — so the whole command is exercised by the suite exactly as a user
12
+ * runs it. The bin is the only thing that knows about the process.
13
+ *
14
+ * `--manifest` and `--out` are REQUIRED and have no defaults. A defaulted `--out` writes generated
15
+ * documents somewhere the caller did not name, and a defaulted `--manifest` picks a service out of
16
+ * whatever directory the command happened to start in.
17
+ */
18
+ export declare class OpenApiCli {
19
+ private readonly loader;
20
+ private readonly generator;
21
+ private readonly writer;
22
+ /** @param argv the arguments AFTER the program name. @param cwd what relative paths resolve against. */
23
+ run(argv: readonly string[], cwd: string): CliResult;
24
+ wantsHelp(argv: readonly string[]): boolean;
25
+ /** `both` unless told otherwise: the second serialization comes free from the same document. */
26
+ private formatOf;
27
+ /**
28
+ * The value after a flag. An UNKNOWN flag is refused rather than ignored: a mistyped `--manifets`
29
+ * would otherwise fall through to "needs both flags", which sends the reader looking at the wrong
30
+ * thing.
31
+ */
32
+ private valueOf;
33
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenApiCli = exports.CliResult = exports.USAGE = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path = tslib_1.__importStar(require("node:path"));
6
+ const ArtifactWriter_1 = require("../emit/ArtifactWriter");
7
+ const OpenApiGenerator_1 = require("../generate/OpenApiGenerator");
8
+ const InputsLoader_1 = require("../load/InputsLoader");
9
+ const OpenApiGenerationError_1 = require("../OpenApiGenerationError");
10
+ const MANIFEST = '--manifest';
11
+ const OUT = '--out';
12
+ const FORMAT = '--format';
13
+ const HELP = '--help';
14
+ const FLAGS = [MANIFEST, OUT, FORMAT];
15
+ const FORMATS = ['json', 'yaml', 'both'];
16
+ exports.USAGE = [
17
+ 'wp-openapi --manifest <openapi.manifest.json> --out <dir> [--format json|yaml|both]',
18
+ '',
19
+ 'Renders the contracts a manifest names into OpenAPI 3.1.0. WHICH documents are written is a',
20
+ "property of the contracts themselves — each one's @ApiType(...) names the documents it feeds:",
21
+ '',
22
+ ' full-private-openapi every contract declaring SVC_TO_SVC, hidden methods included',
23
+ ' public-openapi contracts declaring EXTERNAL_CUSTOMER, minus every { hidden: true } method',
24
+ ' mcp-openapi contracts declaring MCP, carrying the x-mcp-* extensions',
25
+ '',
26
+ 'A document no contract asked for is not written. --format chooses the serialization of',
27
+ 'whichever documents were written, and defaults to both.',
28
+ '',
29
+ 'It exits non-zero, naming the JSON pointer of every offending field, rather than writing a',
30
+ 'document containing a field it has no schema for. There is no flag to switch that off.',
31
+ ].join('\n');
32
+ /** The result of one run: what was written, so a caller can print it or assert on it. */
33
+ class CliResult {
34
+ written;
35
+ artifacts;
36
+ constructor(written, artifacts) {
37
+ this.written = written;
38
+ this.artifacts = artifacts;
39
+ }
40
+ }
41
+ exports.CliResult = CliResult;
42
+ /**
43
+ * `wp-openapi`'s argument parsing and composition, with NO process-level concerns in it — no
44
+ * `process.exit`, no `console` — so the whole command is exercised by the suite exactly as a user
45
+ * runs it. The bin is the only thing that knows about the process.
46
+ *
47
+ * `--manifest` and `--out` are REQUIRED and have no defaults. A defaulted `--out` writes generated
48
+ * documents somewhere the caller did not name, and a defaulted `--manifest` picks a service out of
49
+ * whatever directory the command happened to start in.
50
+ */
51
+ class OpenApiCli {
52
+ loader = new InputsLoader_1.InputsLoader();
53
+ generator = new OpenApiGenerator_1.OpenApiGenerator();
54
+ writer = new ArtifactWriter_1.ArtifactWriter();
55
+ /** @param argv the arguments AFTER the program name. @param cwd what relative paths resolve against. */
56
+ run(argv, cwd) {
57
+ const manifest = this.valueOf(argv, MANIFEST);
58
+ const out = this.valueOf(argv, OUT);
59
+ if (manifest === undefined || out === undefined) {
60
+ throw new OpenApiGenerationError_1.OpenApiGenerationError(`wp-openapi needs both ${MANIFEST} and ${OUT}`, 'wp-openapi', `Run: wp-openapi ${MANIFEST} <openapi.manifest.json> ${OUT} <dir>`);
61
+ }
62
+ const inputs = this.loader.load(path.resolve(cwd, manifest));
63
+ 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);
66
+ }
67
+ wantsHelp(argv) {
68
+ return argv.includes(HELP);
69
+ }
70
+ /** `both` unless told otherwise: the second serialization comes free from the same document. */
71
+ formatOf(argv) {
72
+ const declared = this.valueOf(argv, FORMAT);
73
+ if (declared === undefined) {
74
+ return 'both';
75
+ }
76
+ if (!FORMATS.includes(declared)) {
77
+ throw new OpenApiGenerationError_1.OpenApiGenerationError(`unknown ${FORMAT} '${declared}'`, 'wp-openapi', `${FORMAT} takes one of: ${FORMATS.join(', ')}.`);
78
+ }
79
+ return declared;
80
+ }
81
+ /**
82
+ * The value after a flag. An UNKNOWN flag is refused rather than ignored: a mistyped `--manifets`
83
+ * would otherwise fall through to "needs both flags", which sends the reader looking at the wrong
84
+ * thing.
85
+ */
86
+ valueOf(argv, flag) {
87
+ for (let i = 0; i < argv.length; i++) {
88
+ const argument = argv[i];
89
+ if (argument === flag) {
90
+ return argv[i + 1];
91
+ }
92
+ if (argument.startsWith('--') && !FLAGS.includes(argument)) {
93
+ throw new OpenApiGenerationError_1.OpenApiGenerationError(`unknown flag '${argument}'`, 'wp-openapi', `wp-openapi takes ${FLAGS.join(', ')}, and nothing else.`);
94
+ }
95
+ }
96
+ return undefined;
97
+ }
98
+ }
99
+ exports.OpenApiCli = OpenApiCli;
100
+ //# sourceMappingURL=OpenApiCli.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The body of the `wp-openapi` bin: argument handling, the SINGLE top-level handler, and the exit
3
+ * code — with nothing that touches the process itself, so the suite runs the whole command.
4
+ *
5
+ * ## The single top-level handler
6
+ *
7
+ * Everything below here THROWS {@link OpenApiGenerationError} and prints nothing, so this is the only
8
+ * renderer of a failure and the only writer of an exit code (`.claude/review/error-output.md`). It
9
+ * renders from the error's FIELDS — the message, the location, the cure, the pointers — rather than
10
+ * from a string somebody baked a cure into, so one audience's formatting never freezes into a thrower.
11
+ *
12
+ * ## Why an unmapped field exits NON-ZERO
13
+ *
14
+ * A field with no schema publishes as "anything", so a document containing one is a green build
15
+ * handing a partner a field with no shape. That is worse than no document, because nobody reads a
16
+ * published contract looking for the field that was quietly left undefined. There is deliberately no
17
+ * flag to downgrade it — the cure is at the contract, by naming the type, and every pointer needed to
18
+ * do that is printed.
19
+ */
20
+ export declare class WpOpenApiMain {
21
+ private readonly cli;
22
+ /** @returns the process exit code. 0 on success, 1 on a stated failure. */
23
+ run(argv: readonly string[], cwd: string, out: NodeJS.WriteStream): number;
24
+ private render;
25
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WpOpenApiMain = void 0;
4
+ const OpenApiCli_1 = require("./OpenApiCli");
5
+ const OpenApiGenerationError_1 = require("../OpenApiGenerationError");
6
+ /**
7
+ * The body of the `wp-openapi` bin: argument handling, the SINGLE top-level handler, and the exit
8
+ * code — with nothing that touches the process itself, so the suite runs the whole command.
9
+ *
10
+ * ## The single top-level handler
11
+ *
12
+ * Everything below here THROWS {@link OpenApiGenerationError} and prints nothing, so this is the only
13
+ * renderer of a failure and the only writer of an exit code (`.claude/review/error-output.md`). It
14
+ * renders from the error's FIELDS — the message, the location, the cure, the pointers — rather than
15
+ * from a string somebody baked a cure into, so one audience's formatting never freezes into a thrower.
16
+ *
17
+ * ## Why an unmapped field exits NON-ZERO
18
+ *
19
+ * A field with no schema publishes as "anything", so a document containing one is a green build
20
+ * handing a partner a field with no shape. That is worse than no document, because nobody reads a
21
+ * published contract looking for the field that was quietly left undefined. There is deliberately no
22
+ * flag to downgrade it — the cure is at the contract, by naming the type, and every pointer needed to
23
+ * do that is printed.
24
+ */
25
+ class WpOpenApiMain {
26
+ cli = new OpenApiCli_1.OpenApiCli();
27
+ /** @returns the process exit code. 0 on success, 1 on a stated failure. */
28
+ run(argv, cwd, out) {
29
+ if (this.cli.wantsHelp(argv)) {
30
+ out.write(`${OpenApiCli_1.USAGE}\n`);
31
+ return 0;
32
+ }
33
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- this IS the single top-level handler
34
+ try {
35
+ const result = this.cli.run(argv, cwd);
36
+ for (const file of result.written) {
37
+ out.write(`wrote ${file}\n`);
38
+ }
39
+ return 0;
40
+ // webpieces-disable no-any-unknown -- the catch binding, which TypeScript types for us
41
+ }
42
+ catch (err) {
43
+ //const error = toError(err);
44
+ out.write(`${this.render(err)}\n`);
45
+ return 1;
46
+ }
47
+ }
48
+ // webpieces-disable no-any-unknown -- a caught value; this method is the audience-facing narrowing of it
49
+ render(err) {
50
+ if (!(err instanceof OpenApiGenerationError_1.OpenApiGenerationError)) {
51
+ return `wp-openapi failed: ${err instanceof Error ? err.message : String(err)}`;
52
+ }
53
+ const lines = [`wp-openapi refused: ${err.message}`];
54
+ for (const pointer of err.pointers) {
55
+ lines.push(` ${pointer}`);
56
+ }
57
+ lines.push(` ${err.cure}`);
58
+ return lines.join('\n');
59
+ }
60
+ }
61
+ exports.WpOpenApiMain = WpOpenApiMain;
62
+ //# sourceMappingURL=WpOpenApiMain.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const WpOpenApiMain_1 = require("./WpOpenApiMain");
5
+ /**
6
+ * The `wp-openapi` bin. It is four lines because everything it does lives in {@link WpOpenApiMain},
7
+ * which the suite runs exactly as a user does — a bin whose body is only reachable by spawning a
8
+ * process is a bin nothing tests.
9
+ *
10
+ * The bin is declared in `publishConfig.bin`, never at the top level
11
+ * (`.claude/rules/packaging-and-bins.md`): pnpm chmods every `bin` target while linking a package
12
+ * from its SOURCE directory, where `src/` holds only `.ts` until tsc runs, so a top-level `bin` makes
13
+ * every `pnpm install` print an ENOENT warning. `scripts/publish-packages.sh` hoists it into the
14
+ * PUBLISHED manifest, and fails the release if it ever goes missing.
15
+ */
16
+ // webpieces-disable no-process-exit-outside-main -- this IS main; WpOpenApiMain never touches the process
17
+ process.exitCode = new WpOpenApiMain_1.WpOpenApiMain().run(process.argv.slice(2), process.cwd(), process.stdout);
18
+ //# sourceMappingURL=wp-openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-openapi.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/cli/wp-openapi.ts"],"names":[],"mappings":";;;AACA,mDAAgD;AAEhD;;;;;;;;;;GAUG;AACH,0GAA0G;AAC1G,OAAO,CAAC,QAAQ,GAAG,IAAI,6BAAa,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { WpOpenApiMain } from './WpOpenApiMain';\n\n/**\n * The `wp-openapi` bin. It is four lines because everything it does lives in {@link WpOpenApiMain},\n * which the suite runs exactly as a user does — a bin whose body is only reachable by spawning a\n * process is a bin nothing tests.\n *\n * The bin is declared in `publishConfig.bin`, never at the top level\n * (`.claude/rules/packaging-and-bins.md`): pnpm chmods every `bin` target while linking a package\n * from its SOURCE directory, where `src/` holds only `.ts` until tsc runs, so a top-level `bin` makes\n * every `pnpm install` print an ENOENT warning. `scripts/publish-packages.sh` hoists it into the\n * PUBLISHED manifest, and fails the release if it ever goes missing.\n */\n// webpieces-disable no-process-exit-outside-main -- this IS main; WpOpenApiMain never touches the process\nprocess.exitCode = new WpOpenApiMain().run(process.argv.slice(2), process.cwd(), process.stdout);\n"]}
@@ -0,0 +1,44 @@
1
+ import { GeneratedDocument, GeneratedDocuments } from '../generate/GenerationInputs';
2
+ /** One file this generation pass produces: its name, and its exact bytes. */
3
+ export declare class GeneratedArtifact {
4
+ readonly fileName: string;
5
+ readonly text: string;
6
+ constructor(fileName: string, text: string);
7
+ }
8
+ /** Which serializations to write. Orthogonal to WHICH documents `@ApiType` selected. */
9
+ export type OutputFormat = 'json' | 'yaml' | 'both';
10
+ /**
11
+ * The generated documents, serialized.
12
+ *
13
+ * | file | audience |
14
+ * |---|---|
15
+ * | `full-private-openapi.json` | internal. Every contract declaring `SVC_TO_SVC`, hidden methods included. Nothing renders it for humans |
16
+ * | `public-openapi.json` | customers. Contracts declaring `EXTERNAL_CUSTOMER`, minus every `{ hidden: true }` method |
17
+ * | `mcp-openapi.json` | agents. Contracts declaring `MCP`, carrying the `x-mcp-*` extensions |
18
+ *
19
+ * A document is written only when some contract declared its type, which is decided by the generator
20
+ * — this class writes what it is given. `--format` then chooses `.json`, `.yaml` or both from the
21
+ * SAME in-memory document, so the two serializations cannot disagree.
22
+ *
23
+ * ## Why the goldens commit JSON only
24
+ *
25
+ * Committing both would double the review surface every decorator change has to be diffed against,
26
+ * for a second file that is the first one restated. One spec parses each emitted YAML and asserts
27
+ * deep equality with its JSON counterpart instead, which proves the YAML is correct without asking
28
+ * anybody to read it.
29
+ *
30
+ * ## Why the JSON documents ARE committed
31
+ *
32
+ * `diff full-private-openapi.json public-openapi.json` is the complete list of what these contracts
33
+ * do not show a customer. That only works as a review device if both files are in the tree, so hiding
34
+ * a method shows up as a diff in the PR that hides it.
35
+ */
36
+ export declare class ArtifactWriter {
37
+ private readonly json;
38
+ private readonly yaml;
39
+ artifacts(documents: GeneratedDocuments, format: OutputFormat): readonly GeneratedArtifact[];
40
+ /** Write them all, creating `outDir` if it does not exist. Returns the absolute paths written. */
41
+ write(outDir: string, artifacts: readonly GeneratedArtifact[]): readonly string[];
42
+ /** The document objects, for a caller that wants them rather than their bytes. */
43
+ documentsOf(documents: GeneratedDocuments): readonly GeneratedDocument[];
44
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ArtifactWriter = exports.GeneratedArtifact = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("node:fs"));
6
+ const path = tslib_1.__importStar(require("node:path"));
7
+ const JsonWriter_1 = require("../json/JsonWriter");
8
+ const YamlWriter_1 = require("../json/YamlWriter");
9
+ /** One file this generation pass produces: its name, and its exact bytes. */
10
+ class GeneratedArtifact {
11
+ fileName;
12
+ text;
13
+ constructor(fileName, text) {
14
+ this.fileName = fileName;
15
+ this.text = text;
16
+ }
17
+ }
18
+ exports.GeneratedArtifact = GeneratedArtifact;
19
+ /**
20
+ * The generated documents, serialized.
21
+ *
22
+ * | file | audience |
23
+ * |---|---|
24
+ * | `full-private-openapi.json` | internal. Every contract declaring `SVC_TO_SVC`, hidden methods included. Nothing renders it for humans |
25
+ * | `public-openapi.json` | customers. Contracts declaring `EXTERNAL_CUSTOMER`, minus every `{ hidden: true }` method |
26
+ * | `mcp-openapi.json` | agents. Contracts declaring `MCP`, carrying the `x-mcp-*` extensions |
27
+ *
28
+ * A document is written only when some contract declared its type, which is decided by the generator
29
+ * — this class writes what it is given. `--format` then chooses `.json`, `.yaml` or both from the
30
+ * SAME in-memory document, so the two serializations cannot disagree.
31
+ *
32
+ * ## Why the goldens commit JSON only
33
+ *
34
+ * Committing both would double the review surface every decorator change has to be diffed against,
35
+ * for a second file that is the first one restated. One spec parses each emitted YAML and asserts
36
+ * deep equality with its JSON counterpart instead, which proves the YAML is correct without asking
37
+ * anybody to read it.
38
+ *
39
+ * ## Why the JSON documents ARE committed
40
+ *
41
+ * `diff full-private-openapi.json public-openapi.json` is the complete list of what these contracts
42
+ * do not show a customer. That only works as a review device if both files are in the tree, so hiding
43
+ * a method shows up as a diff in the PR that hides it.
44
+ */
45
+ class ArtifactWriter {
46
+ json = new JsonWriter_1.JsonWriter();
47
+ yaml = new YamlWriter_1.YamlWriter();
48
+ artifacts(documents, format) {
49
+ const artifacts = [];
50
+ for (const generated of documents.documents) {
51
+ if (format !== 'yaml') {
52
+ artifacts.push(new GeneratedArtifact(`${generated.fileName}.json`, this.json.write(generated.document)));
53
+ }
54
+ if (format !== 'json') {
55
+ artifacts.push(new GeneratedArtifact(`${generated.fileName}.yaml`, this.yaml.write(generated.document)));
56
+ }
57
+ }
58
+ return artifacts;
59
+ }
60
+ /** Write them all, creating `outDir` if it does not exist. Returns the absolute paths written. */
61
+ write(outDir, artifacts) {
62
+ fs.mkdirSync(outDir, { recursive: true });
63
+ return artifacts.map((artifact) => {
64
+ const file = path.join(outDir, artifact.fileName);
65
+ fs.writeFileSync(file, artifact.text, 'utf8');
66
+ return file;
67
+ });
68
+ }
69
+ /** The document objects, for a caller that wants them rather than their bytes. */
70
+ documentsOf(documents) {
71
+ return documents.documents;
72
+ }
73
+ }
74
+ exports.ArtifactWriter = ArtifactWriter;
75
+ //# sourceMappingURL=ArtifactWriter.js.map