@scalar/openapi-to-markdown 1.5.2 → 1.5.4

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # @scalar/openapi-to-markdown
2
2
 
3
+ ## 1.5.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#10559](https://github.com/scalar/scalar/pull/10559): Clean up Biome warnings in package code and tests. Handle rejected debounced callbacks and await streaming writes.
8
+ - [#10505](https://github.com/scalar/scalar/pull/10505): Apply formatting and lint compatibility changes for Biome 2.5.15.
9
+
10
+ ## 1.5.3
11
+
12
+ ### Patch Changes
13
+
14
+ - [#10484](https://github.com/scalar/scalar/pull/10484): Use OpenAPI 3.2 tag summaries as Markdown headings, falling back to tag names.
15
+
3
16
  ## 1.5.2
4
17
 
5
18
  ## 1.5.1
package/README.md CHANGED
@@ -143,6 +143,9 @@ Schema normalization and description parsing are cached within each renderer. Re
143
143
  schema expansion still tracks ancestors and stops at a depth of ten. Output may use tighter
144
144
  list spacing and normalized Markdown escaping compared with earlier versions.
145
145
 
146
+ For OpenAPI 3.2 descriptions, tag headings use `summary` when present and otherwise
147
+ use `name`. Tag selectors and operation tag metadata continue to use `name`.
148
+
146
149
  The package only generates Markdown. To produce HTML, pass the Markdown output to a
147
150
  Markdown renderer in your application.
148
151
 
@@ -1 +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"}
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;CAuBhE,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,MAAM,GAAI,IAAI,MAAM,KAAG,IAAuD,CAAA"}
@@ -8,13 +8,15 @@ export const createDocumentAnchors = () => {
8
8
  get: (kind, identity, label = identity) => {
9
9
  const key = JSON.stringify([kind, identity]);
10
10
  const previous = ids.get(key);
11
- if (previous !== undefined)
11
+ if (previous !== undefined) {
12
12
  return previous;
13
+ }
13
14
  const base = `scalar-${kind}-${label}`;
14
15
  let id = slugs.slug(base);
15
16
  // A literal suffix in a later name can collide with the slugger's numeric suffix.
16
- while (used.has(id))
17
+ while (used.has(id)) {
17
18
  id = slugs.slug(base);
19
+ }
18
20
  used.add(id);
19
21
  ids.set(key, id);
20
22
  return id;
@@ -1 +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"}
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;AAeD,uGAAuG;AACvG,eAAO,MAAM,sBAAsB,GAAI,SAAS,UAAU,CAAC,OAAO,qBAAqB,CAAC,KAAG,gBA4C1F,CAAA"}
@@ -4,8 +4,9 @@ import { getMarkdownExamples } from './get-markdown-examples.js';
4
4
  const bookkeeping = new Set(['$ref', '$ref-value', '$global', '$status', '__scalar_']);
5
5
  /** Sibling constraints make a reference a different example source from its target. */
6
6
  const identity = (source) => {
7
- if (!isObject(source.schema))
7
+ if (!isObject(source.schema)) {
8
8
  return undefined;
9
+ }
9
10
  const schema = source.schema;
10
11
  const target = '$ref' in schema && Object.keys(schema).every((key) => bookkeeping.has(key)) ? getResolvedRef(schema) : schema;
11
12
  return isObject(target) ? target : schema;
@@ -20,12 +21,14 @@ export const createDocumentExamples = (anchors) => {
20
21
  get: (...args) => {
21
22
  const [source, ...settings] = args;
22
23
  const schema = identity(source);
23
- if (!schema || source.example !== undefined || Object.keys(source.examples ?? {}).length)
24
+ if (!schema || source.example !== undefined || Object.keys(source.examples ?? {}).length) {
24
25
  return getMarkdownExamples(...args);
26
+ }
25
27
  const scope = JSON.stringify(settings);
26
28
  const previous = cache.get(schema)?.get(scope);
27
- if (previous)
29
+ if (previous) {
28
30
  return previous;
31
+ }
29
32
  // Large APIs must not retain every generated object until the export finishes.
30
33
  if (cachedCount >= 256) {
31
34
  cache.clear();
@@ -42,12 +45,14 @@ export const createDocumentExamples = (anchors) => {
42
45
  const schema = identity(source);
43
46
  const entries = schema ? (shown.get(schema) ?? new Map()) : new Map();
44
47
  const previous = entries.get(scope);
45
- if (previous?.value === code.value)
48
+ if (previous?.value === code.value) {
46
49
  return { id: previous.id, previous: true };
50
+ }
47
51
  const id = anchors.get('example', String(++nextId));
48
52
  entries.set(scope, { id, value: code.value });
49
- if (schema)
53
+ if (schema) {
50
54
  shown.set(schema, entries);
55
+ }
51
56
  return { id, previous: false };
52
57
  },
53
58
  };
@@ -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;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"}
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,MAmEjG,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,EA6DjB,CAAA"}
@@ -17,35 +17,47 @@ const MAX_GENERATED_EXAMPLE_VALUES = 10_000;
17
17
  export const countGeneratedExampleValues = (root, limit = MAX_GENERATED_EXAMPLE_VALUES) => {
18
18
  const counts = new WeakMap();
19
19
  const count = (input, level) => {
20
- if (level > EXAMPLE_DEPTH || !isObject(input))
20
+ if (level > EXAMPLE_DEPTH || !isObject(input)) {
21
21
  return 1;
22
+ }
22
23
  const cached = counts.get(input)?.get(level);
23
- if (cached !== undefined)
24
+ if (cached !== undefined) {
24
25
  return cached;
26
+ }
25
27
  const schema = getResolvedRef(input, mergeSiblingReferences);
26
- if (!isObject(schema))
28
+ if (!isObject(schema)) {
27
29
  return 1;
30
+ }
28
31
  // Supplied examples bypass schema expansion in the generator too.
29
- if (schema.example !== undefined || (Array.isArray(schema.examples) && schema.examples.length > 0))
32
+ if (schema.example !== undefined || (Array.isArray(schema.examples) && schema.examples.length > 0)) {
30
33
  return 1;
34
+ }
31
35
  let total = 1;
32
36
  const add = (child) => {
33
- if (total <= limit)
37
+ if (total <= limit) {
34
38
  total += count(child, level + 1);
39
+ }
35
40
  };
36
- if (isObject(schema.properties))
41
+ if (isObject(schema.properties)) {
37
42
  Object.values(schema.properties).forEach(add);
38
- if (isObject(schema.patternProperties))
43
+ }
44
+ if (isObject(schema.patternProperties)) {
39
45
  Object.values(schema.patternProperties).forEach(add);
40
- if (isObject(schema.additionalProperties))
46
+ }
47
+ if (isObject(schema.additionalProperties)) {
41
48
  add(schema.additionalProperties);
42
- if (schema.items !== undefined)
49
+ }
50
+ if (schema.items !== undefined) {
43
51
  add(schema.items);
44
- if (Array.isArray(schema.prefixItems))
52
+ }
53
+ if (Array.isArray(schema.prefixItems)) {
45
54
  schema.prefixItems.forEach(add);
46
- if (Array.isArray(schema.allOf))
55
+ }
56
+ if (Array.isArray(schema.allOf)) {
47
57
  schema.allOf.forEach(add);
48
- const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [];
58
+ }
59
+ const anyOf = Array.isArray(schema.anyOf) ? schema.anyOf : [];
60
+ const variants = Array.isArray(schema.oneOf) ? schema.oneOf : anyOf;
49
61
  // Object and array generation can use the first variant, while other unions skip null.
50
62
  // A discriminator can choose any variant, so bound the largest candidate in that case.
51
63
  const nonNull = variants.find((variant) => {
@@ -57,8 +69,9 @@ export const countGeneratedExampleValues = (root, limit = MAX_GENERATED_EXAMPLE_
57
69
  : [variants[0], nonNull];
58
70
  let variantCount = 0;
59
71
  for (const candidate of candidates) {
60
- if (candidate !== undefined && total <= limit && variantCount <= limit)
72
+ if (candidate !== undefined && total <= limit && variantCount <= limit) {
61
73
  variantCount = Math.max(variantCount, count(candidate, level + 1));
74
+ }
62
75
  }
63
76
  total += variantCount;
64
77
  const levels = counts.get(input) ?? new Map();
@@ -72,40 +85,51 @@ export const countGeneratedExampleValues = (root, limit = MAX_GENERATED_EXAMPLE_
72
85
  export const getMarkdownExamples = (source, mediaType, mode, openapiVersion = '3.2.0',
73
86
  // Schema metadata can be upgraded while example fields still follow the original version.
74
87
  schemaOpenapiVersion = openapiVersion, linked = false) => {
75
- if (source.example !== undefined)
88
+ if (source.example !== undefined) {
76
89
  return [{ value: source.example }];
90
+ }
77
91
  if (source.examples && Object.keys(source.examples).length) {
78
92
  return Object.entries(source.examples).flatMap(([name, reference]) => {
79
93
  const example = getResolvedRef(reference);
80
- if (!isObject(example))
94
+ if (!isObject(example)) {
81
95
  return [];
96
+ }
82
97
  const metadata = {
83
98
  name,
84
99
  summary: typeof example.summary === 'string' ? example.summary : undefined,
85
100
  description: typeof example.description === 'string' ? example.description : undefined,
86
101
  };
87
- if (example.value !== undefined)
102
+ if (example.value !== undefined) {
88
103
  return [{ ...metadata, value: example.value }];
89
- if (/^3\.2\./.test(openapiVersion) && typeof example.serializedValue === 'string')
104
+ }
105
+ if (/^3\.2\./.test(openapiVersion) && typeof example.serializedValue === 'string') {
90
106
  return [{ ...metadata, serializedValue: example.serializedValue }];
91
- if (typeof example.externalValue === 'string')
107
+ }
108
+ if (typeof example.externalValue === 'string') {
92
109
  return [{ ...metadata, externalValue: example.externalValue }];
93
- if (/^3\.2\./.test(openapiVersion) && example.dataValue !== undefined)
110
+ }
111
+ if (/^3\.2\./.test(openapiVersion) && example.dataValue !== undefined) {
94
112
  return [{ ...metadata, dataValue: example.dataValue }];
113
+ }
95
114
  return [];
96
115
  });
97
116
  }
98
117
  const schema = getResolvedRef(source.schema, mergeSiblingReferences);
99
- if (!isObject(schema))
118
+ if (!isObject(schema)) {
100
119
  return [];
101
- if (schema.example !== undefined)
120
+ }
121
+ if (schema.example !== undefined) {
102
122
  return [{ value: schema.example }];
103
- if (Array.isArray(schema.examples) && schema.examples.length)
123
+ }
124
+ if (Array.isArray(schema.examples) && schema.examples.length) {
104
125
  return schema.examples.map((value) => ({ value }));
105
- if (linked)
126
+ }
127
+ if (linked) {
106
128
  return [];
107
- if (countGeneratedExampleValues(source.schema) > MAX_GENERATED_EXAMPLE_VALUES)
129
+ }
130
+ if (countGeneratedExampleValues(source.schema) > MAX_GENERATED_EXAMPLE_VALUES) {
108
131
  return [{ omitted: true }];
132
+ }
109
133
  if (isXmlMediaType(mediaType)) {
110
134
  const result = getXmlBodyExample(source.schema, undefined, {
111
135
  mode,
@@ -1 +1 @@
1
- {"version":3,"file":"load-document.d.ts","sourceRoot":"","sources":["../src/load-document.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,8DAA8D,CAAA;AA6LrE,8EAA8E;AAC9E,eAAO,MAAM,YAAY,GACvB,OAAO,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,KACxD,OAAO,CAAC,eAAe,CAgGzB,CAAA"}
1
+ {"version":3,"file":"load-document.d.ts","sourceRoot":"","sources":["../src/load-document.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,8DAA8D,CAAA;AAiMrE,8EAA8E;AAC9E,eAAO,MAAM,YAAY,GACvB,OAAO,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,KACxD,OAAO,CAAC,eAAe,CAgGzB,CAAA"}
@@ -40,8 +40,9 @@ const attachRefValues = (document, schemas = getSchemas(document), { detectOnly
40
40
  kind = kind && getChildKind(kind, key);
41
41
  position = Array.isArray(parent) ? position : position && getChildPosition(position, key);
42
42
  // Arbitrary extension and example data has not passed an OpenAPI object schema.
43
- if (position === 'document' && kind === undefined)
43
+ if (position === 'document' && kind === undefined) {
44
44
  position = undefined;
45
+ }
45
46
  parent = getValueByPath(parent, [key]).value;
46
47
  }
47
48
  const schema = getTargetSchema(position, kind);
@@ -78,15 +79,20 @@ const attachRefValues = (document, schemas = getSchemas(document), { detectOnly
78
79
  // support summary and description; casting them as their targets would add defaults.
79
80
  // Path Item Objects declare their own $ref field, so their siblings are cast normally.
80
81
  const reference = typeof value.$ref === 'string' ? value.$ref : undefined;
81
- const cast = reference === undefined || schema === referenceTargetSchemas.pathItem
82
- ? coerceValue(schema, value)
83
- : position === 'schema'
84
- ? { ...coerceValue(SchemaObjectSchema, value), $ref: reference }
85
- : {
86
- $ref: reference,
87
- ...(typeof value.summary === 'string' ? { summary: value.summary } : {}),
88
- ...(typeof value.description === 'string' ? { description: value.description } : {}),
89
- };
82
+ let cast;
83
+ if (reference === undefined || schema === referenceTargetSchemas.pathItem) {
84
+ cast = coerceValue(schema, value);
85
+ }
86
+ else if (position === 'schema') {
87
+ cast = { ...coerceValue(SchemaObjectSchema, value), $ref: reference };
88
+ }
89
+ else {
90
+ cast = {
91
+ $ref: reference,
92
+ ...(typeof value.summary === 'string' ? { summary: value.summary } : {}),
93
+ ...(typeof value.description === 'string' ? { description: value.description } : {}),
94
+ };
95
+ }
90
96
  if (fallback?.booleanSchemas) {
91
97
  restoreBooleanSchemas(value, cast, position);
92
98
  }
@@ -1 +1 @@
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"}
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;AAsG7D,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,CAuDlE,CAAA;AAED,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,GAAU,OAAO,WAAW,EAAE,EAAE,aAAa,iBAAiB,KAAG,OAAO,CAAC,IAAI,CAkB3G,CAAA"}
@@ -10,8 +10,9 @@ const clean = (node) => {
10
10
  node.url = safeUrl(node.url);
11
11
  }
12
12
  if ('children' in node) {
13
- for (const child of node.children)
13
+ for (const child of node.children) {
14
14
  clean(child);
15
+ }
15
16
  // The filter preserves the parent's existing child types.
16
17
  const previousLength = node.children.length;
17
18
  node.children = node.children.filter((child) => child.type !== 'image' &&
@@ -33,10 +34,12 @@ const clean = (node) => {
33
34
  if (node.children.length !== previousLength) {
34
35
  const first = node.children.at(0);
35
36
  const last = node.children.at(-1);
36
- if (first?.type === 'text')
37
+ if (first?.type === 'text') {
37
38
  first.value = first.value.trimStart();
38
- if (last?.type === 'text')
39
+ }
40
+ if (last?.type === 'text') {
39
41
  last.value = last.value.trimEnd();
42
+ }
40
43
  }
41
44
  }
42
45
  delete node.position;
@@ -47,24 +50,31 @@ const pruneDefinitions = (tree) => {
47
50
  const footnotes = new Set();
48
51
  const definitions = new Map(tree.children.filter((node) => node.type === 'footnoteDefinition').map((node) => [node.identifier, node]));
49
52
  const collect = (node) => {
50
- if (node.type === 'linkReference')
53
+ if (node.type === 'linkReference') {
51
54
  links.add(node.identifier);
52
- if (node.type === 'footnoteReference')
55
+ }
56
+ if (node.type === 'footnoteReference') {
53
57
  footnotes.add(node.identifier);
54
- if ('children' in node)
58
+ }
59
+ if ('children' in node) {
55
60
  node.children.forEach(collect);
61
+ }
56
62
  };
57
63
  tree.children.filter((node) => node.type !== 'footnoteDefinition').forEach(collect);
58
64
  // Set iteration also visits footnotes discovered inside another footnote.
59
- for (const identifier of footnotes)
65
+ for (const identifier of footnotes) {
60
66
  definitions.get(identifier)?.children.forEach(collect);
67
+ }
61
68
  tree.children = tree.children.filter((node) => {
62
- if (node.type === 'definition')
69
+ if (node.type === 'definition') {
63
70
  return links.has(node.identifier);
64
- if (node.type === 'footnoteDefinition')
71
+ }
72
+ if (node.type === 'footnoteDefinition') {
65
73
  return footnotes.has(node.identifier);
66
- if (node.type === 'paragraph')
74
+ }
75
+ if (node.type === 'paragraph') {
67
76
  return node.children.some((child) => child.type !== 'text' || child.value.trim());
77
+ }
68
78
  return true;
69
79
  });
70
80
  };
@@ -78,8 +88,9 @@ export const createDescriptionParser = () => {
78
88
  const cache = new Map();
79
89
  const parse = async (value) => {
80
90
  // Most schema descriptions are one plain sentence. Parsing them costs more than the page's other text.
81
- if (isPlainText(value))
91
+ if (isPlainText(value)) {
82
92
  return [{ type: 'paragraph', children: [{ type: 'text', value: value.trim() }] }];
93
+ }
83
94
  const parsed = parser.parse(value);
84
95
  // Raw HTML and GitHub alerts retain Scalar's existing conversion behavior.
85
96
  const tree = containsHtml(parsed) || /^\s*>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|SUCCESS)\]/im.test(value)
@@ -93,26 +104,31 @@ export const createDescriptionParser = () => {
93
104
  const copy = { ...node };
94
105
  if ('identifier' in copy) {
95
106
  copy.identifier = `${prefix}${copy.identifier}`;
96
- if ('label' in copy)
107
+ if ('label' in copy) {
97
108
  copy.label = copy.identifier;
98
- if ('referenceType' in copy)
109
+ }
110
+ if ('referenceType' in copy) {
99
111
  copy.referenceType = 'full';
112
+ }
100
113
  }
101
- if ('children' in copy)
114
+ if ('children' in copy) {
102
115
  copy.children = copy.children.map((child) => namespace(child, prefix));
116
+ }
103
117
  return copy;
104
118
  };
105
119
  const hasReference = (node) => 'identifier' in node || ('children' in node && node.children.some(hasReference));
106
120
  return () => {
107
121
  const state = { nextId: 0 };
108
122
  return async (value) => {
109
- if (!value)
123
+ if (!value) {
110
124
  return [];
125
+ }
111
126
  const prefix = `description-${state.nextId++}-`;
112
127
  const cached = cache.get(value) ?? parse(value);
113
128
  if (!cache.has(value)) {
114
- if (cache.size >= 8192)
129
+ if (cache.size >= 8192) {
115
130
  cache.clear();
131
+ }
116
132
  cache.set(value, cached);
117
133
  }
118
134
  const nodes = await cached;
@@ -127,10 +143,12 @@ export const expandDescriptions = async (nodes, description) => {
127
143
  const found = [];
128
144
  const collect = (siblings) => {
129
145
  for (const node of siblings) {
130
- if (node.type === 'descriptionPlaceholder')
146
+ if (node.type === 'descriptionPlaceholder') {
131
147
  found.push({ siblings, node });
132
- else if ('children' in node)
148
+ }
149
+ else if ('children' in node) {
133
150
  collect(node.children);
151
+ }
134
152
  }
135
153
  };
136
154
  collect(nodes);
@@ -1 +1 @@
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"}
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;AAwD7D,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,QAAO,CAAC,CACzC,QAAQ,EAAE,eAAe,EACzB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,MAAM,CAAC,CA0RnB,CAAA"}
@@ -18,14 +18,17 @@ const serializer = unified()
18
18
  bullet: '-',
19
19
  join: [
20
20
  (left, right, parent) => {
21
- if (!('spread' in parent))
21
+ if (!('spread' in parent)) {
22
22
  return undefined;
23
+ }
23
24
  // Inside a tight list item, content after a nested list would otherwise continue its last item.
24
- if (left.type === 'list' && right.type !== 'list')
25
+ if (left.type === 'list' && right.type !== 'list') {
25
26
  return 1;
27
+ }
26
28
  // Code blocks in descriptions read better apart from the prose around them.
27
- if (left.type === 'code' || right.type === 'code')
29
+ if (left.type === 'code' || right.type === 'code') {
28
30
  return 1;
31
+ }
29
32
  return undefined;
30
33
  },
31
34
  ],
@@ -43,12 +46,14 @@ const renderShownModel = async (name, view, previous, description) => {
43
46
  ? [strong(text(view.title)), text(' ('), inlineCode(name), text(')')]
44
47
  : [inlineCode(name)];
45
48
  label.push(text(' — shown above'));
46
- if (previous.name !== name)
49
+ if (previous.name !== name) {
47
50
  label.push(text(' as '), inlineCode(previous.name));
51
+ }
48
52
  label.push(text('.'));
49
53
  const blocks = [paragraph(...label)];
50
- if (view.description && view.description !== previous.description)
54
+ if (view.description && view.description !== previous.description) {
51
55
  blocks.push(...(await description(view.description)));
56
+ }
52
57
  return item(...blocks);
53
58
  };
54
59
  /** Build Markdown directly, retaining caches only for this immutable document snapshot. */
@@ -81,8 +86,9 @@ export const createDocumentRenderer = () => {
81
86
  const entries = [];
82
87
  for (const [path, reference] of Object.entries(group.paths ?? {})) {
83
88
  const pathItem = getResolvedPathItem(reference);
84
- if (!pathItem)
89
+ if (!pathItem) {
85
90
  continue;
91
+ }
86
92
  forEachPathItemOperation(reference, (method, operation) => {
87
93
  const label = `${formatOperationMethod(method)} ${path}`;
88
94
  entries.push({
@@ -107,8 +113,9 @@ export const createDocumentRenderer = () => {
107
113
  const nodes = [];
108
114
  const sections = [];
109
115
  const flush = async () => {
110
- if (!nodes.length)
116
+ if (!nodes.length) {
111
117
  return;
118
+ }
112
119
  const children = nodes.splice(0);
113
120
  await expandDescriptions(children, description);
114
121
  sections.push(serializer.stringify({ type: 'root', children }).trimEnd());
@@ -116,62 +123,74 @@ export const createDocumentRenderer = () => {
116
123
  if (!single) {
117
124
  const { info } = document;
118
125
  const metadata = [field('OpenAPI Version', inlineCode(document.openapi))];
119
- if (info.version)
126
+ if (info.version) {
120
127
  metadata.push(field('API Version', inlineCode(info.version)));
121
- if (info.termsOfService)
128
+ }
129
+ if (info.termsOfService) {
122
130
  metadata.push(field('Terms of service', link(info.termsOfService, info.termsOfService)));
131
+ }
123
132
  if (info.contact) {
124
133
  const contact = [text(info.contact.name ?? '')];
125
134
  metadata.push(item(paragraph(strong(text('Contact:')), text(' '), ...contact, ...(info.contact.url ? [text(' '), link(info.contact.url, info.contact.url)] : []), ...(info.contact.email ? [text(' '), link(`mailto:${info.contact.email}`, info.contact.email)] : []))));
126
135
  }
127
- if (info.license)
136
+ if (info.license) {
128
137
  metadata.push(field('License', info.license.url ? link(info.license.url, info.license.name ?? '') : text(info.license.name)));
138
+ }
129
139
  nodes.push(heading(1, text(info.title)), list(metadata), ...(await description(info.description)));
130
140
  if (whole) {
131
141
  const contents = [];
132
142
  for (const group of groups) {
133
- if (!group.entries.length)
143
+ if (!group.entries.length) {
134
144
  continue;
145
+ }
135
146
  contents.push(paragraph(strong(text(group.title))), list(group.entries.map(({ id, label }) => item(paragraph(link(`#${encodeURIComponent(id)}`, label))))));
136
147
  }
137
148
  if (destinations?.size) {
138
149
  contents.push(paragraph(strong(text('Schemas'))), list([...destinations].map(([name, url]) => item(paragraph(link(url, name))))));
139
150
  }
140
- if (contents.length)
151
+ if (contents.length) {
141
152
  nodes.push(heading(2, text('Contents')), ...contents);
153
+ }
142
154
  }
143
155
  if (document.servers?.length || (whole && document.servers !== undefined)) {
144
- if (documentContext?.servers)
156
+ if (documentContext?.servers) {
145
157
  nodes.push(anchor(documentContext.servers));
158
+ }
146
159
  nodes.push(heading(2, text('Servers')));
147
160
  const effectiveServers = document.servers?.length
148
161
  ? document.servers
149
162
  : [{ url: '/' }];
150
163
  const servers = effectiveServers.map((server) => {
151
164
  const nested = [];
152
- if (server.description)
165
+ if (server.description) {
153
166
  nested.push(field('Description', text(server.description)));
167
+ }
154
168
  const variables = Object.entries(server.variables ?? {});
155
- if (variables.length)
169
+ if (variables.length) {
156
170
  nested.push(item(paragraph(strong(text('Variables:'))), list(variables.map(([name, variable]) => item(paragraph(inlineCode(name), text(' (default: '), inlineCode(variable.default), text(')'), ...(whole && variable.enum?.length
157
171
  ? [text(', possible values: '), inlineCode(variable.enum.join(', '))]
158
172
  : []), text(variable.description ? `: ${variable.description}` : '')))))));
173
+ }
159
174
  const entry = field('URL', inlineCode(server.url));
160
- if (nested.length)
175
+ if (nested.length) {
161
176
  entry.children.push(list(nested));
177
+ }
162
178
  return entry;
163
179
  });
164
180
  nodes.push(list(servers));
165
181
  }
166
- if (documentContext?.authentication)
182
+ if (documentContext?.authentication) {
167
183
  nodes.push(anchor(documentContext.authentication));
184
+ }
168
185
  nodes.push(...(await renderSecurity(document.security, document.components?.securitySchemes, description, 2)));
169
186
  if (document.tags?.length) {
170
187
  nodes.push(heading(2, text('Tags')));
171
188
  for (const tag of document.tags) {
172
- nodes.push(heading(3, text(tag.name)), ...(await description(tag.description)));
173
- if (tag.externalDocs)
189
+ const title = /^3\.2\./.test(openapiVersion) ? (tag.summary ?? tag.name) : tag.name;
190
+ nodes.push(heading(3, text(title)), ...(await description(tag.description)));
191
+ if (tag.externalDocs) {
174
192
  nodes.push(paragraph(link(tag.externalDocs.url, tag.externalDocs.description ?? tag.externalDocs.url)));
193
+ }
175
194
  }
176
195
  }
177
196
  await flush();
@@ -179,12 +198,14 @@ export const createDocumentRenderer = () => {
179
198
  for (const group of groups) {
180
199
  let hasOperations = false;
181
200
  for (const { path, pathItem, method, operation, id } of group.entries) {
182
- if (!hasOperations && !single)
201
+ if (!hasOperations && !single) {
183
202
  nodes.push(heading(2, text(group.title)));
203
+ }
184
204
  hasOperations = true;
185
205
  schemas.beginSection();
186
- if (id)
206
+ if (id) {
187
207
  nodes.push(anchor(id));
208
+ }
188
209
  nodes.push(...(await renderOperation(document, path, method, pathItem, operation, group.webhook, {
189
210
  description,
190
211
  schemas,
@@ -200,11 +221,13 @@ export const createDocumentRenderer = () => {
200
221
  const renderModel = async (name, schema, level) => {
201
222
  const view = schemas.view(schema);
202
223
  const summary = schemas.summarize(schema);
203
- if (whole)
224
+ if (whole) {
204
225
  nodes.push(anchor(anchors.get('schema', name)));
226
+ }
205
227
  nodes.push(heading(level, text(view.title ?? name)));
206
- if (summary.length)
228
+ if (summary.length) {
207
229
  nodes.push(paragraph(strong(text('Type:')), text('\u00a0'), ...summary));
230
+ }
208
231
  nodes.push(...(await description(view.description)), ...schemas.render(schema, 0, [], { hideDetails: true, name }));
209
232
  if (view.type === 'object' || hasAuthoredExamples(view)) {
210
233
  // A model page's own schema is bounded, so it gets a generated example even in linked mode.
@@ -223,13 +246,15 @@ export const createDocumentRenderer = () => {
223
246
  await renderModel(selected[0], selected[1], 1);
224
247
  }
225
248
  const dependencies = models.filter(([name]) => name !== options.model);
226
- if (dependencies.length)
249
+ if (dependencies.length) {
227
250
  nodes.push(heading(2, text('Schemas')));
251
+ }
228
252
  // Models the page already expanded cost one line each, grouped into a single list.
229
253
  const shownAbove = [];
230
254
  const flushShownAbove = () => {
231
- if (shownAbove.length)
255
+ if (shownAbove.length) {
232
256
  nodes.push(list(shownAbove.splice(0)));
257
+ }
233
258
  };
234
259
  for (const [name, schema] of dependencies) {
235
260
  schemas.beginSection();
@@ -1 +1 @@
1
- {"version":3,"file":"render-examples.d.ts","sourceRoot":"","sources":["../src/render-examples.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAQ,WAAW,EAAE,MAAM,OAAO,CAAA;AAG9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAuB,MAAM,yBAAyB,CAAA;AAEjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,+EAA+E;AAC/E,eAAO,MAAM,cAAc,GACzB,QAAQ,aAAa,EACrB,aAAa,iBAAiB,EAC9B,kBAA8B,EAC9B,OAAO,MAAM,GAAG,OAAO,EACvB,uBAAwB,EAExB,6BAAqC,EACrC,+BAIG;IACD,yFAAyF;IACzF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,oFAAoF;IACpF,QAAQ,CAAC,EAAE,gBAAgB,CAAA;CACvB,KACL,OAAO,CAAC,WAAW,EAAE,CA8EvB,CAAA"}
1
+ {"version":3,"file":"render-examples.d.ts","sourceRoot":"","sources":["../src/render-examples.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAQ,WAAW,EAAE,MAAM,OAAO,CAAA;AAG9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAuB,MAAM,yBAAyB,CAAA;AAEjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,+EAA+E;AAC/E,eAAO,MAAM,cAAc,GACzB,QAAQ,aAAa,EACrB,aAAa,iBAAiB,EAC9B,kBAA8B,EAC9B,OAAO,MAAM,GAAG,OAAO,EACvB,uBAAwB,EAExB,6BAAqC,EACrC,+BAIG;IACD,yFAAyF;IACzF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,oFAAoF;IACpF,QAAQ,CAAC,EAAE,gBAAgB,CAAA;CACvB,KACL,OAAO,CAAC,WAAW,EAAE,CAgFvB,CAAA"}