@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.
- package/README.md +118 -0
- package/package.json +32 -0
- package/src/OpenApiGenerationError.d.ts +41 -0
- package/src/OpenApiGenerationError.js +45 -0
- package/src/OpenApiGenerationError.js.map +1 -0
- package/src/cli/OpenApiCli.d.ts +33 -0
- package/src/cli/OpenApiCli.js +100 -0
- package/src/cli/OpenApiCli.js.map +1 -0
- package/src/cli/WpOpenApiMain.d.ts +25 -0
- package/src/cli/WpOpenApiMain.js +62 -0
- package/src/cli/WpOpenApiMain.js.map +1 -0
- package/src/cli/wp-openapi.d.ts +2 -0
- package/src/cli/wp-openapi.js +18 -0
- package/src/cli/wp-openapi.js.map +1 -0
- package/src/emit/ArtifactWriter.d.ts +44 -0
- package/src/emit/ArtifactWriter.js +75 -0
- package/src/emit/ArtifactWriter.js.map +1 -0
- package/src/generate/DocumentSelection.d.ts +96 -0
- package/src/generate/DocumentSelection.js +153 -0
- package/src/generate/DocumentSelection.js.map +1 -0
- package/src/generate/GenerationInputs.d.ts +67 -0
- package/src/generate/GenerationInputs.js +88 -0
- package/src/generate/GenerationInputs.js.map +1 -0
- package/src/generate/OpenApiGenerator.d.ts +113 -0
- package/src/generate/OpenApiGenerator.js +306 -0
- package/src/generate/OpenApiGenerator.js.map +1 -0
- package/src/generate/OperationRenderer.d.ts +129 -0
- package/src/generate/OperationRenderer.js +256 -0
- package/src/generate/OperationRenderer.js.map +1 -0
- package/src/generate/SchemaRenderer.d.ts +89 -0
- package/src/generate/SchemaRenderer.js +236 -0
- package/src/generate/SchemaRenderer.js.map +1 -0
- package/src/generate/SecurityDeriver.d.ts +39 -0
- package/src/generate/SecurityDeriver.js +81 -0
- package/src/generate/SecurityDeriver.js.map +1 -0
- package/src/index.d.ts +32 -0
- package/src/index.js +70 -0
- package/src/index.js.map +1 -0
- package/src/json/JsonObject.d.ts +35 -0
- package/src/json/JsonObject.js +32 -0
- package/src/json/JsonObject.js.map +1 -0
- package/src/json/JsonWriter.d.ts +18 -0
- package/src/json/JsonWriter.js +48 -0
- package/src/json/JsonWriter.js.map +1 -0
- package/src/json/YamlReader.d.ts +35 -0
- package/src/json/YamlReader.js +111 -0
- package/src/json/YamlReader.js.map +1 -0
- package/src/json/YamlWriter.d.ts +35 -0
- package/src/json/YamlWriter.js +88 -0
- package/src/json/YamlWriter.js.map +1 -0
- package/src/load/ExportedConstantFolder.d.ts +21 -0
- package/src/load/ExportedConstantFolder.js +57 -0
- package/src/load/ExportedConstantFolder.js.map +1 -0
- package/src/load/ForeignFailure.d.ts +24 -0
- package/src/load/ForeignFailure.js +41 -0
- package/src/load/ForeignFailure.js.map +1 -0
- package/src/load/InputsLoader.d.ts +43 -0
- package/src/load/InputsLoader.js +148 -0
- package/src/load/InputsLoader.js.map +1 -0
- package/src/manifest/JsonReader.d.ts +37 -0
- package/src/manifest/JsonReader.js +109 -0
- package/src/manifest/JsonReader.js.map +1 -0
- package/src/manifest/ManifestLoader.d.ts +20 -0
- package/src/manifest/ManifestLoader.js +69 -0
- package/src/manifest/ManifestLoader.js.map +1 -0
- package/src/manifest/OpenApiManifest.d.ts +116 -0
- package/src/manifest/OpenApiManifest.js +142 -0
- package/src/manifest/OpenApiManifest.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"YamlReader.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/json/YamlReader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A reader for the block-YAML subset this package emits.\n *\n * It exists for ONE job: proving that an emitted YAML document is the same document as its JSON\n * counterpart. It is exported because the consumer that needs it is a golden spec in a DIFFERENT\n * project (`apps/app-example/partner-api`), and a reader that only the emitter's own package could\n * reach would leave every downstream golden with no way to check its YAML at all.\n *\n * ## Why it exists rather than a YAML dependency\n *\n * The goldens commit JSON only: committing both serializations would double the review surface every\n * decorator change has to be diffed against, for a second file that is the first one restated. What\n * proves the YAML instead is one spec that PARSES it and asserts deep equality with its JSON\n * counterpart — and doing that with a real YAML library would put a dependency into a package whose\n * whole story is `typescript` plus two webpieces packages.\n *\n * ## Why a mirror bug is unlikely\n *\n * It is written in the OPPOSITE direction from {@link YamlWriter}: indentation and quoting are\n * re-derived here from the text rather than shared with the emitter, so the two do not fail together\n * for the same reason. It understands exactly what the writer produces — double-quoted keys and\n * string scalars, bare numbers/booleans/null, `{}` and `[]` for empties, `- ` list items — and\n * anything else is a parse failure rather than a guess. It is NOT a general YAML parser and must not\n * be used as one.\n */\n// webpieces-disable no-any-unknown -- it parses arbitrary JSON-shaped YAML; `unknown` IS the honest return type, and the caller compares it against a JSON.parse result\nexport class YamlReader {\n private lines: string[] = [];\n private index = 0;\n\n // webpieces-disable no-any-unknown -- see the class doc: a JSON-shaped value read from text\n read(text: string): unknown {\n this.lines = text.split('\\n').filter((line: string) => line.trim() !== '');\n this.index = 0;\n return this.lines.length === 0 ? {} : this.value(0);\n }\n\n /** The value whose first line is at `indent`, consuming every line that belongs to it. */\n // webpieces-disable no-any-unknown -- see the class doc: a JSON-shaped value read from text\n private value(indent: number): unknown {\n const line = this.lines[this.index];\n if (line === undefined) {\n return {};\n }\n return line.trimStart().startsWith('-') ? this.list(indent) : this.map(indent);\n }\n\n // webpieces-disable no-any-unknown -- see the class doc: a JSON-shaped value read from text\n private map(indent: number): Record<string, unknown> {\n // webpieces-disable no-any-unknown -- see the class doc: a JSON-shaped value read from text\n const out: Record<string, unknown> = {};\n while (this.index < this.lines.length) {\n const line = this.lines[this.index]!;\n const depth = line.length - line.trimStart().length;\n if (depth < indent) {\n break;\n }\n const text = line.trim();\n const split = text.indexOf('\": ');\n if (split === -1) {\n // `\"key\":` with the value nested under it.\n const key = JSON.parse(text.slice(0, text.length - 1)) as string;\n this.index += 1;\n out[key] = this.value(depth + 4);\n continue;\n }\n out[JSON.parse(text.slice(0, split + 1)) as string] = this.scalar(\n text.slice(split + 3),\n );\n this.index += 1;\n }\n return out;\n }\n\n // webpieces-disable no-any-unknown -- see the class doc: a JSON-shaped value read from text\n private list(indent: number): unknown[] {\n // webpieces-disable no-any-unknown -- see the class doc: a JSON-shaped value read from text\n const out: unknown[] = [];\n while (this.index < this.lines.length) {\n const line = this.lines[this.index]!;\n const depth = line.length - line.trimStart().length;\n const text = line.trim();\n if (depth < indent || !text.startsWith('-')) {\n break;\n }\n const rest = text.slice(1).trim();\n if (rest.startsWith('\"') && rest.includes('\": ')) {\n // An object whose FIRST key shares the `- ` line; the rest align under it.\n this.lines[this.index] = ' '.repeat(depth + 2) + rest;\n out.push(this.map(depth + 2));\n continue;\n }\n this.index += 1;\n out.push(this.scalar(rest));\n }\n return out;\n }\n\n // webpieces-disable no-any-unknown -- see the class doc: a JSON-shaped value read from text\n private scalar(text: string): unknown {\n if (text === '{}') {\n return {};\n }\n if (text === '[]') {\n return [];\n }\n if (text === 'null') {\n return null;\n }\n // webpieces-disable no-any-unknown -- see the class doc: a JSON-shaped value read from text\n return JSON.parse(text) as unknown;\n }\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { JsonObject } from './JsonObject';
|
|
2
|
+
/**
|
|
3
|
+
* The SAME in-memory {@link JsonObject} -> block YAML. One generation pass, two serializations, so
|
|
4
|
+
* the JSON and the YAML document cannot disagree about anything.
|
|
5
|
+
*
|
|
6
|
+
* ## Why this package emits YAML itself rather than depending on a YAML library
|
|
7
|
+
*
|
|
8
|
+
* The value space here is exactly JSON's — string, number, boolean, null, array, object — because
|
|
9
|
+
* that is all an OpenAPI document holds and all a {@link JsonObject} can carry. There are no dates,
|
|
10
|
+
* no anchors, no tags, no multi-document streams and no cyclic references, which is the entire
|
|
11
|
+
* reason a general YAML serializer is a large dependency. Emitting the subset directly keeps the
|
|
12
|
+
* package's dependencies at `typescript` and `@webpieces/api-doc-model`, which is what lets it be
|
|
13
|
+
* pointed at any upstream project without dragging a tree behind it.
|
|
14
|
+
*
|
|
15
|
+
* ## Every string is DOUBLE-QUOTED, deliberately
|
|
16
|
+
*
|
|
17
|
+
* YAML's plain (unquoted) scalars are where its sharp edges live: `no` is a boolean, `1.0` is a
|
|
18
|
+
* number, `*ref` is an alias, a leading `%` or `@` is reserved, and a colon-space inside the text
|
|
19
|
+
* ends the key. A generator that quoted "only when necessary" would need to model every one of those
|
|
20
|
+
* rules correctly, forever, and would fail silently — a partner reading `version: 1.0` as a float is
|
|
21
|
+
* not a crash, it is a wrong document. Double-quoted YAML uses JSON's own escape grammar, so
|
|
22
|
+
* `JSON.stringify` of a string is a valid YAML scalar by construction. The output is a little
|
|
23
|
+
* noisier and cannot be wrong.
|
|
24
|
+
*/
|
|
25
|
+
export declare class YamlWriter {
|
|
26
|
+
write(root: JsonObject): string;
|
|
27
|
+
/** The lines of one object, each already prefixed with `indent`. */
|
|
28
|
+
private objectLines;
|
|
29
|
+
/** One `key:` (or one `-`) followed by its value, inline when scalar and nested when not. */
|
|
30
|
+
private appendEntry;
|
|
31
|
+
private arrayLines;
|
|
32
|
+
/** A key is a string, so it gets the same unconditional quoting the class doc argues for. */
|
|
33
|
+
private key;
|
|
34
|
+
private scalar;
|
|
35
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.YamlWriter = void 0;
|
|
4
|
+
const JsonObject_1 = require("./JsonObject");
|
|
5
|
+
/**
|
|
6
|
+
* The SAME in-memory {@link JsonObject} -> block YAML. One generation pass, two serializations, so
|
|
7
|
+
* the JSON and the YAML document cannot disagree about anything.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this package emits YAML itself rather than depending on a YAML library
|
|
10
|
+
*
|
|
11
|
+
* The value space here is exactly JSON's — string, number, boolean, null, array, object — because
|
|
12
|
+
* that is all an OpenAPI document holds and all a {@link JsonObject} can carry. There are no dates,
|
|
13
|
+
* no anchors, no tags, no multi-document streams and no cyclic references, which is the entire
|
|
14
|
+
* reason a general YAML serializer is a large dependency. Emitting the subset directly keeps the
|
|
15
|
+
* package's dependencies at `typescript` and `@webpieces/api-doc-model`, which is what lets it be
|
|
16
|
+
* pointed at any upstream project without dragging a tree behind it.
|
|
17
|
+
*
|
|
18
|
+
* ## Every string is DOUBLE-QUOTED, deliberately
|
|
19
|
+
*
|
|
20
|
+
* YAML's plain (unquoted) scalars are where its sharp edges live: `no` is a boolean, `1.0` is a
|
|
21
|
+
* number, `*ref` is an alias, a leading `%` or `@` is reserved, and a colon-space inside the text
|
|
22
|
+
* ends the key. A generator that quoted "only when necessary" would need to model every one of those
|
|
23
|
+
* rules correctly, forever, and would fail silently — a partner reading `version: 1.0` as a float is
|
|
24
|
+
* not a crash, it is a wrong document. Double-quoted YAML uses JSON's own escape grammar, so
|
|
25
|
+
* `JSON.stringify` of a string is a valid YAML scalar by construction. The output is a little
|
|
26
|
+
* noisier and cannot be wrong.
|
|
27
|
+
*/
|
|
28
|
+
class YamlWriter {
|
|
29
|
+
write(root) {
|
|
30
|
+
if (root.isEmpty()) {
|
|
31
|
+
return '{}\n';
|
|
32
|
+
}
|
|
33
|
+
return this.objectLines(root, '').join('\n') + '\n';
|
|
34
|
+
}
|
|
35
|
+
/** The lines of one object, each already prefixed with `indent`. */
|
|
36
|
+
objectLines(object, indent) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
for (const key of object.keys()) {
|
|
39
|
+
this.appendEntry(lines, `${indent}${this.key(key)}:`, object.get(key), indent);
|
|
40
|
+
}
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
43
|
+
/** One `key:` (or one `-`) followed by its value, inline when scalar and nested when not. */
|
|
44
|
+
appendEntry(lines, prefix, value, indent) {
|
|
45
|
+
if (value instanceof JsonObject_1.JsonObject) {
|
|
46
|
+
if (value.isEmpty()) {
|
|
47
|
+
lines.push(`${prefix} {}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
lines.push(prefix);
|
|
51
|
+
lines.push(...this.objectLines(value, `${indent} `));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
if (value.length === 0) {
|
|
56
|
+
lines.push(`${prefix} []`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
lines.push(prefix);
|
|
60
|
+
lines.push(...this.arrayLines(value, `${indent} `));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
lines.push(`${prefix} ${this.scalar(value)}`);
|
|
64
|
+
}
|
|
65
|
+
arrayLines(items, indent) {
|
|
66
|
+
const lines = [];
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
if (item instanceof JsonObject_1.JsonObject && !item.isEmpty()) {
|
|
69
|
+
// A `- ` opens the item and its FIRST key shares that line; the rest align under it.
|
|
70
|
+
const nested = this.objectLines(item, `${indent} `);
|
|
71
|
+
lines.push(`${indent}- ${nested[0].trimStart()}`);
|
|
72
|
+
lines.push(...nested.slice(1));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
this.appendEntry(lines, `${indent}-`, item, indent);
|
|
76
|
+
}
|
|
77
|
+
return lines;
|
|
78
|
+
}
|
|
79
|
+
/** A key is a string, so it gets the same unconditional quoting the class doc argues for. */
|
|
80
|
+
key(key) {
|
|
81
|
+
return JSON.stringify(key);
|
|
82
|
+
}
|
|
83
|
+
scalar(value) {
|
|
84
|
+
return value === null ? 'null' : JSON.stringify(value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.YamlWriter = YamlWriter;
|
|
88
|
+
//# sourceMappingURL=YamlWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"YamlWriter.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/json/YamlWriter.ts"],"names":[],"mappings":";;;AAAA,6CAAqD;AAErD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,UAAU;IACnB,KAAK,CAAC,IAAgB;QAClB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACjB,OAAO,MAAM,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxD,CAAC;IAED,oEAAoE;IAC5D,WAAW,CAAC,MAAkB,EAAE,MAAc;QAClD,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAE,EAAE,MAAM,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,6FAA6F;IACrF,WAAW,CAAC,KAAe,EAAE,MAAc,EAAE,KAAgB,EAAE,MAAc;QACjF,IAAI,KAAK,YAAY,uBAAU,EAAE,CAAC;YAC9B,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;gBAClB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC;gBAC3B,OAAO;YACX,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC;YACxD,OAAO;QACX,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC;gBAC3B,OAAO;YACX,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC;YACvD,OAAO;QACX,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC;IAEO,UAAU,CAAC,KAA2B,EAAE,MAAc;QAC1D,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,IAAI,YAAY,uBAAU,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;gBAChD,qFAAqF;gBACrF,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,CAAC;gBACrD,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAE,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;gBACnD,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/B,SAAS;YACb,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,6FAA6F;IACrF,GAAG,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAEO,MAAM,CAAC,KAAgB;QAC3B,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC;CACJ;AA/DD,gCA+DC","sourcesContent":["import { JsonObject, JsonValue } from './JsonObject';\n\n/**\n * The SAME in-memory {@link JsonObject} -> block YAML. One generation pass, two serializations, so\n * the JSON and the YAML document cannot disagree about anything.\n *\n * ## Why this package emits YAML itself rather than depending on a YAML library\n *\n * The value space here is exactly JSON's — string, number, boolean, null, array, object — because\n * that is all an OpenAPI document holds and all a {@link JsonObject} can carry. There are no dates,\n * no anchors, no tags, no multi-document streams and no cyclic references, which is the entire\n * reason a general YAML serializer is a large dependency. Emitting the subset directly keeps the\n * package's dependencies at `typescript` and `@webpieces/api-doc-model`, which is what lets it be\n * pointed at any upstream project without dragging a tree behind it.\n *\n * ## Every string is DOUBLE-QUOTED, deliberately\n *\n * YAML's plain (unquoted) scalars are where its sharp edges live: `no` is a boolean, `1.0` is a\n * number, `*ref` is an alias, a leading `%` or `@` is reserved, and a colon-space inside the text\n * ends the key. A generator that quoted \"only when necessary\" would need to model every one of those\n * rules correctly, forever, and would fail silently — a partner reading `version: 1.0` as a float is\n * not a crash, it is a wrong document. Double-quoted YAML uses JSON's own escape grammar, so\n * `JSON.stringify` of a string is a valid YAML scalar by construction. The output is a little\n * noisier and cannot be wrong.\n */\nexport class YamlWriter {\n write(root: JsonObject): string {\n if (root.isEmpty()) {\n return '{}\\n';\n }\n return this.objectLines(root, '').join('\\n') + '\\n';\n }\n\n /** The lines of one object, each already prefixed with `indent`. */\n private objectLines(object: JsonObject, indent: string): string[] {\n const lines: string[] = [];\n for (const key of object.keys()) {\n this.appendEntry(lines, `${indent}${this.key(key)}:`, object.get(key)!, indent);\n }\n return lines;\n }\n\n /** One `key:` (or one `-`) followed by its value, inline when scalar and nested when not. */\n private appendEntry(lines: string[], prefix: string, value: JsonValue, indent: string): void {\n if (value instanceof JsonObject) {\n if (value.isEmpty()) {\n lines.push(`${prefix} {}`);\n return;\n }\n lines.push(prefix);\n lines.push(...this.objectLines(value, `${indent} `));\n return;\n }\n if (Array.isArray(value)) {\n if (value.length === 0) {\n lines.push(`${prefix} []`);\n return;\n }\n lines.push(prefix);\n lines.push(...this.arrayLines(value, `${indent} `));\n return;\n }\n lines.push(`${prefix} ${this.scalar(value)}`);\n }\n\n private arrayLines(items: readonly JsonValue[], indent: string): string[] {\n const lines: string[] = [];\n for (const item of items) {\n if (item instanceof JsonObject && !item.isEmpty()) {\n // A `- ` opens the item and its FIRST key shares that line; the rest align under it.\n const nested = this.objectLines(item, `${indent} `);\n lines.push(`${indent}- ${nested[0]!.trimStart()}`);\n lines.push(...nested.slice(1));\n continue;\n }\n this.appendEntry(lines, `${indent}-`, item, indent);\n }\n return lines;\n }\n\n /** A key is a string, so it gets the same unconditional quoting the class doc argues for. */\n private key(key: string): string {\n return JSON.stringify(key);\n }\n\n private scalar(value: JsonValue): string {\n return value === null ? 'null' : JSON.stringify(value);\n }\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fold an exported `const` out of a `.ts` file — the header NAME behind a manifest's `nameConstant`.
|
|
3
|
+
*
|
|
4
|
+
* ## Why the manifest names a CONSTANT and not the header
|
|
5
|
+
*
|
|
6
|
+
* JSON cannot import. A header name written as a literal in `openapi.manifest.json` is a copy of the
|
|
7
|
+
* one the server writes, and a rename leaves the copy silently stale — the published document then
|
|
8
|
+
* documents a header nothing sets, which no test can catch because both files are internally
|
|
9
|
+
* consistent. Naming the constant makes the manifest point at the same declaration the server reads.
|
|
10
|
+
*
|
|
11
|
+
* ## Why it HARD-FAILS instead of falling back to the constant's text
|
|
12
|
+
*
|
|
13
|
+
* The fallback would publish `REQUEST_ID_HEADER` as a header name. That is a document that is wrong
|
|
14
|
+
* in a way a reader cannot detect, which is the one failure mode a generated document exists to
|
|
15
|
+
* remove. It is the same call `ConstantFolder` makes about an `@Endpoint` path, for the same reason.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ExportedConstantFolder {
|
|
18
|
+
/** @param file absolute path to the `.ts` file declaring the constant. */
|
|
19
|
+
fold(file: string, constantName: string): string;
|
|
20
|
+
private find;
|
|
21
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExportedConstantFolder = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("node:fs"));
|
|
6
|
+
const ts = tslib_1.__importStar(require("typescript"));
|
|
7
|
+
const OpenApiGenerationError_1 = require("../OpenApiGenerationError");
|
|
8
|
+
/**
|
|
9
|
+
* Fold an exported `const` out of a `.ts` file — the header NAME behind a manifest's `nameConstant`.
|
|
10
|
+
*
|
|
11
|
+
* ## Why the manifest names a CONSTANT and not the header
|
|
12
|
+
*
|
|
13
|
+
* JSON cannot import. A header name written as a literal in `openapi.manifest.json` is a copy of the
|
|
14
|
+
* one the server writes, and a rename leaves the copy silently stale — the published document then
|
|
15
|
+
* documents a header nothing sets, which no test can catch because both files are internally
|
|
16
|
+
* consistent. Naming the constant makes the manifest point at the same declaration the server reads.
|
|
17
|
+
*
|
|
18
|
+
* ## Why it HARD-FAILS instead of falling back to the constant's text
|
|
19
|
+
*
|
|
20
|
+
* The fallback would publish `REQUEST_ID_HEADER` as a header name. That is a document that is wrong
|
|
21
|
+
* in a way a reader cannot detect, which is the one failure mode a generated document exists to
|
|
22
|
+
* remove. It is the same call `ConstantFolder` makes about an `@Endpoint` path, for the same reason.
|
|
23
|
+
*/
|
|
24
|
+
class ExportedConstantFolder {
|
|
25
|
+
/** @param file absolute path to the `.ts` file declaring the constant. */
|
|
26
|
+
fold(file, constantName) {
|
|
27
|
+
if (!fs.existsSync(file)) {
|
|
28
|
+
throw new OpenApiGenerationError_1.OpenApiGenerationError(`no file declaring '${constantName}'`, file, 'Point `entry` at the .ts file that exports the constant.');
|
|
29
|
+
}
|
|
30
|
+
const source = ts.createSourceFile(file, fs.readFileSync(file, 'utf8'), ts.ScriptTarget.ES2022,
|
|
31
|
+
/*setParentNodes*/ true);
|
|
32
|
+
const value = this.find(source, constantName);
|
|
33
|
+
if (value === undefined) {
|
|
34
|
+
throw new OpenApiGenerationError_1.OpenApiGenerationError(`'${constantName}' is not an exported string constant in this file`, file, `Declare it as \`export const ${constantName} = '<header-name>';\` — a value only ` +
|
|
35
|
+
'known at runtime cannot appear in a published document.');
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
find(source, constantName) {
|
|
40
|
+
for (const statement of source.statements) {
|
|
41
|
+
if (!ts.isVariableStatement(statement)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
45
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
46
|
+
declaration.name.text === constantName &&
|
|
47
|
+
declaration.initializer !== undefined &&
|
|
48
|
+
ts.isStringLiteralLike(declaration.initializer)) {
|
|
49
|
+
return declaration.initializer.text;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ExportedConstantFolder = ExportedConstantFolder;
|
|
57
|
+
//# sourceMappingURL=ExportedConstantFolder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExportedConstantFolder.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/load/ExportedConstantFolder.ts"],"names":[],"mappings":";;;;AAAA,oDAA8B;AAC9B,uDAAiC;AACjC,sEAAmE;AAEnE;;;;;;;;;;;;;;;GAeG;AACH,MAAa,sBAAsB;IAC/B,0EAA0E;IAC1E,IAAI,CAAC,IAAY,EAAE,YAAoB;QACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,+CAAsB,CAC5B,sBAAsB,YAAY,GAAG,EACrC,IAAI,EACJ,0DAA0D,CAC7D,CAAC;QACN,CAAC;QACD,MAAM,MAAM,GAAG,EAAE,CAAC,gBAAgB,CAC9B,IAAI,EACJ,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAC7B,EAAE,CAAC,YAAY,CAAC,MAAM;QACtB,kBAAkB,CAAC,IAAI,CAC1B,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAC9C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,+CAAsB,CAC5B,IAAI,YAAY,mDAAmD,EACnE,IAAI,EACJ,gCAAgC,YAAY,uCAAuC;gBAC/E,yDAAyD,CAChE,CAAC;QACN,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,IAAI,CAAC,MAAqB,EAAE,YAAoB;QACpD,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACxC,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;gBACrC,SAAS;YACb,CAAC;YACD,KAAK,MAAM,WAAW,IAAI,SAAS,CAAC,eAAe,CAAC,YAAY,EAAE,CAAC;gBAC/D,IACI,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC;oBACjC,WAAW,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY;oBACtC,WAAW,CAAC,WAAW,KAAK,SAAS;oBACrC,EAAE,CAAC,mBAAmB,CAAC,WAAW,CAAC,WAAW,CAAC,EACjD,CAAC;oBACC,OAAO,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC;gBACxC,CAAC;YACL,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;CACJ;AA9CD,wDA8CC","sourcesContent":["import * as fs from 'node:fs';\nimport * as ts from 'typescript';\nimport { OpenApiGenerationError } from '../OpenApiGenerationError';\n\n/**\n * Fold an exported `const` out of a `.ts` file — the header NAME behind a manifest's `nameConstant`.\n *\n * ## Why the manifest names a CONSTANT and not the header\n *\n * JSON cannot import. A header name written as a literal in `openapi.manifest.json` is a copy of the\n * one the server writes, and a rename leaves the copy silently stale — the published document then\n * documents a header nothing sets, which no test can catch because both files are internally\n * consistent. Naming the constant makes the manifest point at the same declaration the server reads.\n *\n * ## Why it HARD-FAILS instead of falling back to the constant's text\n *\n * The fallback would publish `REQUEST_ID_HEADER` as a header name. That is a document that is wrong\n * in a way a reader cannot detect, which is the one failure mode a generated document exists to\n * remove. It is the same call `ConstantFolder` makes about an `@Endpoint` path, for the same reason.\n */\nexport class ExportedConstantFolder {\n /** @param file absolute path to the `.ts` file declaring the constant. */\n fold(file: string, constantName: string): string {\n if (!fs.existsSync(file)) {\n throw new OpenApiGenerationError(\n `no file declaring '${constantName}'`,\n file,\n 'Point `entry` at the .ts file that exports the constant.',\n );\n }\n const source = ts.createSourceFile(\n file,\n fs.readFileSync(file, 'utf8'),\n ts.ScriptTarget.ES2022,\n /*setParentNodes*/ true,\n );\n const value = this.find(source, constantName);\n if (value === undefined) {\n throw new OpenApiGenerationError(\n `'${constantName}' is not an exported string constant in this file`,\n file,\n `Declare it as \\`export const ${constantName} = '<header-name>';\\` — a value only ` +\n 'known at runtime cannot appear in a published document.',\n );\n }\n return value;\n }\n\n private find(source: ts.SourceFile, constantName: string): string | undefined {\n for (const statement of source.statements) {\n if (!ts.isVariableStatement(statement)) {\n continue;\n }\n for (const declaration of statement.declarationList.declarations) {\n if (\n ts.isIdentifier(declaration.name) &&\n declaration.name.text === constantName &&\n declaration.initializer !== undefined &&\n ts.isStringLiteralLike(declaration.initializer)\n ) {\n return declaration.initializer.text;\n }\n }\n }\n return undefined;\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { OpenApiGenerationError } from '../OpenApiGenerationError';
|
|
2
|
+
/**
|
|
3
|
+
* A failure thrown by `@webpieces/api-doc-model`, carried across into THIS package's one error type.
|
|
4
|
+
*
|
|
5
|
+
* ## Why it is translated rather than allowed through
|
|
6
|
+
*
|
|
7
|
+
* `wp-openapi`'s top-level handler renders exactly one error type. Letting `ApiDocExtractionError`
|
|
8
|
+
* out as well would give it two to know about, only one of which is on this package's surface — and
|
|
9
|
+
* the second would be rendered by whichever branch somebody remembered to write.
|
|
10
|
+
*
|
|
11
|
+
* The extractor's own `location` and `cure` are the good part of its failure: they name the line of
|
|
12
|
+
* the contract and what to write there. Translating means keeping both, so the handler prints the
|
|
13
|
+
* extractor's cure verbatim instead of a vaguer one of this package's own.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ForeignFailure {
|
|
16
|
+
readonly message: string;
|
|
17
|
+
readonly location: string | undefined;
|
|
18
|
+
readonly cure: string | undefined;
|
|
19
|
+
private constructor();
|
|
20
|
+
/** Read whatever the thrown value actually carries, without assuming it carries anything. */
|
|
21
|
+
static of(err: unknown): ForeignFailure;
|
|
22
|
+
/** The same failure as this package's one error type, falling back where the thrower said less. */
|
|
23
|
+
asGenerationError(fallbackLocation: string, fallbackCure: string): OpenApiGenerationError;
|
|
24
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ForeignFailure = void 0;
|
|
4
|
+
const OpenApiGenerationError_1 = require("../OpenApiGenerationError");
|
|
5
|
+
/**
|
|
6
|
+
* A failure thrown by `@webpieces/api-doc-model`, carried across into THIS package's one error type.
|
|
7
|
+
*
|
|
8
|
+
* ## Why it is translated rather than allowed through
|
|
9
|
+
*
|
|
10
|
+
* `wp-openapi`'s top-level handler renders exactly one error type. Letting `ApiDocExtractionError`
|
|
11
|
+
* out as well would give it two to know about, only one of which is on this package's surface — and
|
|
12
|
+
* the second would be rendered by whichever branch somebody remembered to write.
|
|
13
|
+
*
|
|
14
|
+
* The extractor's own `location` and `cure` are the good part of its failure: they name the line of
|
|
15
|
+
* the contract and what to write there. Translating means keeping both, so the handler prints the
|
|
16
|
+
* extractor's cure verbatim instead of a vaguer one of this package's own.
|
|
17
|
+
*/
|
|
18
|
+
class ForeignFailure {
|
|
19
|
+
message;
|
|
20
|
+
location;
|
|
21
|
+
cure;
|
|
22
|
+
constructor(message, location, cure) {
|
|
23
|
+
this.message = message;
|
|
24
|
+
this.location = location;
|
|
25
|
+
this.cure = cure;
|
|
26
|
+
}
|
|
27
|
+
/** Read whatever the thrown value actually carries, without assuming it carries anything. */
|
|
28
|
+
// webpieces-disable no-any-unknown -- a caught value is genuinely of unknown type; narrowing it is this method's whole job
|
|
29
|
+
// webpieces-disable no-function-outside-class -- static factory; the private constructor is what stops a half-read failure being built
|
|
30
|
+
static of(err) {
|
|
31
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32
|
+
const carried = err instanceof Error ? err : undefined;
|
|
33
|
+
return new ForeignFailure(message, typeof carried?.location === 'string' ? carried.location : undefined, typeof carried?.cure === 'string' ? carried.cure : undefined);
|
|
34
|
+
}
|
|
35
|
+
/** The same failure as this package's one error type, falling back where the thrower said less. */
|
|
36
|
+
asGenerationError(fallbackLocation, fallbackCure) {
|
|
37
|
+
return new OpenApiGenerationError_1.OpenApiGenerationError(this.message, this.location ?? fallbackLocation, this.cure ?? fallbackCure);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.ForeignFailure = ForeignFailure;
|
|
41
|
+
//# sourceMappingURL=ForeignFailure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ForeignFailure.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/load/ForeignFailure.ts"],"names":[],"mappings":";;;AAAA,sEAAmE;AAEnE;;;;;;;;;;;;GAYG;AACH,MAAa,cAAc;IAEV;IACA;IACA;IAHb,YACa,OAAe,EACf,QAA4B,EAC5B,IAAwB;QAFxB,YAAO,GAAP,OAAO,CAAQ;QACf,aAAQ,GAAR,QAAQ,CAAoB;QAC5B,SAAI,GAAJ,IAAI,CAAoB;IAClC,CAAC;IAEJ,6FAA6F;IAC7F,2HAA2H;IAC3H,uIAAuI;IACvI,MAAM,CAAC,EAAE,CAAC,GAAY;QAClB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAE,GAA+B,CAAC,CAAC,CAAC,SAAS,CAAC;QACpF,OAAO,IAAI,cAAc,CACrB,OAAO,EACP,OAAO,OAAO,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EACpE,OAAO,OAAO,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAC/D,CAAC;IACN,CAAC;IAED,mGAAmG;IACnG,iBAAiB,CAAC,gBAAwB,EAAE,YAAoB;QAC5D,OAAO,IAAI,+CAAsB,CAC7B,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,QAAQ,IAAI,gBAAgB,EACjC,IAAI,CAAC,IAAI,IAAI,YAAY,CAC5B,CAAC;IACN,CAAC;CACJ;AA5BD,wCA4BC","sourcesContent":["import { OpenApiGenerationError } from '../OpenApiGenerationError';\n\n/**\n * A failure thrown by `@webpieces/api-doc-model`, carried across into THIS package's one error type.\n *\n * ## Why it is translated rather than allowed through\n *\n * `wp-openapi`'s top-level handler renders exactly one error type. Letting `ApiDocExtractionError`\n * out as well would give it two to know about, only one of which is on this package's surface — and\n * the second would be rendered by whichever branch somebody remembered to write.\n *\n * The extractor's own `location` and `cure` are the good part of its failure: they name the line of\n * the contract and what to write there. Translating means keeping both, so the handler prints the\n * extractor's cure verbatim instead of a vaguer one of this package's own.\n */\nexport class ForeignFailure {\n private constructor(\n readonly message: string,\n readonly location: string | undefined,\n readonly cure: string | undefined,\n ) {}\n\n /** Read whatever the thrown value actually carries, without assuming it carries anything. */\n // webpieces-disable no-any-unknown -- a caught value is genuinely of unknown type; narrowing it is this method's whole job\n // webpieces-disable no-function-outside-class -- static factory; the private constructor is what stops a half-read failure being built\n static of(err: unknown): ForeignFailure {\n const message = err instanceof Error ? err.message : String(err);\n const carried = err instanceof Error ? (err as Partial<ForeignFailure>) : undefined;\n return new ForeignFailure(\n message,\n typeof carried?.location === 'string' ? carried.location : undefined,\n typeof carried?.cure === 'string' ? carried.cure : undefined,\n );\n }\n\n /** The same failure as this package's one error type, falling back where the thrower said less. */\n asGenerationError(fallbackLocation: string, fallbackCure: string): OpenApiGenerationError {\n return new OpenApiGenerationError(\n this.message,\n this.location ?? fallbackLocation,\n this.cure ?? fallbackCure,\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { GenerationInputs } from '../generate/GenerationInputs';
|
|
2
|
+
/**
|
|
3
|
+
* Read everything from disk, ONCE, into {@link GenerationInputs}.
|
|
4
|
+
*
|
|
5
|
+
* Every path in the manifest is resolved against the MANIFEST's own directory, never against the
|
|
6
|
+
* process's cwd. A document whose contents depended on where `wp-openapi` was invoked from would be
|
|
7
|
+
* a different document per caller, and the committed golden would go red depending on which
|
|
8
|
+
* directory a build happened to run in.
|
|
9
|
+
*/
|
|
10
|
+
export declare class InputsLoader {
|
|
11
|
+
private readonly manifests;
|
|
12
|
+
private readonly constants;
|
|
13
|
+
private readonly extractor;
|
|
14
|
+
private options;
|
|
15
|
+
load(manifestPath: string): GenerationInputs;
|
|
16
|
+
/**
|
|
17
|
+
* The nearest `tsconfig.json` at or above the MANIFEST, with the extraction flags forced on.
|
|
18
|
+
*
|
|
19
|
+
* A contract imports its decorators from `@webpieces/core-util`, and a path mapping or a
|
|
20
|
+
* `baseUrl` is what makes that name resolve. Reading the project's own tsconfig is therefore not
|
|
21
|
+
* a convenience — without it the checker cannot see that `POST` is the string `'POST'`, and the
|
|
22
|
+
* constant folder does the right thing and REFUSES rather than guessing a verb. Falling back to
|
|
23
|
+
* a fixed set of options is for a caller with no tsconfig at all, which is a legal thing to be.
|
|
24
|
+
*
|
|
25
|
+
* The flags below are forced over whatever the project says because they are about THIS read and
|
|
26
|
+
* not about the project's build: decorators must parse, nothing may be emitted, and a library
|
|
27
|
+
* type error in `node_modules` is the project's business, not a reason to refuse a document.
|
|
28
|
+
*/
|
|
29
|
+
private compilerOptions;
|
|
30
|
+
private contracts;
|
|
31
|
+
/**
|
|
32
|
+
* The extractor's own failure is re-thrown as THIS package's one error type, carrying the
|
|
33
|
+
* extractor's location and cure. The alternative — letting `ApiDocExtractionError` out — would
|
|
34
|
+
* give `wp-openapi` two failure types that its top-level handler had to know about, and only one
|
|
35
|
+
* of them would be documented on this package's surface.
|
|
36
|
+
*/
|
|
37
|
+
private extract;
|
|
38
|
+
private errorType;
|
|
39
|
+
private rethrow;
|
|
40
|
+
private responseHeaders;
|
|
41
|
+
private description;
|
|
42
|
+
private mustExist;
|
|
43
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InputsLoader = 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 ts = tslib_1.__importStar(require("typescript"));
|
|
8
|
+
const api_doc_model_1 = require("@webpieces/api-doc-model");
|
|
9
|
+
const OpenApiGenerationError_1 = require("../OpenApiGenerationError");
|
|
10
|
+
const ManifestLoader_1 = require("../manifest/ManifestLoader");
|
|
11
|
+
const GenerationInputs_1 = require("../generate/GenerationInputs");
|
|
12
|
+
const ExportedConstantFolder_1 = require("./ExportedConstantFolder");
|
|
13
|
+
const ForeignFailure_1 = require("./ForeignFailure");
|
|
14
|
+
/**
|
|
15
|
+
* The compiler options the contracts are read with, when the project has no `tsconfig.json` of its
|
|
16
|
+
* own to inherit from (see {@link InputsLoader.compilerOptions}).
|
|
17
|
+
*
|
|
18
|
+
* Decorators must be enabled or a contract does not parse at all, and `skipLibCheck` is on because
|
|
19
|
+
* this is an EXTRACTION pass, not a type-check: the app's own build is what decides whether the
|
|
20
|
+
* contract compiles, and duplicating that judgement here would make `wp-openapi` fail for reasons
|
|
21
|
+
* that have nothing to do with the document.
|
|
22
|
+
*/
|
|
23
|
+
const FALLBACK_OPTIONS = {
|
|
24
|
+
target: ts.ScriptTarget.ES2022,
|
|
25
|
+
module: ts.ModuleKind.CommonJS,
|
|
26
|
+
moduleResolution: ts.ModuleResolutionKind.Node10,
|
|
27
|
+
experimentalDecorators: true,
|
|
28
|
+
strict: true,
|
|
29
|
+
noEmit: true,
|
|
30
|
+
skipLibCheck: true,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Read everything from disk, ONCE, into {@link GenerationInputs}.
|
|
34
|
+
*
|
|
35
|
+
* Every path in the manifest is resolved against the MANIFEST's own directory, never against the
|
|
36
|
+
* process's cwd. A document whose contents depended on where `wp-openapi` was invoked from would be
|
|
37
|
+
* a different document per caller, and the committed golden would go red depending on which
|
|
38
|
+
* directory a build happened to run in.
|
|
39
|
+
*/
|
|
40
|
+
class InputsLoader {
|
|
41
|
+
manifests = new ManifestLoader_1.ManifestLoader();
|
|
42
|
+
constants = new ExportedConstantFolder_1.ExportedConstantFolder();
|
|
43
|
+
extractor = new api_doc_model_1.ApiDocExtractor();
|
|
44
|
+
options = FALLBACK_OPTIONS;
|
|
45
|
+
load(manifestPath) {
|
|
46
|
+
const manifest = this.manifests.load(manifestPath);
|
|
47
|
+
this.options = this.compilerOptions(manifestPath);
|
|
48
|
+
return new GenerationInputs_1.GenerationInputs(manifestPath, manifest, this.contracts(manifestPath, manifest), this.errorType(manifestPath, manifest), this.responseHeaders(manifestPath, manifest), this.description(manifestPath, manifest));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The nearest `tsconfig.json` at or above the MANIFEST, with the extraction flags forced on.
|
|
52
|
+
*
|
|
53
|
+
* A contract imports its decorators from `@webpieces/core-util`, and a path mapping or a
|
|
54
|
+
* `baseUrl` is what makes that name resolve. Reading the project's own tsconfig is therefore not
|
|
55
|
+
* a convenience — without it the checker cannot see that `POST` is the string `'POST'`, and the
|
|
56
|
+
* constant folder does the right thing and REFUSES rather than guessing a verb. Falling back to
|
|
57
|
+
* a fixed set of options is for a caller with no tsconfig at all, which is a legal thing to be.
|
|
58
|
+
*
|
|
59
|
+
* The flags below are forced over whatever the project says because they are about THIS read and
|
|
60
|
+
* not about the project's build: decorators must parse, nothing may be emitted, and a library
|
|
61
|
+
* type error in `node_modules` is the project's business, not a reason to refuse a document.
|
|
62
|
+
*/
|
|
63
|
+
compilerOptions(manifestPath) {
|
|
64
|
+
const configFile = ts.findConfigFile(path.dirname(manifestPath), (file) => ts.sys.fileExists(file));
|
|
65
|
+
if (configFile === undefined) {
|
|
66
|
+
return FALLBACK_OPTIONS;
|
|
67
|
+
}
|
|
68
|
+
const read = ts.readConfigFile(configFile, (file) => ts.sys.readFile(file));
|
|
69
|
+
if (read.error !== undefined || read.config === undefined) {
|
|
70
|
+
return FALLBACK_OPTIONS;
|
|
71
|
+
}
|
|
72
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(configFile));
|
|
73
|
+
const options = parsed.options;
|
|
74
|
+
options.noEmit = true;
|
|
75
|
+
options.skipLibCheck = true;
|
|
76
|
+
options.experimentalDecorators = true;
|
|
77
|
+
options.composite = false;
|
|
78
|
+
options.declaration = false;
|
|
79
|
+
return options;
|
|
80
|
+
}
|
|
81
|
+
contracts(manifestPath, manifest) {
|
|
82
|
+
return manifest.apis.map((entry) => {
|
|
83
|
+
const file = this.manifests.resolve(manifestPath, entry.entry);
|
|
84
|
+
this.mustExist(file, entry.entry, manifestPath);
|
|
85
|
+
return new GenerationInputs_1.ContractModel(entry, this.extract(file), file);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The extractor's own failure is re-thrown as THIS package's one error type, carrying the
|
|
90
|
+
* extractor's location and cure. The alternative — letting `ApiDocExtractionError` out — would
|
|
91
|
+
* give `wp-openapi` two failure types that its top-level handler had to know about, and only one
|
|
92
|
+
* of them would be documented on this package's surface.
|
|
93
|
+
*/
|
|
94
|
+
extract(file) {
|
|
95
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- re-thrown as the ONE error type of this package
|
|
96
|
+
try {
|
|
97
|
+
return this.extractor.extractFile(file, this.options);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
//const error = toError(err);
|
|
101
|
+
throw this.rethrow(err, file);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
errorType(manifestPath, manifest) {
|
|
105
|
+
const errors = manifest.errors;
|
|
106
|
+
if (errors === undefined) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const file = this.manifests.resolve(manifestPath, errors.entry);
|
|
110
|
+
this.mustExist(file, errors.entry, manifestPath);
|
|
111
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- re-thrown as the ONE error type of this package
|
|
112
|
+
try {
|
|
113
|
+
return this.extractor.extractType(file, errors.type, this.options);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
//const error = toError(err);
|
|
117
|
+
throw this.rethrow(err, file);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// webpieces-disable no-any-unknown -- a caught value; ForeignFailure is what narrows it
|
|
121
|
+
rethrow(err, file) {
|
|
122
|
+
if (err instanceof OpenApiGenerationError_1.OpenApiGenerationError) {
|
|
123
|
+
return err;
|
|
124
|
+
}
|
|
125
|
+
return ForeignFailure_1.ForeignFailure.of(err).asGenerationError(file, 'Fix the contract this document is read from.');
|
|
126
|
+
}
|
|
127
|
+
responseHeaders(manifestPath, manifest) {
|
|
128
|
+
return manifest.responseHeaders.map((header) => {
|
|
129
|
+
const file = this.manifests.resolve(manifestPath, header.entry);
|
|
130
|
+
return new GenerationInputs_1.ResolvedResponseHeader(this.constants.fold(file, header.nameConstant), header.description);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
description(manifestPath, manifest) {
|
|
134
|
+
if (manifest.descriptionFile === undefined) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
const file = this.manifests.resolve(manifestPath, manifest.descriptionFile);
|
|
138
|
+
this.mustExist(file, manifest.descriptionFile, manifestPath);
|
|
139
|
+
return fs.readFileSync(file, 'utf8').trim();
|
|
140
|
+
}
|
|
141
|
+
mustExist(file, declared, manifestPath) {
|
|
142
|
+
if (!fs.existsSync(file)) {
|
|
143
|
+
throw new OpenApiGenerationError_1.OpenApiGenerationError(`the manifest names '${declared}', which does not exist`, manifestPath, `Paths are relative to the manifest's own directory. Expected it at ${file}.`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.InputsLoader = InputsLoader;
|
|
148
|
+
//# sourceMappingURL=InputsLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InputsLoader.js","sourceRoot":"","sources":["../../../../../../packages/docs/openapi-generator/src/load/InputsLoader.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as ts from 'typescript';\nimport { ApiDocExtractor } from '@webpieces/api-doc-model';\nimport { OpenApiGenerationError } from '../OpenApiGenerationError';\nimport { ManifestLoader } from '../manifest/ManifestLoader';\nimport { ApiEntry, OpenApiManifest, ResponseHeaderEntry } from '../manifest/OpenApiManifest';\nimport {\n ContractModel,\n GenerationInputs,\n ResolvedResponseHeader,\n} from '../generate/GenerationInputs';\nimport { ExportedConstantFolder } from './ExportedConstantFolder';\nimport { ForeignFailure } from './ForeignFailure';\n\n/**\n * The compiler options the contracts are read with, when the project has no `tsconfig.json` of its\n * own to inherit from (see {@link InputsLoader.compilerOptions}).\n *\n * Decorators must be enabled or a contract does not parse at all, and `skipLibCheck` is on because\n * this is an EXTRACTION pass, not a type-check: the app's own build is what decides whether the\n * contract compiles, and duplicating that judgement here would make `wp-openapi` fail for reasons\n * that have nothing to do with the document.\n */\nconst FALLBACK_OPTIONS: ts.CompilerOptions = {\n target: ts.ScriptTarget.ES2022,\n module: ts.ModuleKind.CommonJS,\n moduleResolution: ts.ModuleResolutionKind.Node10,\n experimentalDecorators: true,\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n};\n\n/**\n * Read everything from disk, ONCE, into {@link GenerationInputs}.\n *\n * Every path in the manifest is resolved against the MANIFEST's own directory, never against the\n * process's cwd. A document whose contents depended on where `wp-openapi` was invoked from would be\n * a different document per caller, and the committed golden would go red depending on which\n * directory a build happened to run in.\n */\nexport class InputsLoader {\n private readonly manifests = new ManifestLoader();\n private readonly constants = new ExportedConstantFolder();\n private readonly extractor = new ApiDocExtractor();\n private options: ts.CompilerOptions = FALLBACK_OPTIONS;\n\n load(manifestPath: string): GenerationInputs {\n const manifest = this.manifests.load(manifestPath);\n this.options = this.compilerOptions(manifestPath);\n return new GenerationInputs(\n manifestPath,\n manifest,\n this.contracts(manifestPath, manifest),\n this.errorType(manifestPath, manifest),\n this.responseHeaders(manifestPath, manifest),\n this.description(manifestPath, manifest),\n );\n }\n\n /**\n * The nearest `tsconfig.json` at or above the MANIFEST, with the extraction flags forced on.\n *\n * A contract imports its decorators from `@webpieces/core-util`, and a path mapping or a\n * `baseUrl` is what makes that name resolve. Reading the project's own tsconfig is therefore not\n * a convenience — without it the checker cannot see that `POST` is the string `'POST'`, and the\n * constant folder does the right thing and REFUSES rather than guessing a verb. Falling back to\n * a fixed set of options is for a caller with no tsconfig at all, which is a legal thing to be.\n *\n * The flags below are forced over whatever the project says because they are about THIS read and\n * not about the project's build: decorators must parse, nothing may be emitted, and a library\n * type error in `node_modules` is the project's business, not a reason to refuse a document.\n */\n private compilerOptions(manifestPath: string): ts.CompilerOptions {\n const configFile = ts.findConfigFile(path.dirname(manifestPath), (file: string) =>\n ts.sys.fileExists(file),\n );\n if (configFile === undefined) {\n return FALLBACK_OPTIONS;\n }\n const read = ts.readConfigFile(configFile, (file: string) => ts.sys.readFile(file));\n if (read.error !== undefined || read.config === undefined) {\n return FALLBACK_OPTIONS;\n }\n const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(configFile));\n const options = parsed.options;\n options.noEmit = true;\n options.skipLibCheck = true;\n options.experimentalDecorators = true;\n options.composite = false;\n options.declaration = false;\n return options;\n }\n\n private contracts(manifestPath: string, manifest: OpenApiManifest): readonly ContractModel[] {\n return manifest.apis.map((entry: ApiEntry) => {\n const file = this.manifests.resolve(manifestPath, entry.entry);\n this.mustExist(file, entry.entry, manifestPath);\n return new ContractModel(entry, this.extract(file), file);\n });\n }\n\n /**\n * The extractor's own failure is re-thrown as THIS package's one error type, carrying the\n * extractor's location and cure. The alternative — letting `ApiDocExtractionError` out — would\n * give `wp-openapi` two failure types that its top-level handler had to know about, and only one\n * of them would be documented on this package's surface.\n */\n private extract(file: string): ReturnType<ApiDocExtractor['extractFile']> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- re-thrown as the ONE error type of this package\n try {\n return this.extractor.extractFile(file, this.options);\n } catch (err: unknown) {\n //const error = toError(err);\n throw this.rethrow(err, file);\n }\n }\n\n private errorType(\n manifestPath: string,\n manifest: OpenApiManifest,\n ): ReturnType<ApiDocExtractor['extractType']> | undefined {\n const errors = manifest.errors;\n if (errors === undefined) {\n return undefined;\n }\n const file = this.manifests.resolve(manifestPath, errors.entry);\n this.mustExist(file, errors.entry, manifestPath);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- re-thrown as the ONE error type of this package\n try {\n return this.extractor.extractType(file, errors.type, this.options);\n } catch (err: unknown) {\n //const error = toError(err);\n throw this.rethrow(err, file);\n }\n }\n\n // webpieces-disable no-any-unknown -- a caught value; ForeignFailure is what narrows it\n private rethrow(err: unknown, file: string): OpenApiGenerationError {\n if (err instanceof OpenApiGenerationError) {\n return err;\n }\n return ForeignFailure.of(err).asGenerationError(\n file,\n 'Fix the contract this document is read from.',\n );\n }\n\n private responseHeaders(\n manifestPath: string,\n manifest: OpenApiManifest,\n ): readonly ResolvedResponseHeader[] {\n return manifest.responseHeaders.map((header: ResponseHeaderEntry) => {\n const file = this.manifests.resolve(manifestPath, header.entry);\n return new ResolvedResponseHeader(\n this.constants.fold(file, header.nameConstant),\n header.description,\n );\n });\n }\n\n private description(manifestPath: string, manifest: OpenApiManifest): string | undefined {\n if (manifest.descriptionFile === undefined) {\n return undefined;\n }\n const file = this.manifests.resolve(manifestPath, manifest.descriptionFile);\n this.mustExist(file, manifest.descriptionFile, manifestPath);\n return fs.readFileSync(file, 'utf8').trim();\n }\n\n private mustExist(file: string, declared: string, manifestPath: string): void {\n if (!fs.existsSync(file)) {\n throw new OpenApiGenerationError(\n `the manifest names '${declared}', which does not exist`,\n manifestPath,\n `Paths are relative to the manifest's own directory. Expected it at ${file}.`,\n );\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A narrowing reader over ONE parsed JSON object.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this class exists at all
|
|
5
|
+
*
|
|
6
|
+
* `JSON.parse` returns something with no shape, and a loader that passed that shapelessness around
|
|
7
|
+
* would spread it over every method that touches the manifest. Concentrating it HERE means the rest
|
|
8
|
+
* of the package deals only in `string`, `JsonReader` and the manifest classes — and it means the
|
|
9
|
+
* "this value came off disk and has not been checked yet" state exists in exactly one file, where a
|
|
10
|
+
* reader can see every check that state is subjected to.
|
|
11
|
+
*
|
|
12
|
+
* ## Every miss is a HARD FAILURE naming the field
|
|
13
|
+
*
|
|
14
|
+
* There is deliberately no defaulting of anything a reader would notice. A published document quietly
|
|
15
|
+
* missing a section is indistinguishable, from the outside, from an API that genuinely has no error
|
|
16
|
+
* contract — so a mistyped `errors` must stop the run rather than silently publish less.
|
|
17
|
+
*/
|
|
18
|
+
export declare class JsonReader {
|
|
19
|
+
private readonly entries;
|
|
20
|
+
/** The file this came from, so every failure names something somebody can open. */
|
|
21
|
+
readonly where: string;
|
|
22
|
+
private constructor();
|
|
23
|
+
/** Parse a whole file. */
|
|
24
|
+
static parseFile(text: string, where: string): JsonReader;
|
|
25
|
+
/** One already-parsed value that must be an object. */
|
|
26
|
+
private static of;
|
|
27
|
+
has(field: string): boolean;
|
|
28
|
+
/** A required, non-empty string. */
|
|
29
|
+
string(field: string): string;
|
|
30
|
+
optionalString(field: string): string | undefined;
|
|
31
|
+
/** An array of strings, empty when the field is absent. */
|
|
32
|
+
strings(field: string): readonly string[];
|
|
33
|
+
/** A nested object, or undefined when the field is absent. */
|
|
34
|
+
object(field: string): JsonReader | undefined;
|
|
35
|
+
/** An array of objects, empty when the field is absent. */
|
|
36
|
+
objects(field: string): readonly JsonReader[];
|
|
37
|
+
}
|