@scalar/openapi-to-markdown 1.4.0 → 1.5.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.
- package/CHANGELOG.md +26 -0
- package/README.md +66 -5
- package/dist/document-anchors.d.ts +8 -0
- package/dist/document-anchors.d.ts.map +1 -0
- package/dist/document-anchors.js +25 -0
- package/dist/document-examples.d.ts +14 -0
- package/dist/document-examples.d.ts.map +1 -0
- package/dist/document-examples.js +54 -0
- package/dist/get-markdown-examples.d.ts +2 -0
- package/dist/get-markdown-examples.d.ts.map +1 -1
- package/dist/get-markdown-examples.js +11 -12
- package/dist/markdown-nodes.d.ts +22 -1
- package/dist/markdown-nodes.d.ts.map +1 -1
- package/dist/markdown-nodes.js +4 -0
- package/dist/parse-description.d.ts +2 -0
- package/dist/parse-description.d.ts.map +1 -1
- package/dist/parse-description.js +28 -1
- package/dist/render-document.d.ts +2 -2
- package/dist/render-document.d.ts.map +1 -1
- package/dist/render-document.js +217 -84
- package/dist/render-examples.d.ts +9 -1
- package/dist/render-examples.d.ts.map +1 -1
- package/dist/render-examples.js +26 -8
- package/dist/render-operation-details.d.ts +3 -2
- package/dist/render-operation-details.d.ts.map +1 -1
- package/dist/render-operation-details.js +8 -5
- package/dist/render-operation.d.ts +16 -1
- package/dist/render-operation.d.ts.map +1 -1
- package/dist/render-operation.js +168 -42
- package/dist/render-schema.d.ts +30 -3
- package/dist/render-schema.d.ts.map +1 -1
- package/dist/render-schema.js +474 -111
- package/dist/render-security.d.ts +7 -3
- package/dist/render-security.d.ts.map +1 -1
- package/dist/render-security.js +61 -12
- package/dist/select-document.d.ts +5 -0
- package/dist/select-document.d.ts.map +1 -1
- package/dist/select-document.js +3 -2
- package/package.json +16 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @scalar/openapi-to-markdown
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#10425](https://github.com/scalar/scalar/pull/10425): Make single-page Markdown easier to read, especially for AI agents reading `llms.txt` exports.
|
|
8
|
+
- An operation, webhook or model page now starts with that item as its `#` title and uses `##` sections, without the API's document header. An empty API version is never printed.
|
|
9
|
+
- Schema property descriptions keep their Markdown: paragraphs, links, inline code and fenced code blocks are no longer flattened into one escaped line. A parameter description is no longer repeated by its schema.
|
|
10
|
+
- Each property is one line with its name, type and annotations. Nullable unions read as `string | null`, single-branch `allOf` wrappers as the wrapped type, unions of plain types as one type, `const` and `enum` schemas without a `type` as their inferred JSON type, and arrays of simple items as `array of …`. Composition branches are listed one per item, with discriminator values beside their branch.
|
|
11
|
+
- Parameters are grouped by location. Responses that return the same schema and media type are grouped into one entry, such as a list of error statuses.
|
|
12
|
+
- In linked mode, references are shown as a link in the property's type. Primitive, enum and `const` schemas and aliases of them are written in place instead of linked; set `schemaReferences.inlinePrimitives: false` to link them. Generated examples are left out without a placeholder, while a model page generates an example of its own schema with linked schemas as empty stubs.
|
|
13
|
+
- The Authentication section is left out when the API description declares no security requirements, and only an explicit `security: []` says that no authentication is required. Security schemes are summarized on one line, for example "API key in header `X-Api-Key`", instead of printed as JSON.
|
|
14
|
+
- Selected pages retain empty server overrides as `/` and include server variable choices and descriptions. Model pages retain authored examples for primitive and array schemas as well as objects.
|
|
15
|
+
|
|
16
|
+
- [#10425](https://github.com/scalar/scalar/pull/10425): Give shared schemas canonical definitions and internal links in whole-document Markdown exports, retaining primitive types inline and constraints beside references.
|
|
17
|
+
|
|
18
|
+
Deduplicate generated examples within each schema and request/response/media-type context, label them as generated, and retain authored examples at their original locations.
|
|
19
|
+
|
|
20
|
+
Explain inherited servers and authentication once, with links from operations, while retaining path and operation overrides, server variables, and explicit anonymous access.
|
|
21
|
+
|
|
22
|
+
Add a compact contents index linking to operations, webhooks, and schemas through unique explicit anchors.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- [#10425](https://github.com/scalar/scalar/pull/10425): Add a browser playground for previewing and downloading per-page, linked, and whole-document Markdown exports from Galaxy, Stripe, GitHub, and Cloudflare examples.
|
|
27
|
+
- [#10425](https://github.com/scalar/scalar/pull/10425): Shorten the `## Schemas` section for schemas a page already expanded. Such a model is now a single line, for example `` `Customer` — shown above. ``, instead of a heading, type, "shown above" note and generated example. The line keeps the model's title and any description that a reference sibling replaced where the schema was expanded. A selected model, leaf schemas, models the page has not expanded, and object models with authored examples keep their full sections. A Stripe operation page such as `GET /v1/customers/{customer}` drops from about 2.5 MB to about 1.3 MB, with 819 of its 918 model sections reduced to one line. Everything above `## Schemas` is unchanged.
|
|
28
|
+
|
|
3
29
|
## 1.4.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -130,6 +130,15 @@ It preserves Markdown descriptions, GFM tables and code blocks without rendering
|
|
|
130
130
|
or converting the generated document through HTML. Descriptions containing raw HTML or
|
|
131
131
|
Scalar alerts use a separate sanitization and conversion path. Images remain excluded.
|
|
132
132
|
|
|
133
|
+
Each schema property is one line with its name, type and annotations, for example
|
|
134
|
+
`` **`archived_at` (required)**: `string | null`, format: `date-time` ``, followed by
|
|
135
|
+
its description with its paragraphs, links and code blocks intact. Nullable unions,
|
|
136
|
+
single-branch `allOf` wrappers and unions of plain types are labelled as one type.
|
|
137
|
+
Parameters are listed by location. Responses that return the same schema and media
|
|
138
|
+
type share one entry, such as a list of error statuses. Security schemes are
|
|
139
|
+
summarized on one line, and an Authentication section appears only when the API
|
|
140
|
+
description declares requirements.
|
|
141
|
+
|
|
133
142
|
Schema normalization and description parsing are cached within each renderer. Recursive
|
|
134
143
|
schema expansion still tracks ancestors and stops at a depth of ten. Output may use tighter
|
|
135
144
|
list spacing and normalized Markdown escaping compared with earlier versions.
|
|
@@ -167,15 +176,35 @@ await createMarkdownFromOpenApi(content, {
|
|
|
167
176
|
- **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.
|
|
168
177
|
- **Introduction:** API title, versions, description, contact, license, terms of service, servers and global authentication requirements. No operations, tags, models or webhooks.
|
|
169
178
|
|
|
170
|
-
|
|
179
|
+
An operation, webhook or model page starts with that item as its `#` title, with its sections (parameters, request body, responses) as `##` headings. It leaves out the API title, versions and description, which belong on the introduction page. Tag and whole-document exports keep the document header. Selected pages 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.
|
|
171
180
|
|
|
172
181
|
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.
|
|
173
182
|
|
|
183
|
+
Whole-document exports include a compact contents index after the introduction, linking
|
|
184
|
+
to operations by method and path, webhooks, and component schemas. Empty groups are omitted.
|
|
185
|
+
|
|
186
|
+
Whole-document exports render each named structured schema once under `## Schemas`.
|
|
187
|
+
Operations and nested properties link to that section, while simple primitive references
|
|
188
|
+
remain inline. Generated explicit anchors keep links stable even when schema titles repeat.
|
|
189
|
+
The export is self-contained; a supplied `schemaReferences.resolveUrl` callback still controls
|
|
190
|
+
reference URLs when linked mode is explicitly requested.
|
|
191
|
+
|
|
192
|
+
Within a whole-document export, repeated generated examples link to their first
|
|
193
|
+
occurrence. Request, response, and media-type contexts remain distinct. Synthesized
|
|
194
|
+
values are labeled **Generated example**, and authored examples remain at every
|
|
195
|
+
usage, including all values in a schema's `examples` array.
|
|
196
|
+
|
|
197
|
+
Global servers and authentication are documented once. Operations link to inherited
|
|
198
|
+
defaults and show their own overrides in full. Path-level servers are explained at
|
|
199
|
+
their first use and linked thereafter, including variable defaults, choices, and
|
|
200
|
+
descriptions. Explicit empty server overrides use `/`; explicit `security: []` remains
|
|
201
|
+
anonymous. Absent security requirements do not imply anonymous access.
|
|
202
|
+
|
|
174
203
|
### Errors and limitations
|
|
175
204
|
|
|
176
205
|
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.
|
|
177
206
|
|
|
178
|
-
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.
|
|
207
|
+
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. Selected-page exports expand shared dependencies once, where they are first used. Whole-document exports instead link structured dependencies to their full definitions under `## Schemas`. For selected-page exports, the `## Schemas` section lists a component the page already expanded on one line, with its title and any description that a reference sibling replaced, and keeps a full section for the selected model, for leaf schemas, for models the page has not expanded yet, and for object models with authored examples. Authentication lists alternatives separately; schemes within one requirement must be used together.
|
|
179
208
|
|
|
180
209
|
### Copying Markdown in the browser
|
|
181
210
|
|
|
@@ -237,11 +266,43 @@ method: 'post' } }`, the one-shot `createMarkdownFromOpenApi`, or the browser en
|
|
|
237
266
|
point. The browser entry point still requires a workspace-resolved document.
|
|
238
267
|
Options and URL callbacks are isolated per render, including concurrent renders.
|
|
239
268
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
269
|
+
References to primitive, enum and `const` schemas, and to aliases of them, are
|
|
270
|
+
written in place, because their whole definition fits on one line. Pass
|
|
271
|
+
`inlinePrimitives: false` to link them too. Object, array and composition schemas
|
|
272
|
+
are always linked.
|
|
273
|
+
|
|
274
|
+
Linked mode uses authored media-type and schema examples and does not generate
|
|
275
|
+
examples on operation pages, which would expand every linked schema. A model page
|
|
276
|
+
generates an example for its own schema, with linked schemas left as empty
|
|
277
|
+
stubs. Authored examples are not capped in size. Inline schema content and authored
|
|
243
278
|
text also remain proportional to the source; this mode bounds traversal across
|
|
244
279
|
shared references, not the byte size of arbitrary authored content. Root
|
|
245
280
|
composition branches that contain references link to those schemas rather than
|
|
246
281
|
flattening their constraints. Full-document exports still include every model
|
|
247
282
|
section; use a page selector for individual exports.
|
|
283
|
+
|
|
284
|
+
## Browser playground
|
|
285
|
+
|
|
286
|
+
From the repository root, run:
|
|
287
|
+
|
|
288
|
+
```sh
|
|
289
|
+
pnpm --filter @scalar/openapi-to-markdown dev
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Open `http://localhost:3000` (or the address printed by Vite). Choose Galaxy,
|
|
293
|
+
Stripe, GitHub, or Cloudflare, then preview a **Per-page**, **Linked page**, or
|
|
294
|
+
**Full export**. Page selection includes operations by method/path, models,
|
|
295
|
+
webhooks, tags, and the introduction. Search the page list to find an item.
|
|
296
|
+
|
|
297
|
+
The **Preview** and **Markdown** views show the rendered document and its exact
|
|
298
|
+
source. Copy or download the Markdown, follow internal links in whole exports,
|
|
299
|
+
or follow linked schema URLs to open that model in the playground. The additional
|
|
300
|
+
**Link shared schemas** control also allows a linked full export.
|
|
301
|
+
|
|
302
|
+
Galaxy is included locally through `@scalar/galaxy`; the other examples fetch
|
|
303
|
+
current documents from their official GitHub repositories and require internet
|
|
304
|
+
access on first load. Loading and exporting happen on the development server.
|
|
305
|
+
It retains one resolved document at a time; switching examples releases the previous
|
|
306
|
+
renderer. Large exports can take longer. The reported duration measures Markdown
|
|
307
|
+
export, excluding document loading and HTML preview conversion. `/llms.txt` still
|
|
308
|
+
serves the complete Galaxy Markdown export.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Html } from 'mdast';
|
|
2
|
+
/** One export owns its destinations, so repeated titles and concurrent renders cannot collide. */
|
|
3
|
+
export declare const createDocumentAnchors: () => {
|
|
4
|
+
get: (kind: string, identity: string, label?: string) => string;
|
|
5
|
+
};
|
|
6
|
+
/** Explicit destinations do not depend on a Markdown viewer's heading slug rules. */
|
|
7
|
+
export declare const anchor: (id: string) => Html;
|
|
8
|
+
//# sourceMappingURL=document-anchors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-anchors.d.ts","sourceRoot":"","sources":["../src/document-anchors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEjC,kGAAkG;AAClG,eAAO,MAAM,qBAAqB,QAAO;IACvC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;CAmBhE,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,MAAM,GAAI,IAAI,MAAM,KAAG,IAAuD,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { slugger } from '@scalar/helpers/string/slugger';
|
|
2
|
+
/** One export owns its destinations, so repeated titles and concurrent renders cannot collide. */
|
|
3
|
+
export const createDocumentAnchors = () => {
|
|
4
|
+
const slugs = slugger();
|
|
5
|
+
const ids = new Map();
|
|
6
|
+
const used = new Set();
|
|
7
|
+
return {
|
|
8
|
+
get: (kind, identity, label = identity) => {
|
|
9
|
+
const key = JSON.stringify([kind, identity]);
|
|
10
|
+
const previous = ids.get(key);
|
|
11
|
+
if (previous !== undefined)
|
|
12
|
+
return previous;
|
|
13
|
+
const base = `scalar-${kind}-${label}`;
|
|
14
|
+
let id = slugs.slug(base);
|
|
15
|
+
// A literal suffix in a later name can collide with the slugger's numeric suffix.
|
|
16
|
+
while (used.has(id))
|
|
17
|
+
id = slugs.slug(base);
|
|
18
|
+
used.add(id);
|
|
19
|
+
ids.set(key, id);
|
|
20
|
+
return id;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
/** Explicit destinations do not depend on a Markdown viewer's heading slug rules. */
|
|
25
|
+
export const anchor = (id) => ({ type: 'html', value: `<a id="${id}"></a>` });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Code } from 'mdast';
|
|
2
|
+
import type { createDocumentAnchors } from './document-anchors.js';
|
|
3
|
+
import { type ExampleSource, getMarkdownExamples } from './get-markdown-examples.js';
|
|
4
|
+
/** Generation and destinations are shared only within one whole-document export. */
|
|
5
|
+
export type DocumentExamples = {
|
|
6
|
+
get: typeof getMarkdownExamples;
|
|
7
|
+
show: (source: ExampleSource, scope: string, code: Code) => {
|
|
8
|
+
id: string;
|
|
9
|
+
previous: boolean;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
/** Reuse generation by schema and context; deduplicate only after comparing the serialized example. */
|
|
13
|
+
export declare const createDocumentExamples: (anchors: ReturnType<typeof createDocumentAnchors>) => DocumentExamples;
|
|
14
|
+
//# sourceMappingURL=document-examples.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-examples.d.ts","sourceRoot":"","sources":["../src/document-examples.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,KAAK,aAAa,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAEjF,oFAAoF;AACpF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,OAAO,mBAAmB,CAAA;IAC/B,IAAI,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAA;CAC9F,CAAA;AAaD,uGAAuG;AACvG,eAAO,MAAM,sBAAsB,GAAI,SAAS,UAAU,CAAC,OAAO,qBAAqB,CAAC,KAAG,gBAqC1F,CAAA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
|
+
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
3
|
+
import { getMarkdownExamples } from './get-markdown-examples.js';
|
|
4
|
+
const bookkeeping = new Set(['$ref', '$ref-value', '$global', '$status', '__scalar_']);
|
|
5
|
+
/** Sibling constraints make a reference a different example source from its target. */
|
|
6
|
+
const identity = (source) => {
|
|
7
|
+
if (!isObject(source.schema))
|
|
8
|
+
return undefined;
|
|
9
|
+
const schema = source.schema;
|
|
10
|
+
const target = '$ref' in schema && Object.keys(schema).every((key) => bookkeeping.has(key)) ? getResolvedRef(schema) : schema;
|
|
11
|
+
return isObject(target) ? target : schema;
|
|
12
|
+
};
|
|
13
|
+
/** Reuse generation by schema and context; deduplicate only after comparing the serialized example. */
|
|
14
|
+
export const createDocumentExamples = (anchors) => {
|
|
15
|
+
const cache = new Map();
|
|
16
|
+
const shown = new WeakMap();
|
|
17
|
+
let cachedCount = 0;
|
|
18
|
+
let nextId = 0;
|
|
19
|
+
return {
|
|
20
|
+
get: (...args) => {
|
|
21
|
+
const [source, ...settings] = args;
|
|
22
|
+
const schema = identity(source);
|
|
23
|
+
if (!schema || source.example !== undefined || Object.keys(source.examples ?? {}).length)
|
|
24
|
+
return getMarkdownExamples(...args);
|
|
25
|
+
const scope = JSON.stringify(settings);
|
|
26
|
+
const previous = cache.get(schema)?.get(scope);
|
|
27
|
+
if (previous)
|
|
28
|
+
return previous;
|
|
29
|
+
// Large APIs must not retain every generated object until the export finishes.
|
|
30
|
+
if (cachedCount >= 256) {
|
|
31
|
+
cache.clear();
|
|
32
|
+
cachedCount = 0;
|
|
33
|
+
}
|
|
34
|
+
const entries = cache.get(schema) ?? new Map();
|
|
35
|
+
const examples = getMarkdownExamples(...args);
|
|
36
|
+
entries.set(scope, examples);
|
|
37
|
+
cache.set(schema, entries);
|
|
38
|
+
cachedCount++;
|
|
39
|
+
return examples;
|
|
40
|
+
},
|
|
41
|
+
show: (source, scope, code) => {
|
|
42
|
+
const schema = identity(source);
|
|
43
|
+
const entries = schema ? (shown.get(schema) ?? new Map()) : new Map();
|
|
44
|
+
const previous = entries.get(scope);
|
|
45
|
+
if (previous?.value === code.value)
|
|
46
|
+
return { id: previous.id, previous: true };
|
|
47
|
+
const id = anchors.get('example', String(++nextId));
|
|
48
|
+
entries.set(scope, { id, value: code.value });
|
|
49
|
+
if (schema)
|
|
50
|
+
shown.set(schema, entries);
|
|
51
|
+
return { id, previous: false };
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-markdown-examples.d.ts","sourceRoot":"","sources":["../src/get-markdown-examples.ts"],"names":[],"mappings":"AAMA,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,gFAAgF;AAChF,KAAK,eAAe,GAAG;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"get-markdown-examples.d.ts","sourceRoot":"","sources":["../src/get-markdown-examples.ts"],"names":[],"mappings":"AAMA,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,gFAAgF;AAChF,KAAK,eAAe,GAAG;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,GAAG,CACA;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAClB;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CACpB,CAAA;AAYD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,OAAO,EAAE,cAAoC,KAAG,MA2CjG,CAAA;AAED,yFAAyF;AACzF,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,aAAa,EACrB,WAAW,MAAM,EACjB,OAAO,MAAM,GAAG,OAAO,EACvB,uBAAwB,EAExB,6BAAqC,EACrC,gBAAc,KACb,eAAe,EAyCjB,CAAA"}
|
|
@@ -95,18 +95,15 @@ schemaOpenapiVersion = openapiVersion, linked = false) => {
|
|
|
95
95
|
return [];
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
|
-
const schema =
|
|
99
|
-
? getResolvedRef(source.schema, mergeSiblingReferences)
|
|
100
|
-
: getResolvedRef(source.schema);
|
|
98
|
+
const schema = getResolvedRef(source.schema, mergeSiblingReferences);
|
|
101
99
|
if (!isObject(schema))
|
|
102
100
|
return [];
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return [
|
|
109
|
-
}
|
|
101
|
+
if (schema.example !== undefined)
|
|
102
|
+
return [{ value: schema.example }];
|
|
103
|
+
if (Array.isArray(schema.examples) && schema.examples.length)
|
|
104
|
+
return schema.examples.map((value) => ({ value }));
|
|
105
|
+
if (linked)
|
|
106
|
+
return [];
|
|
110
107
|
if (countGeneratedExampleValues(source.schema) > MAX_GENERATED_EXAMPLE_VALUES)
|
|
111
108
|
return [{ omitted: true }];
|
|
112
109
|
if (isXmlMediaType(mediaType)) {
|
|
@@ -115,11 +112,13 @@ schemaOpenapiVersion = openapiVersion, linked = false) => {
|
|
|
115
112
|
openapiVersion: schemaOpenapiVersion,
|
|
116
113
|
});
|
|
117
114
|
return [
|
|
118
|
-
result.xml === undefined
|
|
115
|
+
result.xml === undefined
|
|
116
|
+
? { error: 'Unable to generate an XML example.', generated: true }
|
|
117
|
+
: { serializedValue: result.xml, generated: true },
|
|
119
118
|
];
|
|
120
119
|
}
|
|
121
120
|
const value = getExampleFromSchema(getResolvedRef(source.schema, mergeSiblingReferences), {
|
|
122
121
|
mode,
|
|
123
122
|
});
|
|
124
|
-
return value === undefined ? [] : [{ value }];
|
|
123
|
+
return value === undefined ? [] : [{ value, generated: true }];
|
|
125
124
|
};
|
package/dist/markdown-nodes.d.ts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
import type { Heading, InlineCode, Link, List, ListItem, Paragraph, PhrasingContent, Strong, Text } from 'mdast';
|
|
1
|
+
import type { Emphasis, Heading, InlineCode, Link, List, ListItem, Node, Paragraph, PhrasingContent, Strong, Text } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* A description that still has to be parsed as Markdown. Schema rendering is synchronous and
|
|
4
|
+
* description parsing is not, so the page renderer replaces these before it serializes a page.
|
|
5
|
+
*/
|
|
6
|
+
type DescriptionPlaceholder = Node & {
|
|
7
|
+
type: 'descriptionPlaceholder';
|
|
8
|
+
value: string;
|
|
9
|
+
};
|
|
10
|
+
declare module 'mdast' {
|
|
11
|
+
interface BlockContentMap {
|
|
12
|
+
descriptionPlaceholder: DescriptionPlaceholder;
|
|
13
|
+
}
|
|
14
|
+
interface RootContentMap {
|
|
15
|
+
descriptionPlaceholder: DescriptionPlaceholder;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
2
18
|
/** Collapse HTML-style inline whitespace; the serializer escapes generated text as Markdown. */
|
|
3
19
|
export declare const text: (value: unknown) => Text;
|
|
4
20
|
/** Inline code fences are chosen by the serializer. */
|
|
@@ -7,6 +23,8 @@ export declare const inlineCode: (value: unknown) => InlineCode;
|
|
|
7
23
|
export declare const paragraph: (...children: PhrasingContent[]) => Paragraph;
|
|
8
24
|
/** Construct an emphasized label. */
|
|
9
25
|
export declare const strong: (...children: PhrasingContent[]) => Strong;
|
|
26
|
+
/** Construct emphasized text, for notes about the output itself. */
|
|
27
|
+
export declare const emphasis: (...children: PhrasingContent[]) => Emphasis;
|
|
10
28
|
/** Construct a section heading. */
|
|
11
29
|
export declare const heading: (depth: Heading["depth"], ...children: PhrasingContent[]) => Heading;
|
|
12
30
|
/** Construct a list item that may contain nested blocks. */
|
|
@@ -22,4 +40,7 @@ export declare const safeUrl: (url: string) => string;
|
|
|
22
40
|
export declare const link: (url: string, label: string) => Link;
|
|
23
41
|
/** Render a metadata label and value with the established nonbreaking separator. */
|
|
24
42
|
export declare const field: (label: string, value: PhrasingContent) => ListItem;
|
|
43
|
+
/** Defer a description, so it keeps its paragraphs, links and code blocks once parsed. */
|
|
44
|
+
export declare const describe: (value: unknown) => DescriptionPlaceholder[];
|
|
45
|
+
export {};
|
|
25
46
|
//# sourceMappingURL=markdown-nodes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-nodes.d.ts","sourceRoot":"","sources":["../src/markdown-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"markdown-nodes.d.ts","sourceRoot":"","sources":["../src/markdown-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,OAAO,EACP,UAAU,EACV,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,eAAe,EACf,MAAM,EACN,IAAI,EACL,MAAM,OAAO,CAAA;AAEd;;;GAGG;AACH,KAAK,sBAAsB,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtF,OAAO,QAAQ,OAAO,CAAC;IACrB,UAAU,eAAe;QACvB,sBAAsB,EAAE,sBAAsB,CAAA;KAC/C;IACD,UAAU,cAAc;QACtB,sBAAsB,EAAE,sBAAsB,CAAA;KAC/C;CACF;AAED,gGAAgG;AAChG,eAAO,MAAM,IAAI,GAAI,OAAO,OAAO,KAAG,IAGpC,CAAA;AACF,uDAAuD;AACvD,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,UAAkE,CAAA;AAC9G,iDAAiD;AACjD,eAAO,MAAM,SAAS,GAAI,GAAG,UAAU,eAAe,EAAE,KAAG,SAA8C,CAAA;AACzG,qCAAqC;AACrC,eAAO,MAAM,MAAM,GAAI,GAAG,UAAU,eAAe,EAAE,KAAG,MAAwC,CAAA;AAChG,oEAAoE;AACpE,eAAO,MAAM,QAAQ,GAAI,GAAG,UAAU,eAAe,EAAE,KAAG,QAA4C,CAAA;AACtG,mCAAmC;AACnC,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,UAAU,eAAe,EAAE,KAAG,OAIhF,CAAA;AACF,4DAA4D;AAC5D,eAAO,MAAM,IAAI,GAAI,GAAG,UAAU,QAAQ,CAAC,UAAU,CAAC,KAAG,QAA2D,CAAA;AACpH,mCAAmC;AACnC,eAAO,MAAM,IAAI,GAAI,UAAU,QAAQ,EAAE,KAAG,IAAmE,CAAA;AAC/G;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,MAIrC,CAAA;AACD,8EAA8E;AAC9E,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,KAAG,IAAsE,CAAA;AACxH,oFAAoF;AACpF,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,EAAE,OAAO,eAAe,KAAG,QACK,CAAA;AACnE,0FAA0F;AAC1F,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,KAAG,sBAAsB,EAC8B,CAAA"}
|
package/dist/markdown-nodes.js
CHANGED
|
@@ -9,6 +9,8 @@ export const inlineCode = (value) => ({ type: 'inlineCode', value: String(value
|
|
|
9
9
|
export const paragraph = (...children) => ({ type: 'paragraph', children });
|
|
10
10
|
/** Construct an emphasized label. */
|
|
11
11
|
export const strong = (...children) => ({ type: 'strong', children });
|
|
12
|
+
/** Construct emphasized text, for notes about the output itself. */
|
|
13
|
+
export const emphasis = (...children) => ({ type: 'emphasis', children });
|
|
12
14
|
/** Construct a section heading. */
|
|
13
15
|
export const heading = (depth, ...children) => ({
|
|
14
16
|
type: 'heading',
|
|
@@ -32,3 +34,5 @@ export const safeUrl = (url) => {
|
|
|
32
34
|
export const link = (url, label) => ({ type: 'link', url: safeUrl(url), children: [text(label)] });
|
|
33
35
|
/** Render a metadata label and value with the established nonbreaking separator. */
|
|
34
36
|
export const field = (label, value) => item(paragraph(strong(text(`${label}:`)), text('\u00a0'), value));
|
|
37
|
+
/** Defer a description, so it keeps its paragraphs, links and code blocks once parsed. */
|
|
38
|
+
export const describe = (value) => typeof value === 'string' && value.trim() ? [{ type: 'descriptionPlaceholder', value }] : [];
|
|
@@ -3,4 +3,6 @@ import type { RootContent } from 'mdast';
|
|
|
3
3
|
export type DescriptionParser = (value?: string) => Promise<RootContent[]>;
|
|
4
4
|
/** Share parsing across pages while giving every rendered document its own reference namespace. */
|
|
5
5
|
export declare const createDescriptionParser: () => (() => DescriptionParser);
|
|
6
|
+
/** Replace every deferred description in the given nodes with its parsed Markdown blocks. */
|
|
7
|
+
export declare const expandDescriptions: (nodes: RootContent[], description: DescriptionParser) => Promise<void>;
|
|
6
8
|
//# sourceMappingURL=parse-description.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-description.d.ts","sourceRoot":"","sources":["../src/parse-description.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"parse-description.d.ts","sourceRoot":"","sources":["../src/parse-description.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,WAAW,EAAE,MAAM,OAAO,CAAA;AAkF7D,0FAA0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;AAE1E,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,QAAO,CAAC,MAAM,iBAAiB,CA4ClE,CAAA;AAED,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,GAAU,OAAO,WAAW,EAAE,EAAE,aAAa,iBAAiB,KAAG,OAAO,CAAC,IAAI,CAe3G,CAAA"}
|
|
@@ -68,10 +68,18 @@ const pruneDefinitions = (tree) => {
|
|
|
68
68
|
return true;
|
|
69
69
|
});
|
|
70
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* A single line without Markdown punctuation, entities, autolinks or block markers parses to one
|
|
73
|
+
* paragraph containing the same text, so it does not need the parser.
|
|
74
|
+
*/
|
|
75
|
+
const isPlainText = (value) => !/[\n\r\t\\`*_[\]<>#|~!&=]|:\/\/|www\.|^\s*(?:[-+]|\d+[.)])|^ {4}/.test(value) && value.trim().length > 0;
|
|
71
76
|
/** Share parsing across pages while giving every rendered document its own reference namespace. */
|
|
72
77
|
export const createDescriptionParser = () => {
|
|
73
78
|
const cache = new Map();
|
|
74
79
|
const parse = async (value) => {
|
|
80
|
+
// Most schema descriptions are one plain sentence. Parsing them costs more than the page's other text.
|
|
81
|
+
if (isPlainText(value))
|
|
82
|
+
return [{ type: 'paragraph', children: [{ type: 'text', value: value.trim() }] }];
|
|
75
83
|
const parsed = parser.parse(value);
|
|
76
84
|
// Raw HTML and GitHub alerts retain Scalar's existing conversion behavior.
|
|
77
85
|
const tree = containsHtml(parsed) || /^\s*>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|SUCCESS)\]/im.test(value)
|
|
@@ -103,7 +111,7 @@ export const createDescriptionParser = () => {
|
|
|
103
111
|
const prefix = `description-${state.nextId++}-`;
|
|
104
112
|
const cached = cache.get(value) ?? parse(value);
|
|
105
113
|
if (!cache.has(value)) {
|
|
106
|
-
if (cache.size >=
|
|
114
|
+
if (cache.size >= 8192)
|
|
107
115
|
cache.clear();
|
|
108
116
|
cache.set(value, cached);
|
|
109
117
|
}
|
|
@@ -114,3 +122,22 @@ export const createDescriptionParser = () => {
|
|
|
114
122
|
};
|
|
115
123
|
};
|
|
116
124
|
};
|
|
125
|
+
/** Replace every deferred description in the given nodes with its parsed Markdown blocks. */
|
|
126
|
+
export const expandDescriptions = async (nodes, description) => {
|
|
127
|
+
const found = [];
|
|
128
|
+
const collect = (siblings) => {
|
|
129
|
+
for (const node of siblings) {
|
|
130
|
+
if (node.type === 'descriptionPlaceholder')
|
|
131
|
+
found.push({ siblings, node });
|
|
132
|
+
else if ('children' in node)
|
|
133
|
+
collect(node.children);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
collect(nodes);
|
|
137
|
+
const parsed = await Promise.all(found.map(({ node }) => description(node.value)));
|
|
138
|
+
// Replace from the end, so earlier positions in a shared sibling list stay valid.
|
|
139
|
+
for (let index = found.length - 1; index >= 0; index--) {
|
|
140
|
+
const { siblings, node } = found[index];
|
|
141
|
+
siblings.splice(siblings.indexOf(node), 1, ...parsed[index]);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OpenApiDocument } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
|
|
2
|
-
import type {
|
|
2
|
+
import type { OpenApiRenderOptions } from './select-document.js';
|
|
3
3
|
/** Build Markdown directly, retaining caches only for this immutable document snapshot. */
|
|
4
|
-
export declare const createDocumentRenderer: () => ((document: OpenApiDocument, options?:
|
|
4
|
+
export declare const createDocumentRenderer: () => ((document: OpenApiDocument, options?: OpenApiRenderOptions) => Promise<string>);
|
|
5
5
|
//# sourceMappingURL=render-document.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-document.d.ts","sourceRoot":"","sources":["../src/render-document.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render-document.d.ts","sourceRoot":"","sources":["../src/render-document.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,8DAA8D,CAAA;AAcrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AA+C7D,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,QAAO,CAAC,CACzC,QAAQ,EAAE,eAAe,EACzB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,MAAM,CAAC,CAsPnB,CAAA"}
|