@umami/shiso 1.11.0 → 1.13.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/bin/shiso.mjs +20 -1
- package/dist/chunks/App.js +412 -51
- package/dist/chunks/abnfDiagram-VCTEODGH.js +109 -0
- package/dist/chunks/arc.js +130 -0
- package/dist/chunks/architecture-7GRP2DOG.js +4 -0
- package/dist/chunks/architectureDiagram-5GKGNRK7.js +7466 -0
- package/dist/chunks/array.js +8 -0
- package/dist/chunks/blockDiagram-I7D4REHJ.js +2546 -0
- package/dist/chunks/c4Diagram-7LVT6UL2.js +3654 -0
- package/dist/chunks/channel.js +9 -0
- package/dist/chunks/chunk-2Q5K7J3B.js +21 -0
- package/dist/chunks/chunk-4HAMMTFA.js +6106 -0
- package/dist/chunks/chunk-5VM5RSS4.js +21 -0
- package/dist/chunks/chunk-75Z2AOVW.js +2185 -0
- package/dist/chunks/chunk-DU6HZSFF.js +8729 -0
- package/dist/chunks/chunk-F27PBJKO.js +95 -0
- package/dist/chunks/chunk-FOHPRMQF.js +27162 -0
- package/dist/chunks/chunk-GMAD6QVW.js +2369 -0
- package/dist/chunks/chunk-GVQU2GXP.js +58 -0
- package/dist/chunks/chunk-IMKFNOWR.js +2593 -0
- package/dist/chunks/chunk-JWPE2WC7.js +12 -0
- package/dist/chunks/chunk-L3NEJ4N5.js +420 -0
- package/dist/chunks/chunk-OSK3NFVY.js +1029 -0
- package/dist/chunks/chunk-P2QGCYS3.js +95 -0
- package/dist/chunks/chunk-POPQ4Y6H.js +33 -0
- package/dist/chunks/chunk-PWAF6VOD.js +467 -0
- package/dist/chunks/chunk-SHT3W25Y.js +4813 -0
- package/dist/chunks/chunk-SVP7TREG.js +686 -0
- package/dist/chunks/chunk-TICWLB2K.js +3800 -0
- package/dist/chunks/chunk-XXDRQBXY.js +12 -0
- package/dist/chunks/chunk-Y2CYZVJY.js +15 -0
- package/dist/chunks/classDiagram-ZZMXUADV.js +33 -0
- package/dist/chunks/classDiagram-v2-VYDZK3BY.js +33 -0
- package/dist/chunks/cose-bilkent-JH36ORCC.js +4268 -0
- package/dist/chunks/cynefin-OW5HDTMX.js +4 -0
- package/dist/chunks/cynefinDiagram-5FMLGOSQ.js +482 -0
- package/dist/chunks/cytoscape.esm.js +28282 -0
- package/dist/chunks/dagre-GXQ25YYZ.js +583 -0
- package/dist/chunks/dagre.js +4235 -0
- package/dist/chunks/defaultLocale.js +246 -0
- package/dist/chunks/diagram-S7CK7UJ4.js +402 -0
- package/dist/chunks/diagram-UQ7AKVKN.js +291 -0
- package/dist/chunks/diagram-VSXAHHWV.js +553 -0
- package/dist/chunks/diagram-VX7I27RA.js +798 -0
- package/dist/chunks/diagram-Z3DM3KII.js +186 -0
- package/dist/chunks/dist.js +92 -0
- package/dist/chunks/docs.js +989 -93
- package/dist/chunks/ebnfDiagram-PWID7BFC.js +123 -0
- package/dist/chunks/erDiagram-RLTQ6QDP.js +2295 -0
- package/dist/chunks/eventmodeling-NTZA5JFV.js +4 -0
- package/dist/chunks/flowDiagram-HODETNUW.js +17 -0
- package/dist/chunks/ganttDiagram-EL5Y4UJY.js +3928 -0
- package/dist/chunks/gitGraph-4MIJSDKK.js +4 -0
- package/dist/chunks/gitGraphDiagram-WWUBYQGX.js +1153 -0
- package/dist/chunks/graphlib.js +4194 -0
- package/dist/chunks/info-A6RAGUB7.js +4 -0
- package/dist/chunks/infoDiagram-27XIBGKW.js +25 -0
- package/dist/chunks/init.js +14 -0
- package/dist/chunks/ishikawaDiagram-5VMMS53U.js +971 -0
- package/dist/chunks/journeyDiagram-3NMN7TZE.js +1226 -0
- package/dist/chunks/kanban-definition-UXKFOSKX.js +1241 -0
- package/dist/chunks/katex.js +26463 -0
- package/dist/chunks/line.js +48 -0
- package/dist/chunks/linear.js +391 -0
- package/dist/chunks/mermaid-parser.core.js +754 -0
- package/dist/chunks/mermaid.core.js +4898 -0
- package/dist/chunks/mindmap-definition-YA3MSWOX.js +1309 -0
- package/dist/chunks/ordinal.js +84 -0
- package/dist/chunks/packet-AYTQ26CC.js +4 -0
- package/dist/chunks/path.js +106 -0
- package/dist/chunks/pegDiagram-XKGWAZYB.js +115 -0
- package/dist/chunks/pie-WAS4IAKB.js +4 -0
- package/dist/chunks/pieDiagram-E7YTZNPT.js +297 -0
- package/dist/chunks/quadrantDiagram-AXDQQJYC.js +2234 -0
- package/dist/chunks/radar-RG4KPBEZ.js +4 -0
- package/dist/chunks/railroad-74A4TZTK.js +4 -0
- package/dist/chunks/railroad-abnf-HS5TGJTU.js +4 -0
- package/dist/chunks/railroad-ebnf-LZEXJU2U.js +4 -0
- package/dist/chunks/railroad-peg-WCYAUIDC.js +4 -0
- package/dist/chunks/railroadDiagram-O6MQD6OU.js +89 -0
- package/dist/chunks/requirementDiagram-BXWQKSXE.js +2464 -0
- package/dist/chunks/rough.esm.js +1396 -0
- package/dist/chunks/sankeyDiagram-P5KCCOFB.js +1308 -0
- package/dist/chunks/sequenceDiagram-WJ2MYXX4.js +5445 -0
- package/dist/chunks/sizeCapture-INFHLROL.js +56 -0
- package/dist/chunks/src.js +2653 -0
- package/dist/chunks/stateDiagram-D77RDMKH.js +368 -0
- package/dist/chunks/stateDiagram-v2-MP3YSRHH.js +33 -0
- package/dist/chunks/swimlanes-42K2YHIH.js +6925 -0
- package/dist/chunks/swimlanesDiagram-VR7AAH4N.js +32 -0
- package/dist/chunks/timeline-definition-24CTP7MA.js +1507 -0
- package/dist/chunks/treeView-Q6P3EWNA.js +4 -0
- package/dist/chunks/treemap-WGGIJYW6.js +4 -0
- package/dist/chunks/vennDiagram-4TSXK5OY.js +2803 -0
- package/dist/chunks/wardley-WFR3VGLG.js +4 -0
- package/dist/chunks/wardleyDiagram-VM6X3IG4.js +877 -0
- package/dist/chunks/xychartDiagram-S5SC5T6Z.js +2698 -0
- package/dist/components.js +2 -2
- package/dist/entry-client.js +2 -2
- package/dist/entry-server.js +31 -3
- package/docs.schema.json +90 -0
- package/mdx.config.ts +13 -97
- package/package.json +6 -4
- package/scripts/build-runtime.mjs +1 -0
- package/scripts/check-content.mjs +358 -0
- package/scripts/expand-navigation-globs.mjs +277 -0
- package/scripts/expand-openapi-navigation.mjs +94 -0
- package/scripts/generate-openapi.mjs +117 -0
- package/scripts/generate-search-index.mjs +31 -1
- package/scripts/lib/openapi.mjs +653 -0
- package/scripts/load-docs-config.mjs +52 -6
- package/scripts/load-shiso-config.mjs +45 -3
- package/scripts/prerender.mjs +83 -3
- package/scripts/vite-docs-config.mjs +27 -6
- package/src/App.tsx +0 -1
- package/src/components/CodeBlock.tsx +108 -8
- package/src/components/DocContent.tsx +18 -0
- package/src/components/Docs.tsx +45 -17
- package/src/components/OpenApiOperation.tsx +205 -0
- package/src/components/SideNav.tsx +13 -2
- package/src/components/docs/Changelog.tsx +163 -0
- package/src/components/docs/CodeGroup.tsx +6 -2
- package/src/components/docs/Mermaid.tsx +164 -0
- package/src/components/docs/Panel.tsx +23 -0
- package/src/components/docs/ParamField.tsx +17 -5
- package/src/components/docs/PropertiesTable.tsx +2 -2
- package/src/components/docs/ResponseField.tsx +17 -5
- package/src/components/docs/Tiles.tsx +59 -0
- package/src/components/docs/Tree.tsx +359 -0
- package/src/components/docs/Update.tsx +72 -0
- package/src/components/docs/index.ts +7 -0
- package/src/components/docs/panel-context.tsx +25 -0
- package/src/components/docs/styles.ts +49 -0
- package/src/components/icons/index.ts +4 -0
- package/src/entry-server.tsx +50 -0
- package/src/lib/code-blocks.ts +18 -0
- package/src/lib/code-meta.ts +87 -0
- package/src/lib/docs-config.ts +4 -1
- package/src/lib/openapi.generated.ts +4 -0
- package/src/lib/openapi.ts +63 -0
- package/src/lib/rehype-shiki.ts +213 -0
- package/src/lib/site-model.ts +2 -0
- package/src/lib/types.ts +116 -2
- package/src/styles/global.css +63 -73
- package/types/components.d.ts +79 -0
- package/types/config.d.ts +11 -4
- package/vite.config.ts +49 -3
- package/CHANGELOG.md +0 -171
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { expandNavigationGlobs, hasNavigationGlobs } from './expand-navigation-globs.mjs';
|
|
4
|
+
import { expandOpenApiNavigation, hasOpenApiItems } from './expand-openapi-navigation.mjs';
|
|
5
|
+
import {
|
|
6
|
+
generateOpenApiStubs,
|
|
7
|
+
loadOpenApiSpec,
|
|
8
|
+
normalizeOperations,
|
|
9
|
+
resolveApiDirectory,
|
|
10
|
+
} from './lib/openapi.mjs';
|
|
11
|
+
import { loadShisoConfig } from './load-shiso-config.mjs';
|
|
3
12
|
|
|
4
13
|
/** Error raised while locating, reading, or parsing a Shiso configuration file. */
|
|
5
14
|
export class DocsConfigLoadError extends Error {
|
|
@@ -220,16 +229,53 @@ async function resolveConfigReferences(entryPath, projectRoot) {
|
|
|
220
229
|
* stable place from which to resolve relative references without changing
|
|
221
230
|
* every build-time consumer again.
|
|
222
231
|
*/
|
|
223
|
-
export async function loadDocsConfig({
|
|
232
|
+
export async function loadDocsConfig({
|
|
233
|
+
root = process.cwd(),
|
|
234
|
+
configFile = 'docs.json',
|
|
235
|
+
expandGlobs = true,
|
|
236
|
+
} = {}) {
|
|
224
237
|
const requestedRoot = path.resolve(root);
|
|
225
238
|
const requestedSourcePath = path.resolve(requestedRoot, configFile);
|
|
226
|
-
const {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
239
|
+
const {
|
|
240
|
+
config: sourceConfig,
|
|
241
|
+
projectRoot,
|
|
242
|
+
sourcePaths,
|
|
243
|
+
} = await resolveConfigReferences(requestedSourcePath, requestedRoot);
|
|
230
244
|
const sourcePath = sourcePaths[0] || requestedSourcePath;
|
|
245
|
+
const hasGlobs = hasNavigationGlobs(sourceConfig.navigation);
|
|
246
|
+
let working = sourceConfig;
|
|
247
|
+
let specPath;
|
|
248
|
+
|
|
249
|
+
// OpenAPI expansion runs before glob expansion so generated stub pages are
|
|
250
|
+
// visible to navigation globs and every downstream consumer.
|
|
251
|
+
if (expandGlobs && working.api?.spec) {
|
|
252
|
+
const loadedSpec = await loadOpenApiSpec({ root: projectRoot, specPath: working.api.spec });
|
|
253
|
+
specPath = loadedSpec.specPath;
|
|
254
|
+
const operations = normalizeOperations(loadedSpec.spec);
|
|
255
|
+
const directory = resolveApiDirectory(working.api);
|
|
256
|
+
const { config: shisoConfig } = await loadShisoConfig({ root: projectRoot });
|
|
257
|
+
|
|
258
|
+
await generateOpenApiStubs({
|
|
259
|
+
root: projectRoot,
|
|
260
|
+
contentDir: shisoConfig.contentDir,
|
|
261
|
+
directory,
|
|
262
|
+
operations,
|
|
263
|
+
});
|
|
264
|
+
working = expandOpenApiNavigation(working, { operations, directory });
|
|
265
|
+
} else if (expandGlobs && hasOpenApiItems(working.navigation)) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
'Navigation contains an { "openapi" } entry but docs.json has no "api.spec" setting.',
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const config = expandGlobs
|
|
272
|
+
? await expandNavigationGlobs(working, {
|
|
273
|
+
root: projectRoot,
|
|
274
|
+
contentDir: (await loadShisoConfig({ root: projectRoot })).config.contentDir,
|
|
275
|
+
})
|
|
276
|
+
: working;
|
|
231
277
|
|
|
232
|
-
return { config, projectRoot, sourcePath, sourcePaths };
|
|
278
|
+
return { config, projectRoot, sourcePath, sourcePaths, hasGlobs, specPath };
|
|
233
279
|
}
|
|
234
280
|
|
|
235
281
|
export async function loadDocsSchema({
|
|
@@ -15,7 +15,9 @@ import { createJiti } from 'jiti';
|
|
|
15
15
|
/** Candidate filenames in precedence order. Exactly one may exist. */
|
|
16
16
|
export const SHISO_CONFIG_FILES = ['shiso.config.ts', 'shiso.config.mjs', 'shiso.config.js'];
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const STRING_KEYS = ['docsPrefix', 'contentDir', 'siteUrl', 'locale'];
|
|
19
|
+
const KNOWN_KEYS = [...STRING_KEYS, 'mdx'];
|
|
20
|
+
const MDX_KEYS = ['remarkPlugins', 'rehypePlugins'];
|
|
19
21
|
|
|
20
22
|
let importGeneration = 0;
|
|
21
23
|
|
|
@@ -53,12 +55,45 @@ function assertStringOption(raw, key, sourcePath) {
|
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
function resolveMdxConfig(value, sourcePath) {
|
|
59
|
+
if (value === undefined) return undefined;
|
|
60
|
+
|
|
61
|
+
if (!isPlainObject(value)) {
|
|
62
|
+
throw new ShisoConfigLoadError('Shiso config option "mdx" must be a plain object.', {
|
|
63
|
+
code: 'INVALID_OPTION',
|
|
64
|
+
sourcePath,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const unknownKeys = Object.keys(value).filter(key => !MDX_KEYS.includes(key));
|
|
69
|
+
if (unknownKeys.length) {
|
|
70
|
+
throw new ShisoConfigLoadError(
|
|
71
|
+
`Shiso config option "mdx" has unknown ${unknownKeys.length === 1 ? 'key' : 'keys'} ${unknownKeys.map(key => `"${key}"`).join(', ')}. Supported keys: ${MDX_KEYS.join(', ')}.`,
|
|
72
|
+
{ code: 'UNKNOWN_OPTION', sourcePath },
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const key of MDX_KEYS) {
|
|
77
|
+
if (value[key] !== undefined && !Array.isArray(value[key])) {
|
|
78
|
+
throw new ShisoConfigLoadError(`Shiso config option "mdx.${key}" must be an array.`, {
|
|
79
|
+
code: 'INVALID_OPTION',
|
|
80
|
+
sourcePath,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
remarkPlugins: value.remarkPlugins || [],
|
|
87
|
+
rehypePlugins: value.rehypePlugins || [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
56
91
|
/**
|
|
57
92
|
* Applies defaults and normalization. Single source of truth for resolved
|
|
58
93
|
* values, so runtime and build-time consumers never re-implement defaulting.
|
|
59
94
|
*/
|
|
60
95
|
export function resolveShisoConfig(raw = {}, sourcePath = null) {
|
|
61
|
-
for (const key of
|
|
96
|
+
for (const key of STRING_KEYS) {
|
|
62
97
|
assertStringOption(raw, key, sourcePath);
|
|
63
98
|
}
|
|
64
99
|
|
|
@@ -67,6 +102,7 @@ export function resolveShisoConfig(raw = {}, sourcePath = null) {
|
|
|
67
102
|
contentDir: (raw.contentDir ?? 'content/docs').trim().replace(/^\/+|\/+$/g, ''),
|
|
68
103
|
siteUrl: raw.siteUrl?.trim().replace(/\/+$/, '') || undefined,
|
|
69
104
|
locale: raw.locale?.trim() || 'en-US',
|
|
105
|
+
mdx: resolveMdxConfig(raw.mdx, sourcePath),
|
|
70
106
|
};
|
|
71
107
|
}
|
|
72
108
|
|
|
@@ -108,7 +144,13 @@ export async function loadShisoConfig({ root = process.cwd() } = {}) {
|
|
|
108
144
|
}
|
|
109
145
|
|
|
110
146
|
if (found.length === 0) {
|
|
111
|
-
return {
|
|
147
|
+
return {
|
|
148
|
+
config: resolveShisoConfig(),
|
|
149
|
+
raw: {},
|
|
150
|
+
projectRoot,
|
|
151
|
+
sourcePath: null,
|
|
152
|
+
sourcePaths: [],
|
|
153
|
+
};
|
|
112
154
|
}
|
|
113
155
|
|
|
114
156
|
const sourcePath = found[0];
|
package/scripts/prerender.mjs
CHANGED
|
@@ -16,6 +16,13 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import process from 'node:process';
|
|
18
18
|
import { pathToFileURL } from 'node:url';
|
|
19
|
+
import {
|
|
20
|
+
loadOpenApiSpec,
|
|
21
|
+
normalizeOperationKey,
|
|
22
|
+
normalizeOperations,
|
|
23
|
+
operationToMarkdown,
|
|
24
|
+
} from './lib/openapi.mjs';
|
|
25
|
+
import { loadDocsConfig } from './load-docs-config.mjs';
|
|
19
26
|
|
|
20
27
|
const DEFAULT_HEAD_OPEN = '<!--shiso-default-head-->';
|
|
21
28
|
const DEFAULT_HEAD_CLOSE = '<!--/shiso-default-head-->';
|
|
@@ -31,8 +38,17 @@ if (!template.includes('<!--app-html-->')) {
|
|
|
31
38
|
);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
const {
|
|
35
|
-
|
|
41
|
+
const {
|
|
42
|
+
render,
|
|
43
|
+
getRoutes,
|
|
44
|
+
getRedirects,
|
|
45
|
+
getSitemapEntries,
|
|
46
|
+
getMarkdownPages,
|
|
47
|
+
getLlmsPages,
|
|
48
|
+
docsHomeUrl,
|
|
49
|
+
siteName,
|
|
50
|
+
siteDescription,
|
|
51
|
+
} = await import(pathToFileURL(path.join(root, 'dist', 'server', 'entry-server.js')).href);
|
|
36
52
|
|
|
37
53
|
/** Vite's `base`, normalized to "" or "/prefix". */
|
|
38
54
|
function readBase() {
|
|
@@ -104,6 +120,25 @@ if (docsHomeUrl && docsHomeUrl !== '/' && !routes.includes('/')) {
|
|
|
104
120
|
);
|
|
105
121
|
}
|
|
106
122
|
|
|
123
|
+
// Pages bound to an API operation publish the generated reference as markdown
|
|
124
|
+
// too, so the .md copies and llms-full.txt stay useful to AI tools.
|
|
125
|
+
let openApiByKey;
|
|
126
|
+
{
|
|
127
|
+
const docsConfig = (await loadDocsConfig({ root, expandGlobs: false })).config;
|
|
128
|
+
if (docsConfig.api?.spec) {
|
|
129
|
+
const { spec } = await loadOpenApiSpec({ root, specPath: docsConfig.api.spec });
|
|
130
|
+
openApiByKey = new Map(normalizeOperations(spec).map(operation => [operation.key, operation]));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function withOperationMarkdown(source) {
|
|
135
|
+
if (!openApiByKey) return source;
|
|
136
|
+
const frontmatter = source.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1] || '';
|
|
137
|
+
const key = normalizeOperationKey(frontmatter.match(/^openapi:\s*(.+)$/m)?.[1]);
|
|
138
|
+
const operation = key ? openApiByKey.get(key) : undefined;
|
|
139
|
+
return operation ? `${source.trimEnd()}\n\n${operationToMarkdown(operation)}\n` : source;
|
|
140
|
+
}
|
|
141
|
+
|
|
107
142
|
// Raw markdown next to every page: "/docs/installation" -> "docs/installation.md".
|
|
108
143
|
// Served for the contextual menu's copy/view options and for AI tools.
|
|
109
144
|
const markdownPages = getMarkdownPages();
|
|
@@ -111,9 +146,52 @@ const markdownPages = getMarkdownPages();
|
|
|
111
146
|
for (const { route, filePath } of markdownPages) {
|
|
112
147
|
const source = await readFile(path.join(root, ...filePath.split('/').filter(Boolean)), 'utf8');
|
|
113
148
|
const relative = withBase(route).replace(/^\//, '') || 'index';
|
|
114
|
-
await writePage(path.join(clientDir, `${relative}.md`), source);
|
|
149
|
+
await writePage(path.join(clientDir, `${relative}.md`), withOperationMarkdown(source));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// AI discovery files. llms.txt is the concise, ordered map; llms-full.txt is
|
|
153
|
+
// the same public corpus concatenated for tools that prefer one fetch.
|
|
154
|
+
const llmsPages = getLlmsPages();
|
|
155
|
+
|
|
156
|
+
function markdownHref(route) {
|
|
157
|
+
const relative = withBase(route).replace(/^\//, '') || 'index';
|
|
158
|
+
return `/${relative}.md`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const llmsHeader = [
|
|
162
|
+
`# ${siteName || 'Documentation'}`,
|
|
163
|
+
siteDescription ? `> ${siteDescription}` : null,
|
|
164
|
+
]
|
|
165
|
+
.filter(Boolean)
|
|
166
|
+
.join('\n\n');
|
|
167
|
+
const llmsLinks = llmsPages
|
|
168
|
+
.map(
|
|
169
|
+
page =>
|
|
170
|
+
`- [${page.title}](${markdownHref(page.route)})${page.description ? `: ${page.description}` : ''}`,
|
|
171
|
+
)
|
|
172
|
+
.join('\n');
|
|
173
|
+
|
|
174
|
+
await writePage(
|
|
175
|
+
path.join(clientDir, 'llms.txt'),
|
|
176
|
+
`${llmsHeader}\n\n## Documentation\n\n${llmsLinks}\n`,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const llmsFullSections = [];
|
|
180
|
+
for (const page of llmsPages) {
|
|
181
|
+
const source = await readFile(
|
|
182
|
+
path.join(root, ...page.filePath.split('/').filter(Boolean)),
|
|
183
|
+
'utf8',
|
|
184
|
+
);
|
|
185
|
+
llmsFullSections.push(
|
|
186
|
+
[`# ${page.title}`, `Source: ${markdownHref(page.route)}`, source.trim()].join('\n\n'),
|
|
187
|
+
);
|
|
115
188
|
}
|
|
116
189
|
|
|
190
|
+
await writePage(
|
|
191
|
+
path.join(clientDir, 'llms-full.txt'),
|
|
192
|
+
`${llmsHeader}\n\n${llmsFullSections.join('\n\n---\n\n')}\n`,
|
|
193
|
+
);
|
|
194
|
+
|
|
117
195
|
// Redirect pages. Static hosting cannot serve real 301s, so each redirect
|
|
118
196
|
// gets the same canonical + meta refresh + immediate replace treatment as
|
|
119
197
|
// the root entry. Real pages always win over redirect rules.
|
|
@@ -187,6 +265,8 @@ if (stray.length) {
|
|
|
187
265
|
const extras = [
|
|
188
266
|
redirects.length ? `${redirects.length} redirects` : null,
|
|
189
267
|
sitemapEntries.length ? 'sitemap.xml' : null,
|
|
268
|
+
'llms.txt',
|
|
269
|
+
'llms-full.txt',
|
|
190
270
|
]
|
|
191
271
|
.filter(Boolean)
|
|
192
272
|
.join(', ');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { loadDocsConfig } from './load-docs-config.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { loadShisoConfig, SHISO_CONFIG_FILES } from './load-shiso-config.mjs';
|
|
4
4
|
|
|
5
5
|
export const VIRTUAL_DOCS_CONFIG_ID = 'virtual:shiso-docs-config';
|
|
6
6
|
export const VIRTUAL_SHISO_CONFIG_ID = 'virtual:shiso-config';
|
|
@@ -11,6 +11,13 @@ function renderConfigModule(config) {
|
|
|
11
11
|
return `export default ${JSON.stringify(config)};`;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function renderShisoConfigModule(config) {
|
|
15
|
+
// Compiler plugins are functions used by vite.config.ts and cannot be
|
|
16
|
+
// serialized into the virtual module consumed by the browser runtime.
|
|
17
|
+
const { mdx: _mdx, ...runtimeConfig } = config;
|
|
18
|
+
return renderConfigModule(runtimeConfig);
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
22
|
* Creates the single config state shared by a Vite build and application
|
|
16
23
|
* modules. Two virtual modules keep Node-only file loading out of the browser
|
|
@@ -35,6 +42,7 @@ export async function createDocsConfigModule({
|
|
|
35
42
|
return {
|
|
36
43
|
getConfig: () => loaded.config,
|
|
37
44
|
getShisoConfig: () => loadedShiso.config,
|
|
45
|
+
getSpecPath: () => loaded.specPath,
|
|
38
46
|
getSourcePaths: () => [...loaded.sourcePaths, ...loadedShiso.sourcePaths],
|
|
39
47
|
sourcePath: loaded.sourcePath,
|
|
40
48
|
shisoSourcePath: loadedShiso.sourcePath,
|
|
@@ -64,26 +72,39 @@ export async function createDocsConfigModule({
|
|
|
64
72
|
for (const sourcePath of loadedShiso.sourcePaths) {
|
|
65
73
|
this.addWatchFile(sourcePath);
|
|
66
74
|
}
|
|
67
|
-
return
|
|
75
|
+
return renderShisoConfigModule(loadedShiso.config);
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
return undefined;
|
|
71
79
|
},
|
|
72
80
|
async handleHotUpdate(context) {
|
|
73
81
|
const changedPath = path.resolve(context.file);
|
|
74
|
-
const isDocsSource =
|
|
82
|
+
const isDocsSource =
|
|
83
|
+
loaded.sourcePaths.includes(changedPath) || changedPath === loaded.specPath;
|
|
75
84
|
const isShisoSource = shisoCandidatePaths.includes(changedPath);
|
|
85
|
+
const contentRoot = path.resolve(root, loadedShiso.config.contentDir);
|
|
86
|
+
const relativeContentPath = path.relative(contentRoot, changedPath);
|
|
87
|
+
const isGlobContent =
|
|
88
|
+
loaded.hasGlobs &&
|
|
89
|
+
/\.(?:md|mdx)$/.test(changedPath) &&
|
|
90
|
+
relativeContentPath !== '..' &&
|
|
91
|
+
!relativeContentPath.startsWith(`..${path.sep}`) &&
|
|
92
|
+
!path.isAbsolute(relativeContentPath);
|
|
76
93
|
|
|
77
|
-
if (!isDocsSource && !isShisoSource) {
|
|
94
|
+
if (!isDocsSource && !isShisoSource && !isGlobContent) {
|
|
78
95
|
return;
|
|
79
96
|
}
|
|
80
97
|
|
|
81
|
-
const resolvedId =
|
|
98
|
+
const resolvedId =
|
|
99
|
+
isDocsSource || isGlobContent ? RESOLVED_DOCS_CONFIG_ID : RESOLVED_SHISO_CONFIG_ID;
|
|
82
100
|
|
|
83
|
-
if (isDocsSource) {
|
|
101
|
+
if (isDocsSource || isGlobContent) {
|
|
84
102
|
loaded = await loadDocsConfig(options);
|
|
85
103
|
} else {
|
|
86
104
|
loadedShiso = await loadShisoConfig({ root });
|
|
105
|
+
// contentDir may have changed, so glob expansion must use the new
|
|
106
|
+
// location before the full reload.
|
|
107
|
+
loaded = await loadDocsConfig(options);
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
const configModule = context.server.moduleGraph.getModuleById(resolvedId);
|
package/src/App.tsx
CHANGED
|
@@ -1,20 +1,97 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type ComponentProps, type CSSProperties, useRef, useState } from 'react';
|
|
2
3
|
import { CheckIcon, Copy } from '@/components/icons';
|
|
3
4
|
import { Button } from '@/components/ui/button';
|
|
4
5
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
import { Mermaid, type MermaidPlacement } from './docs/Mermaid';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Renders a fenced code block. The `data-*` props are produced at build time by
|
|
11
|
+
* `lib/rehype-shiki.ts`; see that file for the markup contract.
|
|
12
|
+
*/
|
|
13
|
+
export interface CodeBlockProps extends ComponentProps<'pre'> {
|
|
14
|
+
'data-title'?: string;
|
|
15
|
+
'data-language'?: string;
|
|
16
|
+
'data-line-numbers'?: string;
|
|
17
|
+
'data-line-start'?: string;
|
|
18
|
+
'data-line-count'?: string;
|
|
19
|
+
'data-diff-markers'?: string;
|
|
20
|
+
'data-placement'?: MermaidPlacement;
|
|
21
|
+
'data-actions'?: string;
|
|
9
22
|
}
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
function reactChildrenToText(node: ReactNode): string {
|
|
25
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
26
|
+
return String(node);
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(node)) {
|
|
29
|
+
return node.map(reactChildrenToText).join('');
|
|
30
|
+
}
|
|
31
|
+
if (node && typeof node === 'object' && 'props' in (node as object)) {
|
|
32
|
+
const props = (node as { props?: { children?: ReactNode; value?: unknown } }).props;
|
|
33
|
+
if (typeof props?.value === 'string') {
|
|
34
|
+
return props.value;
|
|
35
|
+
}
|
|
36
|
+
return reactChildrenToText(props?.children);
|
|
37
|
+
}
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Joins the text of each rendered line. Lines marked as removed by
|
|
43
|
+
* `// [!code --]` are skipped so the clipboard holds the "after" state; in a
|
|
44
|
+
* `diff` block the +/- lines are content and are copied verbatim.
|
|
45
|
+
*/
|
|
46
|
+
function copyText(pre: HTMLPreElement | null, language?: string): string {
|
|
47
|
+
if (!pre) {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const lines = [...pre.querySelectorAll<HTMLElement>('.line')];
|
|
52
|
+
if (!lines.length) {
|
|
53
|
+
return pre.textContent || '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return lines
|
|
57
|
+
.filter(line => language === 'diff' || line.dataset.diff !== 'remove')
|
|
58
|
+
.map(line => line.textContent || '')
|
|
59
|
+
.join('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function CodeBlock({ children, className, style, ...rest }: CodeBlockProps) {
|
|
63
|
+
const {
|
|
64
|
+
'data-title': title,
|
|
65
|
+
'data-language': language,
|
|
66
|
+
'data-line-start': lineStart,
|
|
67
|
+
'data-line-count': lineCount,
|
|
68
|
+
'data-placement': placement,
|
|
69
|
+
'data-actions': actions,
|
|
70
|
+
...preProps
|
|
71
|
+
} = rest;
|
|
12
72
|
const textInput = useRef<HTMLPreElement>(null);
|
|
13
73
|
const [copied, setCopied] = useState(false);
|
|
14
74
|
|
|
75
|
+
// ```mermaid fences render as diagrams; the raw definition stays in the
|
|
76
|
+
// markup for search indexing and no-JS fallbacks.
|
|
77
|
+
if (language === 'mermaid') {
|
|
78
|
+
return (
|
|
79
|
+
<Mermaid
|
|
80
|
+
chart={reactChildrenToText(children).replace(/\n$/, '')}
|
|
81
|
+
title={title}
|
|
82
|
+
placement={placement}
|
|
83
|
+
actions={actions === undefined ? undefined : actions !== 'false'}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const start = Number(lineStart) || 1;
|
|
89
|
+
const lastLine = start + Math.max(Number(lineCount) || 1, 1) - 1;
|
|
90
|
+
const gutter = `${String(lastLine).length}ch`;
|
|
91
|
+
|
|
15
92
|
const handleCopy = () => {
|
|
16
93
|
setCopied(true);
|
|
17
|
-
navigator?.clipboard?.writeText(textInput.current
|
|
94
|
+
navigator?.clipboard?.writeText(copyText(textInput.current, language));
|
|
18
95
|
|
|
19
96
|
setTimeout(() => {
|
|
20
97
|
setCopied(false);
|
|
@@ -23,10 +100,30 @@ export function CodeBlock({ children, className }: CodeBlockProps) {
|
|
|
23
100
|
|
|
24
101
|
return (
|
|
25
102
|
<div data-slot="code-block" className="relative my-5 overflow-hidden rounded-lg bg-card">
|
|
103
|
+
{title ? (
|
|
104
|
+
<div
|
|
105
|
+
data-slot="code-block-header"
|
|
106
|
+
className="flex h-9 items-center border-border border-b px-3 pr-12 font-mono text-muted-foreground text-xs"
|
|
107
|
+
>
|
|
108
|
+
{title}
|
|
109
|
+
</div>
|
|
110
|
+
) : null}
|
|
26
111
|
<ScrollArea scrollbars="horizontal" className="w-full">
|
|
27
112
|
<pre
|
|
28
113
|
ref={textInput}
|
|
29
|
-
|
|
114
|
+
{...preProps}
|
|
115
|
+
data-language={language}
|
|
116
|
+
style={
|
|
117
|
+
{
|
|
118
|
+
...style,
|
|
119
|
+
counterReset: `line ${start - 1}`,
|
|
120
|
+
'--code-gutter': gutter,
|
|
121
|
+
} as CSSProperties
|
|
122
|
+
}
|
|
123
|
+
className={cn(
|
|
124
|
+
'code-block w-max min-w-full py-3 font-mono text-foreground text-sm leading-[1.6]',
|
|
125
|
+
className,
|
|
126
|
+
)}
|
|
30
127
|
>
|
|
31
128
|
{children}
|
|
32
129
|
</pre>
|
|
@@ -35,7 +132,10 @@ export function CodeBlock({ children, className }: CodeBlockProps) {
|
|
|
35
132
|
type="button"
|
|
36
133
|
variant="ghost"
|
|
37
134
|
size="icon-sm"
|
|
38
|
-
className=
|
|
135
|
+
className={cn(
|
|
136
|
+
'absolute right-3 inline-flex size-7 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
|
137
|
+
title ? 'top-1' : 'top-2.5',
|
|
138
|
+
)}
|
|
39
139
|
onClick={handleCopy}
|
|
40
140
|
aria-label="Copy code"
|
|
41
141
|
>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Link } from 'react-router';
|
|
2
2
|
import { ContextualMenu } from '@/components/ContextualMenu';
|
|
3
|
+
import { Badge } from '@/components/docs/Badge';
|
|
3
4
|
import { ArrowLeft, ArrowRight, FileText } from '@/components/icons';
|
|
5
|
+
import { OpenApiOperation } from '@/components/OpenApiOperation';
|
|
4
6
|
import { getLastModified } from '@/lib/content';
|
|
5
7
|
import { getScopeForPage } from '@/lib/docs-config';
|
|
6
8
|
import { resolveLocale } from '@/lib/locale';
|
|
9
|
+
import { getOperation, methodColor } from '@/lib/openapi';
|
|
7
10
|
import { docsSite, getPageByPathname } from '@/lib/site-config';
|
|
8
11
|
import { resolveContextualOptions } from '@/lib/site-model';
|
|
9
12
|
import type { DocModule, NormalizedDocsPage, RelatedEntry, SiteModel } from '@/lib/types';
|
|
@@ -81,6 +84,7 @@ export function DocContent({ page, doc, site }: DocContentProps) {
|
|
|
81
84
|
: page.section;
|
|
82
85
|
const contextualOptions = resolveContextualOptions(site.contextualOptions, page, site.labels);
|
|
83
86
|
const related = resolveRelated(doc.frontmatter?.related);
|
|
87
|
+
const operation = getOperation(doc.frontmatter?.openapi);
|
|
84
88
|
// Dates follow the page's language when it is a valid locale code.
|
|
85
89
|
const dateFormat = new Intl.DateTimeFormat(resolveLocale(page.language, site.locale), {
|
|
86
90
|
dateStyle: 'medium',
|
|
@@ -111,12 +115,26 @@ export function DocContent({ page, doc, site }: DocContentProps) {
|
|
|
111
115
|
)}
|
|
112
116
|
<ContextualMenu options={contextualOptions} labels={site.labels} />
|
|
113
117
|
</div>
|
|
118
|
+
{operation && (
|
|
119
|
+
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
120
|
+
<Badge color={methodColor(operation.method)} size="sm" className="font-mono">
|
|
121
|
+
{operation.method}
|
|
122
|
+
</Badge>
|
|
123
|
+
<code className="font-mono text-muted-foreground text-sm">{operation.path}</code>
|
|
124
|
+
{operation.deprecated && (
|
|
125
|
+
<Badge color="red" size="sm" stroke>
|
|
126
|
+
deprecated
|
|
127
|
+
</Badge>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
114
131
|
{description && (
|
|
115
132
|
<p className="mt-3 mb-8 text-lg text-muted-foreground leading-relaxed">{description}</p>
|
|
116
133
|
)}
|
|
117
134
|
<div className="docs-markdown">
|
|
118
135
|
<Content />
|
|
119
136
|
</div>
|
|
137
|
+
{operation && <OpenApiOperation operation={operation} />}
|
|
120
138
|
{lastModified && (
|
|
121
139
|
<div className="mt-8 text-sm text-muted-foreground">
|
|
122
140
|
{site.labels.lastUpdated}{' '}
|
package/src/components/Docs.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useLocation, useNavigate } from 'react-router';
|
|
3
3
|
import { DocContent } from '@/components/DocContent';
|
|
4
|
+
import { PanelProvider, usePanelContent } from '@/components/docs/panel-context';
|
|
4
5
|
import { Footer } from '@/components/Footer';
|
|
5
6
|
import { Menu } from '@/components/icons';
|
|
6
7
|
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
|
|
@@ -11,8 +12,9 @@ import { Button } from '@/components/ui/button';
|
|
|
11
12
|
import { Sheet, SheetContent, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
|
12
13
|
import { VersionSwitcher } from '@/components/VersionSwitcher';
|
|
13
14
|
import { renderInlineMarkdown } from '@/lib/inline-markdown';
|
|
15
|
+
import { getOperation, operationSections } from '@/lib/openapi';
|
|
14
16
|
import { docsHomeUrl, getScopeByPathname } from '@/lib/site-config';
|
|
15
|
-
import type { DocModule, NormalizedDocsPage, SiteModel } from '@/lib/types';
|
|
17
|
+
import type { DocModule, NormalizedDocsPage, SiteModel, TocEntry } from '@/lib/types';
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* 404 view driven by the `errors.404` config key. The standard defaults to
|
|
@@ -45,11 +47,46 @@ export interface DocsProps {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export function Docs({ page, doc, site }: DocsProps) {
|
|
50
|
+
if (!page || !doc) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="flex min-h-full flex-col">
|
|
53
|
+
<div className="grow">
|
|
54
|
+
<NotFound site={site} />
|
|
55
|
+
</div>
|
|
56
|
+
<Footer footer={site.footer} />
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// API reference pages append their generated section anchors to the TOC.
|
|
62
|
+
const operation = getOperation(doc.frontmatter?.openapi);
|
|
63
|
+
const toc = operation ? [...(doc.toc || []), ...operationSections(operation)] : doc.toc;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<PanelProvider>
|
|
67
|
+
<DocsBody page={page} doc={doc} site={site} toc={toc} />
|
|
68
|
+
</PanelProvider>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function DocsBody({
|
|
73
|
+
page,
|
|
74
|
+
doc,
|
|
75
|
+
site,
|
|
76
|
+
toc,
|
|
77
|
+
}: {
|
|
78
|
+
page: NormalizedDocsPage;
|
|
79
|
+
doc: DocModule;
|
|
80
|
+
site: SiteModel;
|
|
81
|
+
toc: TocEntry[] | undefined;
|
|
82
|
+
}) {
|
|
48
83
|
const { pathname } = useLocation();
|
|
49
84
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
50
85
|
// Navigation follows the scope (version/language) that owns the current page.
|
|
51
86
|
const scopeDocs = getScopeByPathname(pathname).docs;
|
|
52
87
|
const { tabs, navigation } = scopeDocs;
|
|
88
|
+
// A <Panel> in the page replaces the table of contents in the right rail.
|
|
89
|
+
const panel = usePanelContent();
|
|
53
90
|
|
|
54
91
|
// Close the mobile menu and start each newly loaded page at the top. Hash
|
|
55
92
|
// links keep their native section-scrolling behavior.
|
|
@@ -61,17 +98,6 @@ export function Docs({ page, doc, site }: DocsProps) {
|
|
|
61
98
|
}
|
|
62
99
|
}, [pathname]);
|
|
63
100
|
|
|
64
|
-
if (!page || !doc) {
|
|
65
|
-
return (
|
|
66
|
-
<div className="flex min-h-full flex-col">
|
|
67
|
-
<div className="grow">
|
|
68
|
-
<NotFound site={site} />
|
|
69
|
-
</div>
|
|
70
|
-
<Footer footer={site.footer} />
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
101
|
return (
|
|
76
102
|
<div className="flex min-h-full flex-col gap-6 lg:gap-0">
|
|
77
103
|
<Sheet open={menuOpen} onOpenChange={setMenuOpen}>
|
|
@@ -123,11 +149,13 @@ export function Docs({ page, doc, site }: DocsProps) {
|
|
|
123
149
|
<div className="flex grow items-start gap-12">
|
|
124
150
|
<DocContent page={page} doc={doc} site={site} />
|
|
125
151
|
<div className="hidden min-w-0 max-w-60 basis-60 self-start lg:sticky lg:top-[calc(var(--header-height)+1.5rem)] lg:block lg:shrink-0">
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
{panel ?? (
|
|
153
|
+
<PageLinks
|
|
154
|
+
items={toc}
|
|
155
|
+
title={site.labels.tableOfContents}
|
|
156
|
+
navigationLabel={site.labels.tableOfContentsNavigation}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
131
159
|
</div>
|
|
132
160
|
</div>
|
|
133
161
|
<Footer footer={site.footer} className="lg:mr-72" />
|