@scalar/openapi-to-markdown 1.3.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +114 -2
  3. package/dist/browser.d.ts.map +1 -1
  4. package/dist/browser.js +1 -1
  5. package/dist/create-markdown-from-openapi.js +1 -1
  6. package/dist/document-anchors.d.ts +8 -0
  7. package/dist/document-anchors.d.ts.map +1 -0
  8. package/dist/document-anchors.js +25 -0
  9. package/dist/document-examples.d.ts +14 -0
  10. package/dist/document-examples.d.ts.map +1 -0
  11. package/dist/document-examples.js +54 -0
  12. package/dist/get-markdown-examples.d.ts +3 -1
  13. package/dist/get-markdown-examples.d.ts.map +1 -1
  14. package/dist/get-markdown-examples.js +12 -4
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/markdown-nodes.d.ts +22 -1
  18. package/dist/markdown-nodes.d.ts.map +1 -1
  19. package/dist/markdown-nodes.js +4 -0
  20. package/dist/parse-description.d.ts +2 -0
  21. package/dist/parse-description.d.ts.map +1 -1
  22. package/dist/parse-description.js +28 -1
  23. package/dist/render-document.d.ts +2 -1
  24. package/dist/render-document.d.ts.map +1 -1
  25. package/dist/render-document.js +217 -84
  26. package/dist/render-examples.d.ts +9 -1
  27. package/dist/render-examples.d.ts.map +1 -1
  28. package/dist/render-examples.js +26 -8
  29. package/dist/render-operation-details.d.ts +3 -2
  30. package/dist/render-operation-details.d.ts.map +1 -1
  31. package/dist/render-operation-details.js +8 -5
  32. package/dist/render-operation.d.ts +16 -1
  33. package/dist/render-operation.d.ts.map +1 -1
  34. package/dist/render-operation.js +168 -42
  35. package/dist/render-schema.d.ts +32 -3
  36. package/dist/render-schema.d.ts.map +1 -1
  37. package/dist/render-schema.js +499 -104
  38. package/dist/render-security.d.ts +7 -3
  39. package/dist/render-security.d.ts.map +1 -1
  40. package/dist/render-security.js +61 -12
  41. package/dist/select-document.d.ts +19 -2
  42. package/dist/select-document.d.ts.map +1 -1
  43. package/dist/select-document.js +9 -6
  44. package/package.json +19 -10
@@ -1,6 +1,10 @@
1
1
  import type { OpenApiDocument } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
2
- import type { RootContent } from 'mdast';
2
+ import type { Heading, RootContent } from 'mdast';
3
3
  import type { DescriptionParser } from './parse-description.js';
4
- /** Preserve OR between requirements and AND between schemes within a requirement. */
5
- export declare const renderSecurity: (requirements: OpenApiDocument["security"], schemes: NonNullable<OpenApiDocument["components"]>["securitySchemes"], description: DescriptionParser) => Promise<RootContent[]>;
4
+ /**
5
+ * Preserve OR between requirements and AND between schemes within a requirement.
6
+ * Without declared requirements there is nothing to say: only an explicit empty list means that
7
+ * no authentication is required.
8
+ */
9
+ export declare const renderSecurity: (requirements: OpenApiDocument["security"], schemes: NonNullable<OpenApiDocument["components"]>["securitySchemes"], description: DescriptionParser, level?: Heading["depth"]) => Promise<RootContent[]>;
6
10
  //# sourceMappingURL=render-security.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-security.d.ts","sourceRoot":"","sources":["../src/render-security.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAA;AACnG,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,qFAAqF;AACrF,eAAO,MAAM,cAAc,GACzB,cAAc,eAAe,CAAC,UAAU,CAAC,EACzC,SAAS,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,EACtE,aAAa,iBAAiB,KAC7B,OAAO,CAAC,WAAW,EAAE,CA0BvB,CAAA"}
1
+ {"version":3,"file":"render-security.d.ts","sourceRoot":"","sources":["../src/render-security.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAA;AACnG,OAAO,KAAK,EAAE,OAAO,EAA6B,WAAW,EAAE,MAAM,OAAO,CAAA;AAG5E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAsD5D;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,cAAc,eAAe,CAAC,UAAU,CAAC,EACzC,SAAS,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,EACtE,aAAa,iBAAiB,EAC9B,QAAO,OAAO,CAAC,OAAO,CAAK,KAC1B,OAAO,CAAC,WAAW,EAAE,CAsBvB,CAAA"}
@@ -1,10 +1,61 @@
1
1
  import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
2
- import { heading, item, list, paragraph, strong, text } from './markdown-nodes.js';
3
- /** Preserve OR between requirements and AND between schemes within a requirement. */
4
- export const renderSecurity = async (requirements, schemes, description) => {
2
+ import { heading, inlineCode, item, list, paragraph, strong, text } from './markdown-nodes.js';
3
+ const flowNames = {
4
+ implicit: 'implicit',
5
+ password: 'password',
6
+ clientCredentials: 'client credentials',
7
+ authorizationCode: 'authorization code',
8
+ deviceAuthorization: 'device authorization',
9
+ };
10
+ /** Summarize a security scheme on one line, for example "API key in header `X-Api-Key`". */
11
+ const summarizeScheme = (scheme) => {
12
+ const value = scheme;
13
+ switch (value.type) {
14
+ case 'apiKey':
15
+ return [text(`API key in ${String(value.in ?? 'header')} `), inlineCode(value.name)];
16
+ case 'http': {
17
+ const name = typeof value.scheme === 'string' ? value.scheme.toLowerCase() : '';
18
+ return [
19
+ text(`HTTP ${name || 'authentication'}`),
20
+ ...(typeof value.bearerFormat === 'string' ? [text(' ('), inlineCode(value.bearerFormat), text(')')] : []),
21
+ ];
22
+ }
23
+ case 'oauth2': {
24
+ const flows = Object.entries(typeof value.flows === 'object' && value.flows ? value.flows : {});
25
+ const nodes = [text('OAuth 2.0')];
26
+ for (const [index, [flow, settings]] of flows.entries()) {
27
+ const urls = settings;
28
+ nodes.push(text(`${index ? '; ' : ': '}${flowNames[flow] ?? flow}`));
29
+ for (const [key, label] of [
30
+ ['authorizationUrl', 'authorize'],
31
+ ['deviceAuthorizationUrl', 'device authorization'],
32
+ ['tokenUrl', 'token'],
33
+ ['refreshUrl', 'refresh'],
34
+ ]) {
35
+ // Coercion fills in optional URLs as empty strings.
36
+ if (typeof urls[key] === 'string' && urls[key])
37
+ nodes.push(text(`, ${label} `), inlineCode(urls[key]));
38
+ }
39
+ }
40
+ return nodes;
41
+ }
42
+ case 'openIdConnect':
43
+ return [text('OpenID Connect '), inlineCode(value.openIdConnectUrl)];
44
+ case 'mutualTLS':
45
+ return [text('Mutual TLS')];
46
+ default:
47
+ return [inlineCode(value.type)];
48
+ }
49
+ };
50
+ /**
51
+ * Preserve OR between requirements and AND between schemes within a requirement.
52
+ * Without declared requirements there is nothing to say: only an explicit empty list means that
53
+ * no authentication is required.
54
+ */
55
+ export const renderSecurity = async (requirements, schemes, description, level = 4) => {
5
56
  if (!requirements)
6
57
  return [];
7
- const nodes = [heading(4, text('Authentication'))];
58
+ const nodes = [heading(level, text('Authentication'))];
8
59
  if (!requirements.length)
9
60
  nodes.push(paragraph(text('No authentication required.')));
10
61
  for (const [index, requirement] of requirements.entries()) {
@@ -17,15 +68,13 @@ export const renderSecurity = async (requirements, schemes, description) => {
17
68
  }
18
69
  const entriesNodes = [];
19
70
  for (const [name, scopes] of entries) {
20
- const blocks = [
21
- paragraph(strong(text(name)), ...(scopes?.length ? [text(` Scopes: ${scopes.join(', ')}`)] : [])),
22
- ];
23
71
  const scheme = getResolvedRef(schemes?.[name]);
24
- if (scheme) {
25
- blocks.push({ type: 'code', value: JSON.stringify(scheme, null, 2) });
26
- blocks.push(...(await description(scheme.description)));
27
- }
28
- entriesNodes.push(item(...blocks));
72
+ const line = [strong(text(name))];
73
+ if (scheme)
74
+ line.push(text(': '), ...summarizeScheme(scheme));
75
+ if (scopes?.length)
76
+ line.push(text(', scopes: '), inlineCode(scopes.join(', ')));
77
+ entriesNodes.push(item(paragraph(...line), ...(await description(scheme?.description))));
29
78
  }
30
79
  nodes.push(list(entriesNodes));
31
80
  }
@@ -10,9 +10,26 @@ export type OperationSelector = {
10
10
  pointer: string;
11
11
  };
12
12
  /** Select one reference page, or omit selectors for the whole document. */
13
- export type OpenApiRenderOptions = {
13
+ export type OpenApiRenderOptions = SchemaReferenceOptions & ({
14
14
  [Key in keyof PageSelectors]: Partial<Record<Exclude<keyof PageSelectors, Key>, never>> & Pick<PageSelectors, Key>;
15
- }[keyof PageSelectors] | Partial<Record<keyof PageSelectors, never>>;
15
+ }[keyof PageSelectors] | Partial<Record<keyof PageSelectors, never>>);
16
+ /** Control shared-schema expansion without assuming a documentation URL layout. */
17
+ export type SchemaReferenceOptions = {
18
+ /** Expand root schemas, link nested references, and omit generated examples and the transitive appendix. */
19
+ schemaReferences?: {
20
+ mode: 'linked';
21
+ /** Return a published URL, or undefined to retain the schema name as plain text. */
22
+ resolveUrl?: (reference: {
23
+ ref: string;
24
+ name: string;
25
+ }) => string | undefined;
26
+ /**
27
+ * Render references to primitive, enum and `const` schemas, and to aliases of them, in place
28
+ * instead of linking them. Their whole definition fits on one line. Defaults to `true`.
29
+ */
30
+ inlinePrimitives?: boolean;
31
+ };
32
+ };
16
33
  type PageSelectors = {
17
34
  operation: OperationSelector;
18
35
  tag: string;
@@ -1 +1 @@
1
- {"version":3,"file":"select-document.d.ts","sourceRoot":"","sources":["../src/select-document.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAA;AACjF,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,8DAA8D,CAAA;AAEnH,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,eAAe,CAAA;CACxB,GACD;IACE,WAAW,EAAE,MAAM,CAAA;CACpB,GACD;IACE,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AACL,2EAA2E;AAC3E,MAAM,MAAM,oBAAoB,GAC5B;KACG,GAAG,IAAI,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,aAAa,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC;CACnH,CAAC,MAAM,aAAa,CAAC,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,aAAa,EAAE,KAAK,CAAC,CAAC,CAAA;AAE/C,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,iBAAiB,CAAA;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,eAAe,CAAA;KAAE,CAAA;IAClD,YAAY,EAAE,IAAI,CAAA;CACnB,CAAA;AAyKD,sEAAsE;AACtE,eAAO,MAAM,cAAc,GAAI,UAAU,eAAe,EAAE,UAAS,oBAAyB,KAAG,eAuN9F,CAAA"}
1
+ {"version":3,"file":"select-document.d.ts","sourceRoot":"","sources":["../src/select-document.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAA;AACjF,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,8DAA8D,CAAA;AAEnH,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,eAAe,CAAA;CACxB,GACD;IACE,WAAW,EAAE,MAAM,CAAA;CACpB,GACD;IACE,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AACL,2EAA2E;AAC3E,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,GACvD,CACI;KACG,GAAG,IAAI,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,aAAa,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,GACrF,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC;CAC3B,CAAC,MAAM,aAAa,CAAC,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,aAAa,EAAE,KAAK,CAAC,CAAC,CAC9C,CAAA;AAEH,mFAAmF;AACnF,MAAM,MAAM,sBAAsB,GAAG;IACnC,4GAA4G;IAC5G,gBAAgB,CAAC,EAAE;QACjB,IAAI,EAAE,QAAQ,CAAA;QACd,oFAAoF;QACpF,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,KAAK,MAAM,GAAG,SAAS,CAAA;QAC7E;;;WAGG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAC3B,CAAA;CACF,CAAA;AAED,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,iBAAiB,CAAA;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,eAAe,CAAA;KAAE,CAAA;IAClD,YAAY,EAAE,IAAI,CAAA;CACnB,CAAA;AAyKD,sEAAsE;AACtE,eAAO,MAAM,cAAc,GAAI,UAAU,eAAe,EAAE,UAAS,oBAAyB,KAAG,eA4N9F,CAAA"}
@@ -126,7 +126,7 @@ export const selectDocument = (document, options = {}) => {
126
126
  if (!isObject(options)) {
127
127
  throw new Error('Render options must be an object');
128
128
  }
129
- const keys = Object.keys(options).filter((key) => options[key] !== undefined);
129
+ const keys = Object.keys(options).filter((key) => key !== 'schemaReferences' && options[key] !== undefined);
130
130
  if (!keys.length) {
131
131
  return document;
132
132
  }
@@ -231,8 +231,9 @@ export const selectDocument = (document, options = {}) => {
231
231
  parameters.set(`${parameter.in}:${parameter.name}`, ref);
232
232
  }
233
233
  }
234
- const security = operation.security ?? document.security ?? [];
235
- for (const requirement of security) {
234
+ // Undeclared security stays undeclared: an empty list would claim that no authentication is required.
235
+ const security = operation.security ?? document.security;
236
+ for (const requirement of security ?? []) {
236
237
  for (const name of Object.keys(requirement)) {
237
238
  securityNames.add(name);
238
239
  }
@@ -320,9 +321,11 @@ export const selectDocument = (document, options = {}) => {
320
321
  }
321
322
  }
322
323
  };
323
- visit({ paths: selected.paths, webhooks: selected.webhooks });
324
- for (const root of modelRoots) {
325
- visit(root);
324
+ if (options.schemaReferences?.mode !== 'linked') {
325
+ visit({ paths: selected.paths, webhooks: selected.webhooks });
326
+ for (const root of modelRoots) {
327
+ visit(root);
328
+ }
326
329
  }
327
330
  selected.components = {
328
331
  ...document.components,
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "llm",
17
17
  "swagger"
18
18
  ],
19
- "version": "1.3.0",
19
+ "version": "1.5.0",
20
20
  "engines": {
21
21
  "node": ">=22"
22
22
  },
@@ -41,10 +41,10 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "@scalar/code-highlight": "0.4.7",
44
- "@scalar/helpers": "0.15.0",
45
- "@scalar/json-magic": "0.15.2",
46
- "@scalar/openapi-upgrader": "0.4.0",
47
- "@scalar/workspace-store": "0.67.0",
44
+ "@scalar/helpers": "0.16.0",
45
+ "@scalar/json-magic": "0.15.3",
46
+ "@scalar/openapi-upgrader": "0.4.1",
47
+ "@scalar/workspace-store": "0.67.2",
48
48
  "rehype-parse": "^9.0.1",
49
49
  "rehype-remark": "^10.0.1",
50
50
  "rehype-sanitize": "^6.0.0",
@@ -54,16 +54,25 @@
54
54
  "unified": "^11.0.5"
55
55
  },
56
56
  "devDependencies": {
57
- "@hono/node-server": "^2.1.1",
57
+ "@scalar/components": "0.30.5",
58
58
  "@scalar/galaxy": "0.7.1",
59
+ "@scalar/themes": "0.18.1",
60
+ "@tailwindcss/vite": "^4.3.3",
59
61
  "@types/mdast": "^4.0.4",
60
- "hono": "^4.13.8",
61
- "vitest": "4.1.10"
62
+ "@vitejs/plugin-vue": "^6.0.8",
63
+ "marked": "^14.0.0",
64
+ "rehype-stringify": "^10.0.1",
65
+ "tailwindcss": "^4.3.3",
66
+ "tsx": "4.19.4",
67
+ "vite": "8.1.5",
68
+ "vitest": "4.1.10",
69
+ "vue": "^3.5.40",
70
+ "vue-tsc": "^3.3.8"
62
71
  },
63
72
  "scripts": {
64
73
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
65
- "dev": "tsx watch playground/index.ts",
74
+ "dev": "cd playground && vite",
66
75
  "test": "vitest --run",
67
- "types:check": "tsgo --noEmit"
76
+ "types:check": "tsgo --noEmit && vue-tsc --noEmit -p playground/tsconfig.json"
68
77
  }
69
78
  }