@webpieces/docs-site 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 (95) hide show
  1. package/README.md +83 -0
  2. package/package.json +28 -0
  3. package/src/DocsSiteError.d.ts +24 -0
  4. package/src/DocsSiteError.js +33 -0
  5. package/src/DocsSiteError.js.map +1 -0
  6. package/src/assets/SiteScript.d.ts +11 -0
  7. package/src/assets/SiteScript.js +88 -0
  8. package/src/assets/SiteScript.js.map +1 -0
  9. package/src/assets/SiteStyles.d.ts +13 -0
  10. package/src/assets/SiteStyles.js +106 -0
  11. package/src/assets/SiteStyles.js.map +1 -0
  12. package/src/cli/DocsSiteCli.d.ts +37 -0
  13. package/src/cli/DocsSiteCli.js +116 -0
  14. package/src/cli/DocsSiteCli.js.map +1 -0
  15. package/src/cli/WpDocsSiteMain.d.ts +21 -0
  16. package/src/cli/WpDocsSiteMain.js +60 -0
  17. package/src/cli/WpDocsSiteMain.js.map +1 -0
  18. package/src/cli/wp-docs-site.d.ts +2 -0
  19. package/src/cli/wp-docs-site.js +22 -0
  20. package/src/cli/wp-docs-site.js.map +1 -0
  21. package/src/emit/SiteWriter.d.ts +13 -0
  22. package/src/emit/SiteWriter.js +34 -0
  23. package/src/emit/SiteWriter.js.map +1 -0
  24. package/src/index.d.ts +41 -0
  25. package/src/index.js +105 -0
  26. package/src/index.js.map +1 -0
  27. package/src/manifest/DocsManifest.d.ts +28 -0
  28. package/src/manifest/DocsManifest.js +44 -0
  29. package/src/manifest/DocsManifest.js.map +1 -0
  30. package/src/manifest/ProseLoader.d.ts +23 -0
  31. package/src/manifest/ProseLoader.js +81 -0
  32. package/src/manifest/ProseLoader.js.map +1 -0
  33. package/src/markdown/Markdown.d.ts +47 -0
  34. package/src/markdown/Markdown.js +178 -0
  35. package/src/markdown/Markdown.js.map +1 -0
  36. package/src/render/CardRenderer.d.ts +28 -0
  37. package/src/render/CardRenderer.js +70 -0
  38. package/src/render/CardRenderer.js.map +1 -0
  39. package/src/render/FieldTreeRenderer.d.ts +34 -0
  40. package/src/render/FieldTreeRenderer.js +91 -0
  41. package/src/render/FieldTreeRenderer.js.map +1 -0
  42. package/src/render/Html.d.ts +14 -0
  43. package/src/render/Html.js +28 -0
  44. package/src/render/Html.js.map +1 -0
  45. package/src/render/Layout.d.ts +21 -0
  46. package/src/render/Layout.js +74 -0
  47. package/src/render/Layout.js.map +1 -0
  48. package/src/render/NavModel.d.ts +47 -0
  49. package/src/render/NavModel.js +86 -0
  50. package/src/render/NavModel.js.map +1 -0
  51. package/src/render/OperationPageRenderer.d.ts +44 -0
  52. package/src/render/OperationPageRenderer.js +102 -0
  53. package/src/render/OperationPageRenderer.js.map +1 -0
  54. package/src/render/SitePage.d.ts +30 -0
  55. package/src/render/SitePage.js +46 -0
  56. package/src/render/SitePage.js.map +1 -0
  57. package/src/render/SiteRenderer.d.ts +34 -0
  58. package/src/render/SiteRenderer.js +121 -0
  59. package/src/render/SiteRenderer.js.map +1 -0
  60. package/src/render/SiteUrls.d.ts +24 -0
  61. package/src/render/SiteUrls.js +37 -0
  62. package/src/render/SiteUrls.js.map +1 -0
  63. package/src/samples/CodeSamples.d.ts +49 -0
  64. package/src/samples/CodeSamples.js +166 -0
  65. package/src/samples/CodeSamples.js.map +1 -0
  66. package/src/serve/DevServer.d.ts +35 -0
  67. package/src/serve/DevServer.js +109 -0
  68. package/src/serve/DevServer.js.map +1 -0
  69. package/src/spec/ApiSpec.d.ts +110 -0
  70. package/src/spec/ApiSpec.js +165 -0
  71. package/src/spec/ApiSpec.js.map +1 -0
  72. package/src/spec/ExampleBuilder.d.ts +32 -0
  73. package/src/spec/ExampleBuilder.js +119 -0
  74. package/src/spec/ExampleBuilder.js.map +1 -0
  75. package/src/spec/FieldTree.d.ts +75 -0
  76. package/src/spec/FieldTree.js +155 -0
  77. package/src/spec/FieldTree.js.map +1 -0
  78. package/src/spec/JsonNode.d.ts +53 -0
  79. package/src/spec/JsonNode.js +93 -0
  80. package/src/spec/JsonNode.js.map +1 -0
  81. package/src/spec/SchemaLens.d.ts +57 -0
  82. package/src/spec/SchemaLens.js +162 -0
  83. package/src/spec/SchemaLens.js.map +1 -0
  84. package/src/spec/SchemaShape.d.ts +22 -0
  85. package/src/spec/SchemaShape.js +61 -0
  86. package/src/spec/SchemaShape.js.map +1 -0
  87. package/src/spec/Slug.d.ts +16 -0
  88. package/src/spec/Slug.js +43 -0
  89. package/src/spec/Slug.js.map +1 -0
  90. package/src/spec/SpecLoader.d.ts +26 -0
  91. package/src/spec/SpecLoader.js +64 -0
  92. package/src/spec/SpecLoader.js.map +1 -0
  93. package/src/spec/SpecReader.d.ts +46 -0
  94. package/src/spec/SpecReader.js +174 -0
  95. package/src/spec/SpecReader.js.map +1 -0
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SiteUrls = void 0;
4
+ /**
5
+ * Every URL the site has, in ONE place.
6
+ *
7
+ * Each is a real FILE ending in `index.html`, and each is stated RELATIVE to the site root; a page
8
+ * prepends its own `../` prefix with {@link prefixFor}. Root-relative links (`/reference/…`) only
9
+ * work when the site is the whole host, and a generated reference is routinely served under a path —
10
+ * GitHub Pages does it by default — where a root-relative link 404s on every page but the home one.
11
+ * Ending each href in `index.html` rather than a bare directory is the same argument one level down:
12
+ * a directory URL needs a server willing to serve the index, and `file://` is not one.
13
+ */
14
+ class SiteUrls {
15
+ /** The generated home page. */
16
+ home() {
17
+ return 'index.html';
18
+ }
19
+ /** `reference/<kebab-operation>/index.html` — one file per operation URL. */
20
+ operation(operation) {
21
+ return `reference/${operation.slug}/index.html`;
22
+ }
23
+ /** `schemas/<kebab-name>/index.html` — one file per named object DTO. */
24
+ schema(schema) {
25
+ return `schemas/${schema.slug}/index.html`;
26
+ }
27
+ /** `prose/<kebab-title>/index.html`. */
28
+ prose(page) {
29
+ return `prose/${page.slug}/index.html`;
30
+ }
31
+ /** `../` once per directory between a page and the site root. */
32
+ prefixFor(url) {
33
+ return '../'.repeat(url.split('/').length - 1);
34
+ }
35
+ }
36
+ exports.SiteUrls = SiteUrls;
37
+ //# sourceMappingURL=SiteUrls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SiteUrls.js","sourceRoot":"","sources":["../../../../../../packages/docs/docs-site/src/render/SiteUrls.ts"],"names":[],"mappings":";;;AAGA;;;;;;;;;GASG;AACH,MAAa,QAAQ;IACjB,+BAA+B;IAC/B,IAAI;QACA,OAAO,YAAY,CAAC;IACxB,CAAC;IAED,6EAA6E;IAC7E,SAAS,CAAC,SAAwB;QAC9B,OAAO,aAAa,SAAS,CAAC,IAAI,aAAa,CAAC;IACpD,CAAC;IAED,yEAAyE;IACzE,MAAM,CAAC,MAAmB;QACtB,OAAO,WAAW,MAAM,CAAC,IAAI,aAAa,CAAC;IAC/C,CAAC;IAED,wCAAwC;IACxC,KAAK,CAAC,IAAe;QACjB,OAAO,SAAS,IAAI,CAAC,IAAI,aAAa,CAAC;IAC3C,CAAC;IAED,iEAAiE;IACjE,SAAS,CAAC,GAAW;QACjB,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;CACJ;AAzBD,4BAyBC","sourcesContent":["import { NamedSchema, OperationInfo } from '../spec/ApiSpec';\nimport { ProsePage } from '../manifest/DocsManifest';\n\n/**\n * Every URL the site has, in ONE place.\n *\n * Each is a real FILE ending in `index.html`, and each is stated RELATIVE to the site root; a page\n * prepends its own `../` prefix with {@link prefixFor}. Root-relative links (`/reference/…`) only\n * work when the site is the whole host, and a generated reference is routinely served under a path —\n * GitHub Pages does it by default — where a root-relative link 404s on every page but the home one.\n * Ending each href in `index.html` rather than a bare directory is the same argument one level down:\n * a directory URL needs a server willing to serve the index, and `file://` is not one.\n */\nexport class SiteUrls {\n /** The generated home page. */\n home(): string {\n return 'index.html';\n }\n\n /** `reference/<kebab-operation>/index.html` — one file per operation URL. */\n operation(operation: OperationInfo): string {\n return `reference/${operation.slug}/index.html`;\n }\n\n /** `schemas/<kebab-name>/index.html` — one file per named object DTO. */\n schema(schema: NamedSchema): string {\n return `schemas/${schema.slug}/index.html`;\n }\n\n /** `prose/<kebab-title>/index.html`. */\n prose(page: ProsePage): string {\n return `prose/${page.slug}/index.html`;\n }\n\n /** `../` once per directory between a page and the site root. */\n prefixFor(url: string): string {\n return '../'.repeat(url.split('/').length - 1);\n }\n}\n"]}
@@ -0,0 +1,49 @@
1
+ import { ApiSpec, OperationInfo } from '../spec/ApiSpec';
2
+ import { ExampleBuilder } from '../spec/ExampleBuilder';
3
+ /** One language's sample for one operation. `id` is the tab's value, `label` is what a reader sees. */
4
+ export declare class CodeSample {
5
+ readonly id: string;
6
+ readonly label: string;
7
+ readonly source: string;
8
+ constructor(id: string, label: string, source: string);
9
+ }
10
+ /** One header a sample sends: the name the document declares, and a placeholder for the value. */
11
+ export declare class SampleHeader {
12
+ readonly name: string;
13
+ readonly value: string;
14
+ constructor(name: string, value: string);
15
+ }
16
+ /**
17
+ * Builds the code samples FROM THE SPEC: the server url, the path, the header names the document's
18
+ * own security schemes declare, and an example body generated from the request schema.
19
+ *
20
+ * Nothing here is authored per operation, which is the point. A sample somebody wrote by hand is a
21
+ * copy of the contract that no build can contradict, so the first rename leaves a partner pasting a
22
+ * header the server stopped reading.
23
+ *
24
+ * ## No samples for a webhook
25
+ *
26
+ * A webhook's url belongs to the PARTNER. A curl of ours against one would invite them to call
27
+ * something that does not exist, so the whole card is absent rather than present-and-wrong.
28
+ *
29
+ * ## No vendor logos
30
+ *
31
+ * The languages are labelled in words. The gopher and the Java cup are other companies'
32
+ * trademarks, and a docs generator that ships them hands every consumer that problem too.
33
+ */
34
+ export declare class CodeSampleBuilder {
35
+ private readonly spec;
36
+ private readonly examples;
37
+ constructor(spec: ApiSpec, examples: ExampleBuilder);
38
+ /** Every sample for an operation, in published tab order. Empty for a webhook. */
39
+ samplesFor(operation: OperationInfo): readonly CodeSample[];
40
+ /** The header each security scheme this operation requires is sent in. */
41
+ headersFor(operation: OperationInfo): readonly SampleHeader[];
42
+ private baseUrl;
43
+ private hostName;
44
+ private http;
45
+ private javascript;
46
+ private go;
47
+ private java;
48
+ private indent;
49
+ }
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CodeSampleBuilder = exports.SampleHeader = exports.CodeSample = void 0;
4
+ /** One language's sample for one operation. `id` is the tab's value, `label` is what a reader sees. */
5
+ class CodeSample {
6
+ id;
7
+ label;
8
+ source;
9
+ constructor(id, label, source) {
10
+ this.id = id;
11
+ this.label = label;
12
+ this.source = source;
13
+ }
14
+ }
15
+ exports.CodeSample = CodeSample;
16
+ /** One header a sample sends: the name the document declares, and a placeholder for the value. */
17
+ class SampleHeader {
18
+ name;
19
+ value;
20
+ constructor(name, value) {
21
+ this.name = name;
22
+ this.value = value;
23
+ }
24
+ }
25
+ exports.SampleHeader = SampleHeader;
26
+ /**
27
+ * Builds the code samples FROM THE SPEC: the server url, the path, the header names the document's
28
+ * own security schemes declare, and an example body generated from the request schema.
29
+ *
30
+ * Nothing here is authored per operation, which is the point. A sample somebody wrote by hand is a
31
+ * copy of the contract that no build can contradict, so the first rename leaves a partner pasting a
32
+ * header the server stopped reading.
33
+ *
34
+ * ## No samples for a webhook
35
+ *
36
+ * A webhook's url belongs to the PARTNER. A curl of ours against one would invite them to call
37
+ * something that does not exist, so the whole card is absent rather than present-and-wrong.
38
+ *
39
+ * ## No vendor logos
40
+ *
41
+ * The languages are labelled in words. The gopher and the Java cup are other companies'
42
+ * trademarks, and a docs generator that ships them hands every consumer that problem too.
43
+ */
44
+ class CodeSampleBuilder {
45
+ spec;
46
+ examples;
47
+ constructor(spec, examples) {
48
+ this.spec = spec;
49
+ this.examples = examples;
50
+ }
51
+ /** Every sample for an operation, in published tab order. Empty for a webhook. */
52
+ samplesFor(operation) {
53
+ if (operation.isWebhook) {
54
+ return [];
55
+ }
56
+ const url = `${this.baseUrl()}${operation.path}`;
57
+ const headers = this.headersFor(operation);
58
+ const body = this.examples.render(this.examples.build(operation.requestSchema));
59
+ return [
60
+ new CodeSample('http', 'HTTP', this.http(operation, headers, body)),
61
+ new CodeSample('javascript', 'JavaScript', this.javascript(operation, url, headers, body)),
62
+ new CodeSample('go', 'Go', this.go(operation, url, headers, body)),
63
+ new CodeSample('java', 'Java', this.java(operation, url, headers, body)),
64
+ ];
65
+ }
66
+ /** The header each security scheme this operation requires is sent in. */
67
+ headersFor(operation) {
68
+ const headers = [];
69
+ for (const key of operation.securityKeys) {
70
+ const scheme = this.spec.securitySchemes.find((one) => one.key === key);
71
+ if (scheme !== undefined && scheme.sentIn === 'header' && scheme.headerName !== '') {
72
+ headers.push(new SampleHeader(scheme.headerName, `<your ${scheme.key}>`));
73
+ }
74
+ }
75
+ return headers;
76
+ }
77
+ baseUrl() {
78
+ const first = this.spec.servers[0];
79
+ return first === undefined ? '' : first.url.replace(/\/$/, '');
80
+ }
81
+ hostName() {
82
+ const host = this.baseUrl().replace(/^https?:\/\//, '');
83
+ return host === '' ? 'your-api-host' : host;
84
+ }
85
+ http(operation, headers, body) {
86
+ const lines = [
87
+ `${operation.httpMethod} ${operation.path} HTTP/1.1`,
88
+ `Host: ${this.hostName()}`,
89
+ ];
90
+ for (const header of headers) {
91
+ lines.push(`${header.name}: ${header.value}`);
92
+ }
93
+ if (body !== '') {
94
+ lines.push('Content-Type: application/json');
95
+ lines.push('');
96
+ lines.push(body);
97
+ }
98
+ return lines.join('\n');
99
+ }
100
+ javascript(operation, url, headers, body) {
101
+ const lines = [
102
+ // webpieces-disable no-fetch -- sample TEXT a partner pastes into their own client; nothing here calls anything
103
+ `const response = await fetch('${url}', {`,
104
+ ` method: '${operation.httpMethod}',`,
105
+ ' headers: {',
106
+ ];
107
+ for (const header of headers) {
108
+ lines.push(` '${header.name}': '${header.value}',`);
109
+ }
110
+ if (body !== '') {
111
+ lines.push(" 'Content-Type': 'application/json',");
112
+ }
113
+ lines.push(' },');
114
+ if (body !== '') {
115
+ lines.push(` body: JSON.stringify(${this.indent(body, ' ')}),`);
116
+ }
117
+ lines.push('});');
118
+ lines.push('const result = await response.json();');
119
+ return lines.join('\n');
120
+ }
121
+ go(operation, url, headers, body) {
122
+ const lines = [];
123
+ if (body === '') {
124
+ lines.push(`req, _ := http.NewRequest("${operation.httpMethod}", "${url}", nil)`);
125
+ }
126
+ else {
127
+ lines.push(`body := []byte(\`${body}\`)`);
128
+ lines.push(`req, _ := http.NewRequest("${operation.httpMethod}", "${url}", bytes.NewReader(body))`);
129
+ lines.push('req.Header.Set("Content-Type", "application/json")');
130
+ }
131
+ for (const header of headers) {
132
+ lines.push(`req.Header.Set("${header.name}", "${header.value}")`);
133
+ }
134
+ lines.push('resp, err := http.DefaultClient.Do(req)');
135
+ return lines.join('\n');
136
+ }
137
+ java(operation, url, headers, body) {
138
+ const lines = [
139
+ 'HttpRequest request = HttpRequest.newBuilder()',
140
+ ` .uri(URI.create("${url}"))`,
141
+ ];
142
+ for (const header of headers) {
143
+ lines.push(` .header("${header.name}", "${header.value}")`);
144
+ }
145
+ if (body === '') {
146
+ lines.push(` .method("${operation.httpMethod}", HttpRequest.BodyPublishers.noBody())`);
147
+ }
148
+ else {
149
+ lines.push(' .header("Content-Type", "application/json")');
150
+ lines.push(` .method("${operation.httpMethod}", HttpRequest.BodyPublishers.ofString("""`);
151
+ lines.push(body);
152
+ lines.push('"""))');
153
+ }
154
+ lines.push(' .build();');
155
+ lines.push('HttpResponse<String> response = client.send(request, BodyHandlers.ofString());');
156
+ return lines.join('\n');
157
+ }
158
+ indent(body, prefix) {
159
+ return body
160
+ .split('\n')
161
+ .map((line, index) => (index === 0 ? line : `${prefix}${line}`))
162
+ .join('\n');
163
+ }
164
+ }
165
+ exports.CodeSampleBuilder = CodeSampleBuilder;
166
+ //# sourceMappingURL=CodeSamples.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CodeSamples.js","sourceRoot":"","sources":["../../../../../../packages/docs/docs-site/src/samples/CodeSamples.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiSpec, OperationInfo, SecuritySchemeInfo } from '../spec/ApiSpec';\nimport { ExampleBuilder } from '../spec/ExampleBuilder';\n\n/** One language's sample for one operation. `id` is the tab's value, `label` is what a reader sees. */\nexport class CodeSample {\n constructor(\n readonly id: string,\n readonly label: string,\n readonly source: string,\n ) {}\n}\n\n/** One header a sample sends: the name the document declares, and a placeholder for the value. */\nexport class SampleHeader {\n constructor(\n readonly name: string,\n readonly value: string,\n ) {}\n}\n\n/**\n * Builds the code samples FROM THE SPEC: the server url, the path, the header names the document's\n * own security schemes declare, and an example body generated from the request schema.\n *\n * Nothing here is authored per operation, which is the point. A sample somebody wrote by hand is a\n * copy of the contract that no build can contradict, so the first rename leaves a partner pasting a\n * header the server stopped reading.\n *\n * ## No samples for a webhook\n *\n * A webhook's url belongs to the PARTNER. A curl of ours against one would invite them to call\n * something that does not exist, so the whole card is absent rather than present-and-wrong.\n *\n * ## No vendor logos\n *\n * The languages are labelled in words. The gopher and the Java cup are other companies'\n * trademarks, and a docs generator that ships them hands every consumer that problem too.\n */\nexport class CodeSampleBuilder {\n constructor(\n private readonly spec: ApiSpec,\n private readonly examples: ExampleBuilder,\n ) {}\n\n /** Every sample for an operation, in published tab order. Empty for a webhook. */\n samplesFor(operation: OperationInfo): readonly CodeSample[] {\n if (operation.isWebhook) {\n return [];\n }\n const url = `${this.baseUrl()}${operation.path}`;\n const headers = this.headersFor(operation);\n const body = this.examples.render(this.examples.build(operation.requestSchema));\n return [\n new CodeSample('http', 'HTTP', this.http(operation, headers, body)),\n new CodeSample(\n 'javascript',\n 'JavaScript',\n this.javascript(operation, url, headers, body),\n ),\n new CodeSample('go', 'Go', this.go(operation, url, headers, body)),\n new CodeSample('java', 'Java', this.java(operation, url, headers, body)),\n ];\n }\n\n /** The header each security scheme this operation requires is sent in. */\n headersFor(operation: OperationInfo): readonly SampleHeader[] {\n const headers: SampleHeader[] = [];\n for (const key of operation.securityKeys) {\n const scheme = this.spec.securitySchemes.find(\n (one: SecuritySchemeInfo): boolean => one.key === key,\n );\n if (scheme !== undefined && scheme.sentIn === 'header' && scheme.headerName !== '') {\n headers.push(new SampleHeader(scheme.headerName, `<your ${scheme.key}>`));\n }\n }\n return headers;\n }\n\n private baseUrl(): string {\n const first = this.spec.servers[0];\n return first === undefined ? '' : first.url.replace(/\\/$/, '');\n }\n\n private hostName(): string {\n const host = this.baseUrl().replace(/^https?:\\/\\//, '');\n return host === '' ? 'your-api-host' : host;\n }\n\n private http(operation: OperationInfo, headers: readonly SampleHeader[], body: string): string {\n const lines = [\n `${operation.httpMethod} ${operation.path} HTTP/1.1`,\n `Host: ${this.hostName()}`,\n ];\n for (const header of headers) {\n lines.push(`${header.name}: ${header.value}`);\n }\n if (body !== '') {\n lines.push('Content-Type: application/json');\n lines.push('');\n lines.push(body);\n }\n return lines.join('\\n');\n }\n\n private javascript(\n operation: OperationInfo,\n url: string,\n headers: readonly SampleHeader[],\n body: string,\n ): string {\n const lines = [\n // webpieces-disable no-fetch -- sample TEXT a partner pastes into their own client; nothing here calls anything\n `const response = await fetch('${url}', {`,\n ` method: '${operation.httpMethod}',`,\n ' headers: {',\n ];\n for (const header of headers) {\n lines.push(` '${header.name}': '${header.value}',`);\n }\n if (body !== '') {\n lines.push(\" 'Content-Type': 'application/json',\");\n }\n lines.push(' },');\n if (body !== '') {\n lines.push(` body: JSON.stringify(${this.indent(body, ' ')}),`);\n }\n lines.push('});');\n lines.push('const result = await response.json();');\n return lines.join('\\n');\n }\n\n private go(\n operation: OperationInfo,\n url: string,\n headers: readonly SampleHeader[],\n body: string,\n ): string {\n const lines: string[] = [];\n if (body === '') {\n lines.push(`req, _ := http.NewRequest(\"${operation.httpMethod}\", \"${url}\", nil)`);\n } else {\n lines.push(`body := []byte(\\`${body}\\`)`);\n lines.push(\n `req, _ := http.NewRequest(\"${operation.httpMethod}\", \"${url}\", bytes.NewReader(body))`,\n );\n lines.push('req.Header.Set(\"Content-Type\", \"application/json\")');\n }\n for (const header of headers) {\n lines.push(`req.Header.Set(\"${header.name}\", \"${header.value}\")`);\n }\n lines.push('resp, err := http.DefaultClient.Do(req)');\n return lines.join('\\n');\n }\n\n private java(\n operation: OperationInfo,\n url: string,\n headers: readonly SampleHeader[],\n body: string,\n ): string {\n const lines = [\n 'HttpRequest request = HttpRequest.newBuilder()',\n ` .uri(URI.create(\"${url}\"))`,\n ];\n for (const header of headers) {\n lines.push(` .header(\"${header.name}\", \"${header.value}\")`);\n }\n if (body === '') {\n lines.push(\n ` .method(\"${operation.httpMethod}\", HttpRequest.BodyPublishers.noBody())`,\n );\n } else {\n lines.push(' .header(\"Content-Type\", \"application/json\")');\n lines.push(\n ` .method(\"${operation.httpMethod}\", HttpRequest.BodyPublishers.ofString(\"\"\"`,\n );\n lines.push(body);\n lines.push('\"\"\"))');\n }\n lines.push(' .build();');\n lines.push(\n 'HttpResponse<String> response = client.send(request, BodyHandlers.ofString());',\n );\n return lines.join('\\n');\n }\n\n private indent(body: string, prefix: string): string {\n return body\n .split('\\n')\n .map((line: string, index: number): string => (index === 0 ? line : `${prefix}${line}`))\n .join('\\n');\n }\n}\n"]}
@@ -0,0 +1,35 @@
1
+ /** The only interface this server ever binds. It is not configurable, on purpose. */
2
+ export declare const LOOPBACK = "127.0.0.1";
3
+ /**
4
+ * A LOCALHOST-ONLY static preview of a generated site — what `wp-docs-site --serve` runs.
5
+ *
6
+ * ## It is a preview, and it is not a host
7
+ *
8
+ * It binds {@link LOOPBACK} and nothing else, so it is not reachable from another machine. It is not
9
+ * hardened and it is not authenticated: there is no logging, no rate limiting, no TLS and no access
10
+ * control, and nothing here should be read as a claim that adding an address would make it safe to
11
+ * expose. Hosting a generated site is explicitly out of this package's scope (#985) — the output is
12
+ * a folder, and a real static host serves it.
13
+ *
14
+ * The one security property it DOES enforce is that a request cannot escape the output directory:
15
+ * the resolved path is checked against the root, so `../../etc/passwd` is a 404 rather than a read.
16
+ * That is not hardening, it is the minimum for a process that opens a socket at all.
17
+ */
18
+ export declare class DevServer {
19
+ private readonly root;
20
+ private server;
21
+ constructor(root: string);
22
+ /** Binds and resolves with the port actually listening. `0` asks the OS for a free one. */
23
+ start(port: number): Promise<number>;
24
+ /** The bound port, or 0 when nothing is listening. */
25
+ port(): number;
26
+ /** The url a human opens. */
27
+ url(): string;
28
+ stop(): Promise<void>;
29
+ private handle;
30
+ /**
31
+ * The file a request maps to, or `undefined` for anything outside the root or not there.
32
+ * A directory maps to its `index.html`, which is what makes `/reference/fetch-orders/` work.
33
+ */
34
+ fileFor(requestUrl: string): string | undefined;
35
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DevServer = exports.LOOPBACK = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("node:fs"));
6
+ const http = tslib_1.__importStar(require("node:http"));
7
+ const path = tslib_1.__importStar(require("node:path"));
8
+ const DocsSiteError_1 = require("../DocsSiteError");
9
+ /** The only interface this server ever binds. It is not configurable, on purpose. */
10
+ exports.LOOPBACK = '127.0.0.1';
11
+ /** Extension to content type. Four entries, because a generated site holds four kinds of file. */
12
+ const CONTENT_TYPES = new Map([
13
+ ['.html', 'text/html; charset=utf-8'],
14
+ ['.css', 'text/css; charset=utf-8'],
15
+ ['.js', 'text/javascript; charset=utf-8'],
16
+ ['.json', 'application/json; charset=utf-8'],
17
+ ]);
18
+ /**
19
+ * A LOCALHOST-ONLY static preview of a generated site — what `wp-docs-site --serve` runs.
20
+ *
21
+ * ## It is a preview, and it is not a host
22
+ *
23
+ * It binds {@link LOOPBACK} and nothing else, so it is not reachable from another machine. It is not
24
+ * hardened and it is not authenticated: there is no logging, no rate limiting, no TLS and no access
25
+ * control, and nothing here should be read as a claim that adding an address would make it safe to
26
+ * expose. Hosting a generated site is explicitly out of this package's scope (#985) — the output is
27
+ * a folder, and a real static host serves it.
28
+ *
29
+ * The one security property it DOES enforce is that a request cannot escape the output directory:
30
+ * the resolved path is checked against the root, so `../../etc/passwd` is a 404 rather than a read.
31
+ * That is not hardening, it is the minimum for a process that opens a socket at all.
32
+ */
33
+ class DevServer {
34
+ root;
35
+ server;
36
+ constructor(root) {
37
+ this.root = root;
38
+ }
39
+ /** Binds and resolves with the port actually listening. `0` asks the OS for a free one. */
40
+ start(port) {
41
+ if (!fs.existsSync(this.root)) {
42
+ throw new DocsSiteError_1.DocsSiteError('there is no generated site to preview', this.root, 'Run wp-docs-site --spec <document> --out <dir> first, then --serve the same --out.');
43
+ }
44
+ const server = http.createServer((request, response) => {
45
+ this.handle(request, response);
46
+ });
47
+ this.server = server;
48
+ return new Promise((resolve) => {
49
+ server.listen(port, exports.LOOPBACK, () => {
50
+ resolve(this.port());
51
+ });
52
+ });
53
+ }
54
+ /** The bound port, or 0 when nothing is listening. */
55
+ port() {
56
+ const address = this.server?.address();
57
+ if (address === undefined || address === null || typeof address === 'string') {
58
+ return 0;
59
+ }
60
+ return address.port;
61
+ }
62
+ /** The url a human opens. */
63
+ url() {
64
+ return `http://${exports.LOOPBACK}:${this.port()}/`;
65
+ }
66
+ stop() {
67
+ const server = this.server;
68
+ if (server === undefined) {
69
+ return Promise.resolve();
70
+ }
71
+ this.server = undefined;
72
+ return new Promise((resolve) => {
73
+ server.close(() => {
74
+ resolve();
75
+ });
76
+ });
77
+ }
78
+ handle(request, response) {
79
+ const file = this.fileFor(request.url ?? '/');
80
+ if (file === undefined) {
81
+ response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
82
+ response.end('not found');
83
+ return;
84
+ }
85
+ const type = CONTENT_TYPES.get(path.extname(file)) ?? 'application/octet-stream';
86
+ response.writeHead(200, { 'content-type': type });
87
+ response.end(fs.readFileSync(file));
88
+ }
89
+ /**
90
+ * The file a request maps to, or `undefined` for anything outside the root or not there.
91
+ * A directory maps to its `index.html`, which is what makes `/reference/fetch-orders/` work.
92
+ */
93
+ fileFor(requestUrl) {
94
+ const withoutQuery = requestUrl.split('?')[0] ?? '/';
95
+ const decoded = decodeURIComponent(withoutQuery);
96
+ const resolvedRoot = path.resolve(this.root);
97
+ const target = path.resolve(resolvedRoot, `.${decoded}`);
98
+ if (target !== resolvedRoot && !target.startsWith(`${resolvedRoot}${path.sep}`)) {
99
+ return undefined;
100
+ }
101
+ if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
102
+ const index = path.join(target, 'index.html');
103
+ return fs.existsSync(index) ? index : undefined;
104
+ }
105
+ return fs.existsSync(target) ? target : undefined;
106
+ }
107
+ }
108
+ exports.DevServer = DevServer;
109
+ //# sourceMappingURL=DevServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevServer.js","sourceRoot":"","sources":["../../../../../../packages/docs/docs-site/src/serve/DevServer.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as http from 'node:http';\nimport * as path from 'node:path';\nimport { DocsSiteError } from '../DocsSiteError';\n\n/** The only interface this server ever binds. It is not configurable, on purpose. */\nexport const LOOPBACK = '127.0.0.1';\n\n/** Extension to content type. Four entries, because a generated site holds four kinds of file. */\nconst CONTENT_TYPES = new Map<string, string>([\n ['.html', 'text/html; charset=utf-8'],\n ['.css', 'text/css; charset=utf-8'],\n ['.js', 'text/javascript; charset=utf-8'],\n ['.json', 'application/json; charset=utf-8'],\n]);\n\n/**\n * A LOCALHOST-ONLY static preview of a generated site — what `wp-docs-site --serve` runs.\n *\n * ## It is a preview, and it is not a host\n *\n * It binds {@link LOOPBACK} and nothing else, so it is not reachable from another machine. It is not\n * hardened and it is not authenticated: there is no logging, no rate limiting, no TLS and no access\n * control, and nothing here should be read as a claim that adding an address would make it safe to\n * expose. Hosting a generated site is explicitly out of this package's scope (#985) — the output is\n * a folder, and a real static host serves it.\n *\n * The one security property it DOES enforce is that a request cannot escape the output directory:\n * the resolved path is checked against the root, so `../../etc/passwd` is a 404 rather than a read.\n * That is not hardening, it is the minimum for a process that opens a socket at all.\n */\nexport class DevServer {\n private server: http.Server | undefined;\n\n constructor(private readonly root: string) {}\n\n /** Binds and resolves with the port actually listening. `0` asks the OS for a free one. */\n start(port: number): Promise<number> {\n if (!fs.existsSync(this.root)) {\n throw new DocsSiteError(\n 'there is no generated site to preview',\n this.root,\n 'Run wp-docs-site --spec <document> --out <dir> first, then --serve the same --out.',\n );\n }\n const server = http.createServer(\n (request: http.IncomingMessage, response: http.ServerResponse): void => {\n this.handle(request, response);\n },\n );\n this.server = server;\n return new Promise<number>((resolve: (value: number) => void): void => {\n server.listen(port, LOOPBACK, (): void => {\n resolve(this.port());\n });\n });\n }\n\n /** The bound port, or 0 when nothing is listening. */\n port(): number {\n const address = this.server?.address();\n if (address === undefined || address === null || typeof address === 'string') {\n return 0;\n }\n return address.port;\n }\n\n /** The url a human opens. */\n url(): string {\n return `http://${LOOPBACK}:${this.port()}/`;\n }\n\n stop(): Promise<void> {\n const server = this.server;\n if (server === undefined) {\n return Promise.resolve();\n }\n this.server = undefined;\n return new Promise<void>((resolve: () => void): void => {\n server.close((): void => {\n resolve();\n });\n });\n }\n\n private handle(request: http.IncomingMessage, response: http.ServerResponse): void {\n const file = this.fileFor(request.url ?? '/');\n if (file === undefined) {\n response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });\n response.end('not found');\n return;\n }\n const type = CONTENT_TYPES.get(path.extname(file)) ?? 'application/octet-stream';\n response.writeHead(200, { 'content-type': type });\n response.end(fs.readFileSync(file));\n }\n\n /**\n * The file a request maps to, or `undefined` for anything outside the root or not there.\n * A directory maps to its `index.html`, which is what makes `/reference/fetch-orders/` work.\n */\n fileFor(requestUrl: string): string | undefined {\n const withoutQuery = requestUrl.split('?')[0] ?? '/';\n const decoded = decodeURIComponent(withoutQuery);\n const resolvedRoot = path.resolve(this.root);\n const target = path.resolve(resolvedRoot, `.${decoded}`);\n if (target !== resolvedRoot && !target.startsWith(`${resolvedRoot}${path.sep}`)) {\n return undefined;\n }\n if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {\n const index = path.join(target, 'index.html');\n return fs.existsSync(index) ? index : undefined;\n }\n return fs.existsSync(target) ? target : undefined;\n }\n}\n"]}
@@ -0,0 +1,110 @@
1
+ import { JsonNode } from './JsonNode';
2
+ /**
3
+ * The model this package renders. It is read from a document STRUCTURALLY (see {@link JsonNode}), so
4
+ * it is deliberately NOT `@webpieces/openapi-generator`'s document classes even though it looks like
5
+ * them: a compile-time dependency on the generator would make "generated by us" a precondition for
6
+ * "renderable by us", and #985 is explicit that any conforming 3.0 or 3.1 document renders here.
7
+ *
8
+ * Everything below is data, so everything below is a class (`CLAUDE.md` §1).
9
+ */
10
+ /** One entry of the document's `servers[]`. The first one is what the code samples call. */
11
+ export declare class ServerInfo {
12
+ readonly url: string;
13
+ readonly description: string | undefined;
14
+ constructor(url: string, description: string | undefined);
15
+ }
16
+ /**
17
+ * One published security scheme. Only the fields a partner has to ACT on are carried: what to send
18
+ * and where. `headerName` is empty for a scheme that is not sent in a header, and the code samples
19
+ * then omit it rather than inventing a header name the server never reads.
20
+ */
21
+ export declare class SecuritySchemeInfo {
22
+ readonly key: string;
23
+ readonly kind: string;
24
+ readonly sentIn: string;
25
+ readonly headerName: string;
26
+ readonly description: string | undefined;
27
+ constructor(key: string, kind: string, sentIn: string, headerName: string, description: string | undefined);
28
+ }
29
+ /** One response of one operation. */
30
+ export declare class ResponseInfo {
31
+ readonly status: string;
32
+ readonly description: string;
33
+ readonly schema: JsonNode | undefined;
34
+ constructor(status: string, description: string, schema: JsonNode | undefined);
35
+ }
36
+ /**
37
+ * One rendered page's worth of operation.
38
+ *
39
+ * A WEBHOOK carries `isWebhook`, and with it no method, no path and no code samples. The url belongs
40
+ * to the PARTNER, so a curl of ours against one would invite them to call something that does not
41
+ * exist — see `responsibilities.md`.
42
+ */
43
+ export declare class OperationInfo {
44
+ readonly name: string;
45
+ readonly slug: string;
46
+ readonly httpMethod: string;
47
+ readonly path: string;
48
+ readonly description: string;
49
+ readonly tag: string;
50
+ readonly requestSchema: JsonNode | undefined;
51
+ readonly responses: readonly ResponseInfo[];
52
+ readonly isWebhook: boolean;
53
+ readonly securityKeys: readonly string[];
54
+ /**
55
+ * The event NAME a webhook is keyed by in the document's `webhooks:` block, and the empty
56
+ * string for an ordinary operation. It is carried separately from `name` because the two
57
+ * are different published facts — `orderStateChanged` is what we call the method,
58
+ * `order.state-changed` is what the partner matches on the wire.
59
+ */
60
+ readonly eventName: string;
61
+ constructor(name: string, slug: string, httpMethod: string, path: string, description: string, tag: string, requestSchema: JsonNode | undefined, responses: readonly ResponseInfo[], isWebhook: boolean, securityKeys: readonly string[],
62
+ /**
63
+ * The event NAME a webhook is keyed by in the document's `webhooks:` block, and the empty
64
+ * string for an ordinary operation. It is carried separately from `name` because the two
65
+ * are different published facts — `orderStateChanged` is what we call the method,
66
+ * `order.state-changed` is what the partner matches on the wire.
67
+ */
68
+ eventName: string);
69
+ }
70
+ /**
71
+ * One nav section: an OpenAPI tag and the operations under it, webhooks appended after the
72
+ * operations that share the tag. EVERY tag gets one — a tag missing from the sidebar is an operation
73
+ * a partner cannot find, which reads exactly like an operation that does not exist.
74
+ */
75
+ export declare class TagSection {
76
+ readonly name: string;
77
+ readonly description: string;
78
+ readonly operations: readonly OperationInfo[];
79
+ constructor(name: string, description: string, operations: readonly OperationInfo[]);
80
+ }
81
+ /**
82
+ * One named schema from `components.schemas`.
83
+ *
84
+ * `hasOwnPage` is true for an OBJECT only. A named enum or scalar alias stays inline at every use
85
+ * site, because a page carrying one line of chips is a click that costs the reader the context they
86
+ * were reading in.
87
+ */
88
+ export declare class NamedSchema {
89
+ readonly name: string;
90
+ readonly slug: string;
91
+ readonly node: JsonNode;
92
+ readonly hasOwnPage: boolean;
93
+ constructor(name: string, slug: string, node: JsonNode, hasOwnPage: boolean);
94
+ }
95
+ /** The whole document, read once. */
96
+ export declare class ApiSpec {
97
+ readonly title: string;
98
+ readonly version: string;
99
+ readonly description: string;
100
+ readonly servers: readonly ServerInfo[];
101
+ readonly sections: readonly TagSection[];
102
+ readonly securitySchemes: readonly SecuritySchemeInfo[];
103
+ readonly schemas: readonly NamedSchema[];
104
+ readonly documentSecurityKeys: readonly string[];
105
+ constructor(title: string, version: string, description: string, servers: readonly ServerInfo[], sections: readonly TagSection[], securitySchemes: readonly SecuritySchemeInfo[], schemas: readonly NamedSchema[], documentSecurityKeys: readonly string[]);
106
+ /** The named schema `name` refers to, or `undefined` when the document does not define it. */
107
+ schemaNamed(name: string): NamedSchema | undefined;
108
+ /** Every operation, across every section, in published order. */
109
+ operations(): readonly OperationInfo[];
110
+ }