@scalar/openapi-to-markdown 0.5.44 → 1.0.0

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 (49) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +73 -34
  3. package/dist/create-markdown-from-openapi.d.ts +16 -15
  4. package/dist/create-markdown-from-openapi.d.ts.map +1 -1
  5. package/dist/create-markdown-from-openapi.js +35 -0
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -600
  9. package/dist/load-document.d.ts +4 -0
  10. package/dist/load-document.d.ts.map +1 -0
  11. package/dist/load-document.js +151 -0
  12. package/dist/markdown-nodes.d.ts +25 -0
  13. package/dist/markdown-nodes.d.ts.map +1 -0
  14. package/dist/markdown-nodes.js +34 -0
  15. package/dist/parse-description.d.ts +6 -0
  16. package/dist/parse-description.d.ts.map +1 -0
  17. package/dist/parse-description.js +116 -0
  18. package/dist/parse-html-description.d.ts +4 -0
  19. package/dist/parse-html-description.d.ts.map +1 -0
  20. package/dist/parse-html-description.js +11 -0
  21. package/dist/render-document.d.ts +4 -0
  22. package/dist/render-document.d.ts.map +1 -0
  23. package/dist/render-document.js +112 -0
  24. package/dist/render-operation.d.ts +13 -0
  25. package/dist/render-operation.d.ts.map +1 -0
  26. package/dist/render-operation.js +89 -0
  27. package/dist/render-schema.d.ts +31 -0
  28. package/dist/render-schema.d.ts.map +1 -0
  29. package/dist/render-schema.js +98 -0
  30. package/dist/render-security.d.ts +6 -0
  31. package/dist/render-security.d.ts.map +1 -0
  32. package/dist/render-security.js +33 -0
  33. package/dist/select-document.d.ts +29 -0
  34. package/dist/select-document.d.ts.map +1 -0
  35. package/dist/select-document.js +312 -0
  36. package/package.json +16 -28
  37. package/dist/components/MarkdownReference.test.d.ts +0 -2
  38. package/dist/components/MarkdownReference.test.d.ts.map +0 -1
  39. package/dist/components/MarkdownReference.vue.d.ts +0 -9
  40. package/dist/components/MarkdownReference.vue.d.ts.map +0 -1
  41. package/dist/components/Schema.test.d.ts +0 -2
  42. package/dist/components/Schema.test.d.ts.map +0 -1
  43. package/dist/components/Schema.vue.d.ts +0 -9
  44. package/dist/components/Schema.vue.d.ts.map +0 -1
  45. package/dist/components/XmlOrJson.vue.d.ts +0 -8
  46. package/dist/components/XmlOrJson.vue.d.ts.map +0 -1
  47. package/dist/create-markdown-from-openapi.test.d.ts +0 -2
  48. package/dist/create-markdown-from-openapi.test.d.ts.map +0 -1
  49. package/dist/index.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @scalar/openapi-to-markdown
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#10233](https://github.com/scalar/scalar/pull/10233): Generate Markdown directly from a Markdown syntax tree, avoiding Vue server rendering and the generated HTML conversion pipeline. Reuse normalized schemas and parsed descriptions across pages while preserving selection, schema coercion, reference resolution, and raw HTML description sanitization.
8
+
9
+ Keep the HTML API through an on-demand Markdown conversion. Markdown and HTML spacing and escaping may differ from previous output.
10
+
11
+ ### Minor Changes
12
+
13
+ - [#10224](https://github.com/scalar/scalar/pull/10224): Add a reusable renderer that loads an OpenAPI document once and generates multiple Markdown or HTML pages from it.
14
+
15
+ ### Patch Changes
16
+
17
+ - [#10241](https://github.com/scalar/scalar/pull/10241): Simplify reference link rebuilding after OpenAPI document coercion.
18
+
19
+ ## 0.6.0
20
+
21
+ ### Minor Changes
22
+
23
+ - [#10169](https://github.com/scalar/scalar/pull/10169): Render scoped operation, tag, model, webhook, and introduction pages in Markdown and HTML while preserving referenced schemas and inherited context.
24
+
3
25
  ## 0.5.44
4
26
 
5
27
  ## 0.5.43
package/README.md CHANGED
@@ -68,13 +68,42 @@ const operationMarkdownByPointer = await createMarkdownFromOpenApi(content, {
68
68
  })
69
69
  ```
70
70
 
71
+ ### Render multiple pages
72
+
73
+ Create a reusable renderer when exporting several pages from the same API description.
74
+ It loads, upgrades, coerces, and resolves the document once. Each call uses the same selectors as
75
+ `createMarkdownFromOpenApi`, and omitting a selector renders the complete document.
76
+
77
+ ```ts
78
+ import { createOpenApiMarkdownRenderer } from '@scalar/openapi-to-markdown'
79
+
80
+ const renderer = await createOpenApiMarkdownRenderer(content)
81
+
82
+ const introduction = await renderer.render({ introduction: true })
83
+ const operation = await renderer.render({
84
+ operation: { path: '/users/{id}', method: 'get' },
85
+ })
86
+ const tag = await renderer.render({ tag: 'Users' })
87
+ const model = await renderer.render({ model: 'User' })
88
+ const webhook = await renderer.render({
89
+ webhook: { name: 'userCreated', method: 'post' },
90
+ })
91
+ ```
92
+
93
+ The factory accepts the same document objects, JSON/YAML strings, file paths, and URLs
94
+ as the one-shot functions. Source files and URLs are read during creation, including
95
+ references. Create a new renderer when the source changes. Reuse one renderer per API
96
+ description during a build, then release it when the build finishes. Renderers do not
97
+ share a global document cache. An invalid selection rejects that call without preventing
98
+ later calls on the same renderer.
99
+
71
100
  ### With Hono
72
101
 
73
102
  You use the package with any Node.js framework. Here is an example for [Hono](https://hono.dev/):
74
103
 
75
104
  ```ts
76
- import { Hono } from 'hono'
77
105
  import { createMarkdownFromOpenApi } from '@scalar/openapi-to-markdown'
106
+ import { Hono } from 'hono'
78
107
 
79
108
  // Generate Markdown from an OpenAPI document
80
109
  const markdown = await createMarkdownFromOpenApi(content)
@@ -94,43 +123,21 @@ app.get('/llms.txt', (c) => c.text(markdown))
94
123
  serve(app)
95
124
  ```
96
125
 
97
- ### Generate HTML
98
-
99
- This is not really the purpose of the package, but maybe good to know: This package actually renders HTML at first, and
100
- transforms the HTML to Markdown then.
126
+ ### Markdown rendering
101
127
 
102
- So if you'd like to have a really light-weight HTML API Reference, here you are:
128
+ The renderer constructs a Markdown syntax tree directly from the resolved API description.
129
+ It preserves Markdown descriptions, GFM tables and code blocks without rendering a Vue app
130
+ or converting the generated document through HTML. Descriptions containing raw HTML or
131
+ Scalar alerts use a separate sanitization and conversion path. Images remain excluded.
103
132
 
104
- ```ts
105
- import { Hono } from 'hono'
106
- import { createHtmlFromOpenApi } from '@scalar/openapi-to-markdown'
133
+ Schema normalization and description parsing are cached within each renderer. Recursive
134
+ schema expansion still tracks ancestors and stops at a depth of ten. Output may use tighter
135
+ list spacing and normalized Markdown escaping compared with earlier versions.
107
136
 
108
- // Generate HTML from an OpenAPI document
109
- const html = await createHtmlFromOpenApi(content)
110
-
111
- const app = new Hono()
137
+ ### HTML output
112
138
 
113
- app.get('/', (c) =>
114
- c.html(
115
- `<!doctype html>
116
- <html lang="en" data-theme="light">
117
- <head>
118
- <meta charset="UTF-8" />
119
- <title>Scalar Galaxy</title>
120
- <!-- Basic styling for semantic HTML tags (optional) -->
121
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
122
- </head>
123
- <body>
124
- <main class="container">
125
- ${html}
126
- </main>
127
- </body>
128
- </html>`,
129
- ),
130
- )
131
-
132
- serve(app)
133
- ```
139
+ `createHtmlFromOpenApi` and `renderer.renderHtml` remain available. They convert the
140
+ Markdown output to HTML on demand, without loading a Vue renderer.
134
141
 
135
142
  ## Community
136
143
 
@@ -139,3 +146,35 @@ We are API nerds. You too? Let's chat on Discord: <https://discord.gg/scalar>
139
146
  ## License
140
147
 
141
148
  The source code in this repository is licensed under [MIT](https://github.com/scalar/scalar/blob/main/LICENSE).
149
+
150
+ ## Individual reference pages
151
+
152
+ `createMarkdownFromOpenApi` and `renderer.render` accept the same selection options. Choose one selector per call:
153
+
154
+ ```ts
155
+ await createMarkdownFromOpenApi(content, { tag: 'pets' })
156
+ await createMarkdownFromOpenApi(content, { model: 'Pet' })
157
+ await createMarkdownFromOpenApi(content, {
158
+ webhook: { name: 'petCreated', method: 'post' },
159
+ })
160
+ await createMarkdownFromOpenApi(content, { introduction: true })
161
+ await createMarkdownFromOpenApi(content, {
162
+ operation: { operationId: 'getUser' },
163
+ })
164
+ ```
165
+
166
+ - **Operation:** One operation, effective parameters, servers and authentication, its tags, and referenced component schemas. Existing path/method, operation ID, and JSON pointer selectors still work. Methods are case insensitive.
167
+ - **Tag:** Tag metadata and all path operations with that exact tag, plus their context and schema dependencies. A tag used only by operations is supported. A declared tag with no operations renders its metadata. Operations with multiple tags appear once, with only the selected tag shown.
168
+ - **Model:** One component schema and its referenced schemas. Primitive, array, composed, and recursive models use the shared schema renderer.
169
+ - **Webhook:** One operation selected by its exact OpenAPI webhook name and method, including parameters, payload and responses. The name is a label, not a delivery URL.
170
+ - **Introduction:** API title, versions, description, contact, license, terms of service, servers and global authentication requirements. No operations, tags, models or webhooks.
171
+
172
+ Selected pages retain API title, versions and description. They exclude unrelated reference content. Operation servers override path servers, which override document servers. Operation security overrides document security, including `security: []` for anonymous access. Parameter overrides use the parameter name and location. Required schemas are collected after reference resolution, so dependencies remain available even when their original section is omitted.
173
+
174
+ Omitting options, or passing `{}`, renders the whole document. OpenAPI 2.0 inputs are migrated before selection: use definition names with `model`. Webhooks require OpenAPI 3.1 or later.
175
+
176
+ ### Errors and limitations
177
+
178
+ Invalid, combined, or missing selectors reject the returned promise with an error. Duplicate operation IDs are ambiguous and list matching paths and methods; use a path/method selector instead. Duplicate tag declarations are also rejected. Names are case sensitive. Operation JSON pointers must target `/paths/{path}/{method}`, with an optional leading `#` and standard `~0`/`~1` escaping.
179
+
180
+ Selection does not add support for every OpenAPI or JSON Schema keyword. Callbacks are not selectable pages. External references follow the existing workspace loader behavior. Recursive schema expansion stops on a repeated ancestor, with a depth limit of ten as a fallback. Shared dependencies have one component section, but may also appear inline where used. Authentication lists alternatives separately; schemes within one requirement must be used together.
@@ -1,18 +1,19 @@
1
- import type { HttpMethod } from '@scalar/helpers/http/http-methods';
2
- import type { OpenApiDocument } from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document';
1
+ import type { OpenApiDocument } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
2
+ import { type OpenApiRenderOptions } from './select-document.js';
3
3
  type AnyDocument = OpenApiDocument | Record<string, unknown> | string;
4
- export type { HttpMethod };
5
- export type OperationSelector = {
6
- path: string;
7
- method: HttpMethod | Uppercase<HttpMethod>;
8
- } | {
9
- operationId: string;
10
- } | {
11
- pointer: string;
4
+ /** A resolved API description that can render multiple pages without loading it again. */
5
+ export type OpenApiMarkdownRenderer = {
6
+ render: (options?: OpenApiRenderOptions) => Promise<string>;
7
+ renderHtml: (options?: OpenApiRenderOptions) => Promise<string>;
12
8
  };
13
- export type OpenApiRenderOptions = {
14
- operation?: OperationSelector;
15
- };
16
- export declare function createHtmlFromOpenApi(input: AnyDocument, options?: OpenApiRenderOptions): Promise<string>;
17
- export declare function createMarkdownFromOpenApi(content: AnyDocument, options?: OpenApiRenderOptions): Promise<string>;
9
+ /**
10
+ * Load and resolve an API description once, then render any number of selections.
11
+ * Each renderer owns its document; create a new renderer to pick up source changes.
12
+ */
13
+ export declare const createOpenApiMarkdownRenderer: (input: AnyDocument) => Promise<OpenApiMarkdownRenderer>;
14
+ /** Generate Markdown from an API description, optionally scoped to a single page. */
15
+ export declare const createMarkdownFromOpenApi: (input: AnyDocument, options?: OpenApiRenderOptions) => Promise<string>;
16
+ /** Generate HTML through the optional Markdown conversion path. */
17
+ export declare const createHtmlFromOpenApi: (input: AnyDocument, options?: OpenApiRenderOptions) => Promise<string>;
18
+ export {};
18
19
  //# sourceMappingURL=create-markdown-from-openapi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-markdown-from-openapi.d.ts","sourceRoot":"","sources":["../src/create-markdown-from-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAMnE,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,8DAA8D,CAAA;AAanH,KAAK,WAAW,GAAG,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAA;AACrE,YAAY,EAAE,UAAU,EAAE,CAAA;AAC1B,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,CAAA;CAC3C,GACD;IACE,WAAW,EAAE,MAAM,CAAA;CACpB,GACD;IACE,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AACL,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,CAAC,EAAE,iBAAiB,CAAA;CAC9B,CAAA;AAwMD,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,oBAAoB,mBA8C7F;AAED,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,oBAAoB,mBAEnG"}
1
+ {"version":3,"file":"create-markdown-from-openapi.d.ts","sourceRoot":"","sources":["../src/create-markdown-from-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAA;AAInG,OAAO,EAAE,KAAK,oBAAoB,EAAkB,MAAM,mBAAmB,CAAA;AAE7E,KAAK,WAAW,GAAG,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAA;AACrE,0FAA0F;AAC1F,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3D,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAChE,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GAAU,OAAO,WAAW,KAAG,OAAO,CAAC,uBAAuB,CAoBvG,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,yBAAyB,GACpC,OAAO,WAAW,EAClB,UAAU,oBAAoB,KAC7B,OAAO,CAAC,MAAM,CAGhB,CAAA;AAED,mEAAmE;AACnE,eAAO,MAAM,qBAAqB,GAAU,OAAO,WAAW,EAAE,UAAU,oBAAoB,KAAG,OAAO,CAAC,MAAM,CAG9G,CAAA"}
@@ -0,0 +1,35 @@
1
+ import { loadDocument } from './load-document.js';
2
+ import { createDocumentRenderer } from './render-document.js';
3
+ import { selectDocument } from './select-document.js';
4
+ /**
5
+ * Load and resolve an API description once, then render any number of selections.
6
+ * Each renderer owns its document; create a new renderer to pick up source changes.
7
+ */
8
+ export const createOpenApiMarkdownRenderer = async (input) => {
9
+ const content = await loadDocument(input);
10
+ const renderDocument = createDocumentRenderer();
11
+ const render = async (options) => await renderDocument(selectDocument(content, options));
12
+ return {
13
+ render,
14
+ renderHtml: async (options) => {
15
+ const [{ unified }, { default: remarkParse }, { default: remarkRehype }, { default: rehypeStringify }] = await Promise.all([
16
+ import('unified'),
17
+ import('remark-parse'),
18
+ import('remark-rehype'),
19
+ import('rehype-stringify'),
20
+ ]);
21
+ const processor = unified().use(remarkParse).use(remarkRehype).use(rehypeStringify);
22
+ return processor.processSync(await render(options)).toString();
23
+ },
24
+ };
25
+ };
26
+ /** Generate Markdown from an API description, optionally scoped to a single page. */
27
+ export const createMarkdownFromOpenApi = async (input, options) => {
28
+ const renderer = await createOpenApiMarkdownRenderer(input);
29
+ return renderer.render(options);
30
+ };
31
+ /** Generate HTML through the optional Markdown conversion path. */
32
+ export const createHtmlFromOpenApi = async (input, options) => {
33
+ const renderer = await createOpenApiMarkdownRenderer(input);
34
+ return renderer.renderHtml(options);
35
+ };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { createHtmlFromOpenApi, createMarkdownFromOpenApi, } from './create-markdown-from-openapi';
2
- export type { HttpMethod, OpenApiRenderOptions, OperationSelector, } from './create-markdown-from-openapi';
1
+ export type { HttpMethod } from '@scalar/helpers/http/http-methods';
2
+ export type { OpenApiMarkdownRenderer } from './create-markdown-from-openapi.js';
3
+ export { createHtmlFromOpenApi, createMarkdownFromOpenApi, createOpenApiMarkdownRenderer, } from './create-markdown-from-openapi.js';
4
+ export type { OpenApiRenderOptions, OperationSelector } from './select-document.js';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,gCAAgC,CAAA;AACvC,YAAY,EACV,UAAU,EACV,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,gCAAgC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAEnE,YAAY,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAA;AAC7E,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,6BAA6B,GAC9B,MAAM,gCAAgC,CAAA;AACvC,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA"}