blodemd 0.0.4 → 0.0.6
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/README.md +12 -1
- package/dev-server/app/[[...slug]]/page.tsx +139 -0
- package/dev-server/app/blodemd-dev/invalidate/route.ts +12 -0
- package/dev-server/app/blodemd-dev/static/[...path]/route.ts +32 -0
- package/dev-server/app/blodemd-dev/version/route.ts +14 -0
- package/dev-server/app/blodemd-internal/proxy/route.ts +86 -0
- package/dev-server/app/error.tsx +24 -0
- package/dev-server/app/globals.css +4 -0
- package/dev-server/app/layout.tsx +38 -0
- package/dev-server/app/not-found.tsx +18 -0
- package/dev-server/app/search/route.ts +17 -0
- package/dev-server/components/dev-reload-script.tsx +86 -0
- package/dev-server/components/providers.tsx +15 -0
- package/dev-server/lib/dev-state.ts +8 -0
- package/dev-server/lib/local-content-source.ts +103 -0
- package/dev-server/lib/local-runtime.tsx +558 -0
- package/dev-server/next.config.js +46 -0
- package/dev-server/package.json +57 -0
- package/dev-server/postcss.config.mjs +7 -0
- package/dev-server/public/glide-variable.woff2 +0 -0
- package/dev-server/tsconfig.json +49 -0
- package/dist/cli.mjs +299 -26
- package/dist/cli.mjs.map +1 -1
- package/docs/app/globals.css +455 -0
- package/docs/components/api/api-playground.tsx +295 -0
- package/docs/components/api/api-reference.tsx +121 -0
- package/docs/components/content/collection-index.tsx +114 -0
- package/docs/components/docs/contextual-menu.tsx +406 -0
- package/docs/components/docs/copy-page-menu.tsx +255 -0
- package/docs/components/docs/doc-header.tsx +192 -0
- package/docs/components/docs/doc-shell.tsx +289 -0
- package/docs/components/docs/doc-sidebar.tsx +206 -0
- package/docs/components/docs/doc-toc.tsx +45 -0
- package/docs/components/docs/mobile-nav.tsx +207 -0
- package/docs/components/mdx/accordion.tsx +83 -0
- package/docs/components/mdx/badge.tsx +79 -0
- package/docs/components/mdx/callout.tsx +88 -0
- package/docs/components/mdx/card.tsx +104 -0
- package/docs/components/mdx/code-block.tsx +75 -0
- package/docs/components/mdx/code-group.tsx +94 -0
- package/docs/components/mdx/color.tsx +87 -0
- package/docs/components/mdx/columns.tsx +25 -0
- package/docs/components/mdx/expandable.tsx +45 -0
- package/docs/components/mdx/field-layout.tsx +77 -0
- package/docs/components/mdx/frame.tsx +23 -0
- package/docs/components/mdx/get-text-content.ts +18 -0
- package/docs/components/mdx/icon.tsx +56 -0
- package/docs/components/mdx/index.tsx +96 -0
- package/docs/components/mdx/installer.tsx +20 -0
- package/docs/components/mdx/panel.tsx +11 -0
- package/docs/components/mdx/param-field.tsx +56 -0
- package/docs/components/mdx/preview.tsx +36 -0
- package/docs/components/mdx/prompt.tsx +63 -0
- package/docs/components/mdx/request-example.tsx +27 -0
- package/docs/components/mdx/response-field.tsx +42 -0
- package/docs/components/mdx/steps.tsx +92 -0
- package/docs/components/mdx/tabs.tsx +88 -0
- package/docs/components/mdx/tile.tsx +43 -0
- package/docs/components/mdx/tooltip.tsx +71 -0
- package/docs/components/mdx/tree.tsx +120 -0
- package/docs/components/mdx/type-table.tsx +71 -0
- package/docs/components/mdx/update.tsx +44 -0
- package/docs/components/mdx/video.tsx +12 -0
- package/docs/components/mdx/view.tsx +66 -0
- package/docs/components/providers.tsx +15 -0
- package/docs/components/ui/breadcrumb.tsx +92 -0
- package/docs/components/ui/button.tsx +90 -0
- package/docs/components/ui/card.tsx +92 -0
- package/docs/components/ui/command.tsx +139 -0
- package/docs/components/ui/dialog.tsx +97 -0
- package/docs/components/ui/field.tsx +237 -0
- package/docs/components/ui/input.tsx +105 -0
- package/docs/components/ui/label.tsx +22 -0
- package/docs/components/ui/popover.tsx +72 -0
- package/docs/components/ui/search.tsx +380 -0
- package/docs/components/ui/separator.tsx +26 -0
- package/docs/components/ui/sheet.tsx +104 -0
- package/docs/components/ui/sidebar.tsx +433 -0
- package/docs/components/ui/theme-toggle.tsx +62 -0
- package/docs/components/ui/tooltip.tsx +53 -0
- package/docs/lib/contextual-options.ts +193 -0
- package/docs/lib/docs-collection.ts +22 -0
- package/docs/lib/mdx.ts +90 -0
- package/docs/lib/navigation.ts +288 -0
- package/docs/lib/openapi.ts +158 -0
- package/docs/lib/routes.ts +10 -0
- package/docs/lib/server-cache.ts +83 -0
- package/docs/lib/shiki.ts +35 -0
- package/docs/lib/theme.ts +29 -0
- package/docs/lib/toc.ts +2 -0
- package/docs/lib/utils.ts +5 -0
- package/package.json +34 -3
- package/packages/@repo/common/dist/index.d.ts +9 -0
- package/packages/@repo/common/dist/index.d.ts.map +1 -0
- package/packages/@repo/common/dist/index.js +42 -0
- package/packages/@repo/common/package.json +34 -0
- package/packages/@repo/common/src/common.unit.test.ts +55 -0
- package/packages/@repo/common/src/index.ts +51 -0
- package/packages/@repo/contracts/dist/api-key.d.ts +30 -0
- package/packages/@repo/contracts/dist/api-key.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/api-key.js +20 -0
- package/packages/@repo/contracts/dist/dates.d.ts +4 -0
- package/packages/@repo/contracts/dist/dates.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/dates.js +2 -0
- package/packages/@repo/contracts/dist/deployment.d.ts +71 -0
- package/packages/@repo/contracts/dist/deployment.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/deployment.js +46 -0
- package/packages/@repo/contracts/dist/domain.d.ts +94 -0
- package/packages/@repo/contracts/dist/domain.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/domain.js +36 -0
- package/packages/@repo/contracts/dist/ids.d.ts +14 -0
- package/packages/@repo/contracts/dist/ids.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/ids.js +10 -0
- package/packages/@repo/contracts/dist/index.d.ts +10 -0
- package/packages/@repo/contracts/dist/index.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/index.js +11 -0
- package/packages/@repo/contracts/dist/pagination.d.ts +23 -0
- package/packages/@repo/contracts/dist/pagination.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/pagination.js +15 -0
- package/packages/@repo/contracts/dist/project.d.ts +25 -0
- package/packages/@repo/contracts/dist/project.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/project.js +23 -0
- package/packages/@repo/contracts/dist/tenant.d.ts +99 -0
- package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/tenant.js +36 -0
- package/packages/@repo/contracts/dist/user.d.ts +9 -0
- package/packages/@repo/contracts/dist/user.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/user.js +9 -0
- package/packages/@repo/contracts/package.json +37 -0
- package/packages/@repo/contracts/src/api-key.ts +27 -0
- package/packages/@repo/contracts/src/dates.ts +4 -0
- package/packages/@repo/contracts/src/deployment.ts +73 -0
- package/packages/@repo/contracts/src/domain.ts +51 -0
- package/packages/@repo/contracts/src/ids.ts +22 -0
- package/packages/@repo/contracts/src/index.ts +11 -0
- package/packages/@repo/contracts/src/pagination.ts +21 -0
- package/packages/@repo/contracts/src/project.ts +30 -0
- package/packages/@repo/contracts/src/tenant.ts +54 -0
- package/packages/@repo/contracts/src/user.ts +12 -0
- package/packages/@repo/models/dist/docs-config.d.ts +985 -0
- package/packages/@repo/models/dist/docs-config.d.ts.map +1 -0
- package/packages/@repo/models/dist/docs-config.js +548 -0
- package/packages/@repo/models/dist/index.d.ts +3 -0
- package/packages/@repo/models/dist/index.d.ts.map +1 -0
- package/packages/@repo/models/dist/index.js +3 -0
- package/packages/@repo/models/dist/tenant.d.ts +25 -0
- package/packages/@repo/models/dist/tenant.d.ts.map +1 -0
- package/packages/@repo/models/dist/tenant.js +1 -0
- package/packages/@repo/models/package.json +37 -0
- package/packages/@repo/models/src/docs-config.ts +648 -0
- package/packages/@repo/models/src/index.ts +3 -0
- package/packages/@repo/models/src/tenant.ts +29 -0
- package/packages/@repo/prebuild/dist/index.d.ts +2 -0
- package/packages/@repo/prebuild/dist/index.d.ts.map +1 -0
- package/packages/@repo/prebuild/dist/index.js +2 -0
- package/packages/@repo/prebuild/dist/openapi.d.ts +43 -0
- package/packages/@repo/prebuild/dist/openapi.d.ts.map +1 -0
- package/packages/@repo/prebuild/dist/openapi.js +58 -0
- package/packages/@repo/prebuild/package.json +39 -0
- package/packages/@repo/prebuild/src/index.ts +2 -0
- package/packages/@repo/prebuild/src/openapi.ts +116 -0
- package/packages/@repo/previewing/dist/blob-source.d.ts +16 -0
- package/packages/@repo/previewing/dist/blob-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/blob-source.js +110 -0
- package/packages/@repo/previewing/dist/content-source.d.ts +12 -0
- package/packages/@repo/previewing/dist/content-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/content-source.js +1 -0
- package/packages/@repo/previewing/dist/fs-source.d.ts +11 -0
- package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/fs-source.js +79 -0
- package/packages/@repo/previewing/dist/index.d.ts +120 -0
- package/packages/@repo/previewing/dist/index.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/index.js +984 -0
- package/packages/@repo/previewing/package.json +41 -0
- package/packages/@repo/previewing/src/blob-source.ts +167 -0
- package/packages/@repo/previewing/src/content-source.ts +12 -0
- package/packages/@repo/previewing/src/fs-source.ts +111 -0
- package/packages/@repo/previewing/src/index.ts +1490 -0
- package/packages/@repo/previewing/src/index.unit.test.ts +290 -0
- package/packages/@repo/validation/dist/index.d.ts +12 -0
- package/packages/@repo/validation/dist/index.d.ts.map +1 -0
- package/packages/@repo/validation/dist/index.js +30 -0
- package/packages/@repo/validation/package.json +37 -0
- package/packages/@repo/validation/src/index.ts +59 -0
- package/packages/@repo/validation/src/mintlify-docs-schema.json +5016 -0
|
@@ -0,0 +1,984 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ensureArray, normalizePath, slugify } from "@repo/common";
|
|
3
|
+
import { PageModeSchema } from "@repo/models";
|
|
4
|
+
import { extractOpenApiOperations, openApiIdentifier, openApiSlug, parseOpenApiSpec, } from "@repo/prebuild";
|
|
5
|
+
import { validateDocsConfig, validateFrontmatter } from "@repo/validation";
|
|
6
|
+
import YAML from "yaml";
|
|
7
|
+
export { BlobContentSource, createBlobSource } from "./blob-source.js";
|
|
8
|
+
export { createFsSource, FsContentSource } from "./fs-source.js";
|
|
9
|
+
export const PREBUILT_INDEX_PATH = "_content-index.json";
|
|
10
|
+
export const PREBUILT_OPENAPI_INDEX_PATH = "_openapi-index.json";
|
|
11
|
+
export const PREBUILT_SEARCH_INDEX_PATH = "_search-index.json";
|
|
12
|
+
export const PREBUILT_TOC_INDEX_PATH = "_toc-index.json";
|
|
13
|
+
export const PREBUILT_UTILITY_INDEX_PATH = "_utility-index.json";
|
|
14
|
+
export const PREBUILT_UTILITY_SITEMAP_PATH = "_utility/sitemap.xml";
|
|
15
|
+
export const PREBUILT_UTILITY_LLMS_PATH = "_utility/llms.txt";
|
|
16
|
+
export const PREBUILT_UTILITY_LLMS_FULL_PATH = "_utility/llms-full.txt";
|
|
17
|
+
export const UTILITY_DOCS_ROOT_TOKEN = "__BLODEMD_DOCS_ROOT__";
|
|
18
|
+
const validModes = new Set(PageModeSchema.options);
|
|
19
|
+
export const buildPageMetadataMap = (index) => {
|
|
20
|
+
const map = new Map();
|
|
21
|
+
for (const entry of index.entries) {
|
|
22
|
+
if (entry.kind !== "entry") {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const fm = entry.frontmatter;
|
|
26
|
+
const meta = {
|
|
27
|
+
title: entry.title,
|
|
28
|
+
};
|
|
29
|
+
const hasFields = true;
|
|
30
|
+
if (typeof fm.sidebarTitle === "string") {
|
|
31
|
+
meta.sidebarTitle = fm.sidebarTitle;
|
|
32
|
+
}
|
|
33
|
+
if (typeof fm.icon === "string") {
|
|
34
|
+
meta.icon = fm.icon;
|
|
35
|
+
}
|
|
36
|
+
if (typeof fm.iconType === "string") {
|
|
37
|
+
meta.iconType = fm.iconType;
|
|
38
|
+
}
|
|
39
|
+
if (typeof fm.tag === "string") {
|
|
40
|
+
meta.tag = fm.tag;
|
|
41
|
+
}
|
|
42
|
+
if (typeof fm.hidden === "boolean") {
|
|
43
|
+
meta.hidden = fm.hidden;
|
|
44
|
+
}
|
|
45
|
+
if (typeof fm.deprecated === "boolean") {
|
|
46
|
+
meta.deprecated = fm.deprecated;
|
|
47
|
+
}
|
|
48
|
+
if (typeof fm.url === "string") {
|
|
49
|
+
meta.url = fm.url;
|
|
50
|
+
}
|
|
51
|
+
if (typeof fm.mode === "string" && validModes.has(fm.mode)) {
|
|
52
|
+
meta.mode = fm.mode;
|
|
53
|
+
}
|
|
54
|
+
if (typeof fm.noindex === "boolean") {
|
|
55
|
+
meta.noindex = fm.noindex;
|
|
56
|
+
}
|
|
57
|
+
if (typeof fm.hideFooterPagination === "boolean") {
|
|
58
|
+
meta.hideFooterPagination = fm.hideFooterPagination;
|
|
59
|
+
}
|
|
60
|
+
if (typeof fm.hideApiMarker === "boolean") {
|
|
61
|
+
meta.hideApiMarker = fm.hideApiMarker;
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(fm.keywords)) {
|
|
64
|
+
meta.keywords = fm.keywords;
|
|
65
|
+
}
|
|
66
|
+
if (hasFields) {
|
|
67
|
+
map.set(entry.slug, meta);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return map;
|
|
71
|
+
};
|
|
72
|
+
const DOCS_CONFIG_FILE = "docs.json";
|
|
73
|
+
const DOC_FILE_EXTENSION_REGEX = /\.(mdx|md)$/;
|
|
74
|
+
const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)\n---\s*\n?/;
|
|
75
|
+
const INDEX_SUFFIX = "/index";
|
|
76
|
+
const titleFromSlug = (slug) => {
|
|
77
|
+
const clean = slug.replaceAll("-", " ").split("/").pop() ?? slug;
|
|
78
|
+
if (clean === "index") {
|
|
79
|
+
return "Overview";
|
|
80
|
+
}
|
|
81
|
+
return clean.replaceAll(/\b\w/g, (char) => char.toUpperCase());
|
|
82
|
+
};
|
|
83
|
+
const parseFrontmatter = (source) => {
|
|
84
|
+
const match = FRONTMATTER_REGEX.exec(source);
|
|
85
|
+
if (!match) {
|
|
86
|
+
return { body: source, frontmatter: {} };
|
|
87
|
+
}
|
|
88
|
+
const raw = match[1] ?? "";
|
|
89
|
+
const data = YAML.parse(raw) ?? {};
|
|
90
|
+
const body = source.slice(match[0].length);
|
|
91
|
+
return { body, frontmatter: data };
|
|
92
|
+
};
|
|
93
|
+
const slugFromFile = (relativePath) => {
|
|
94
|
+
const clean = normalizePath(relativePath);
|
|
95
|
+
const withoutExt = clean.replace(DOC_FILE_EXTENSION_REGEX, "");
|
|
96
|
+
if (withoutExt.endsWith(INDEX_SUFFIX)) {
|
|
97
|
+
const trimmed = withoutExt.slice(0, -INDEX_SUFFIX.length);
|
|
98
|
+
return trimmed.length ? trimmed : "index";
|
|
99
|
+
}
|
|
100
|
+
return withoutExt.length ? withoutExt : "index";
|
|
101
|
+
};
|
|
102
|
+
const defaultLinkLabel = (input) => {
|
|
103
|
+
if (input.label) {
|
|
104
|
+
return input.label;
|
|
105
|
+
}
|
|
106
|
+
if (input.type === "github") {
|
|
107
|
+
return "GitHub";
|
|
108
|
+
}
|
|
109
|
+
if (input.type === "discord") {
|
|
110
|
+
return "Discord";
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return new URL(input.href).hostname;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return input.href;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const buildGoogleFontsCssUrl = (fonts) => {
|
|
120
|
+
if (!fonts) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const fontEntries = [];
|
|
124
|
+
if (fonts.family) {
|
|
125
|
+
fontEntries.push({ family: fonts.family, source: fonts.source });
|
|
126
|
+
}
|
|
127
|
+
if (fonts.body) {
|
|
128
|
+
fontEntries.push({
|
|
129
|
+
family: fonts.body.family,
|
|
130
|
+
source: fonts.body.source,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (fonts.heading) {
|
|
134
|
+
fontEntries.push({
|
|
135
|
+
family: fonts.heading.family,
|
|
136
|
+
source: fonts.heading.source,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const googleFamilies = [
|
|
140
|
+
...new Set(fontEntries.filter((entry) => !entry.source).map((entry) => entry.family)),
|
|
141
|
+
];
|
|
142
|
+
if (!googleFamilies.length) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
const params = googleFamilies.map((family) => `family=${encodeURIComponent(family).replaceAll("%20", "+")}`);
|
|
146
|
+
return `https://fonts.googleapis.com/css2?${params.join("&")}&display=swap`;
|
|
147
|
+
};
|
|
148
|
+
// oxlint-disable-next-line eslint/complexity
|
|
149
|
+
const mapDocsConfig = (docs) => {
|
|
150
|
+
const navigation = {
|
|
151
|
+
global: docs.navbar?.links?.length || docs.navigation.global?.anchors?.length
|
|
152
|
+
? {
|
|
153
|
+
anchors: docs.navigation.global?.anchors?.map((anchor) => ({
|
|
154
|
+
href: anchor.href,
|
|
155
|
+
label: anchor.anchor,
|
|
156
|
+
})),
|
|
157
|
+
links: docs.navbar?.links?.map((link) => ({
|
|
158
|
+
href: link.href,
|
|
159
|
+
label: defaultLinkLabel(link),
|
|
160
|
+
})),
|
|
161
|
+
}
|
|
162
|
+
: undefined,
|
|
163
|
+
groups: docs.navigation.groups?.map((group) => ({
|
|
164
|
+
expanded: group.expanded,
|
|
165
|
+
group: group.group,
|
|
166
|
+
hidden: group.hidden,
|
|
167
|
+
pages: group.root
|
|
168
|
+
? [
|
|
169
|
+
group.root,
|
|
170
|
+
...(group.pages ?? []).filter((page) => page !== group.root),
|
|
171
|
+
]
|
|
172
|
+
: group.pages,
|
|
173
|
+
})),
|
|
174
|
+
languages: docs.navigation.languages?.map((language) => ({
|
|
175
|
+
label: language.language,
|
|
176
|
+
locale: language.language,
|
|
177
|
+
url: language.href,
|
|
178
|
+
})),
|
|
179
|
+
pages: docs.navigation.pages,
|
|
180
|
+
tabs: docs.navigation.tabs?.map((tab) => ({
|
|
181
|
+
groups: tab.groups?.map((group) => ({
|
|
182
|
+
expanded: group.expanded,
|
|
183
|
+
group: group.group,
|
|
184
|
+
hidden: group.hidden,
|
|
185
|
+
pages: group.root
|
|
186
|
+
? [
|
|
187
|
+
group.root,
|
|
188
|
+
...(group.pages ?? []).filter((page) => page !== group.root),
|
|
189
|
+
]
|
|
190
|
+
: group.pages,
|
|
191
|
+
})),
|
|
192
|
+
href: tab.href,
|
|
193
|
+
icon: tab.icon,
|
|
194
|
+
label: tab.tab,
|
|
195
|
+
pages: tab.pages,
|
|
196
|
+
})),
|
|
197
|
+
versions: docs.navigation.versions?.map((version) => ({
|
|
198
|
+
label: version.version,
|
|
199
|
+
url: version.href,
|
|
200
|
+
})),
|
|
201
|
+
};
|
|
202
|
+
const baseFontFamily = docs.fonts?.family;
|
|
203
|
+
const fonts = docs.fonts && (baseFontFamily || docs.fonts.body || docs.fonts.heading)
|
|
204
|
+
? {
|
|
205
|
+
body: docs.fonts.body?.family ?? baseFontFamily,
|
|
206
|
+
cssUrl: buildGoogleFontsCssUrl(docs.fonts),
|
|
207
|
+
heading: docs.fonts.heading?.family ?? baseFontFamily,
|
|
208
|
+
provider: "google",
|
|
209
|
+
}
|
|
210
|
+
: undefined;
|
|
211
|
+
return {
|
|
212
|
+
collections: [
|
|
213
|
+
{
|
|
214
|
+
id: "docs",
|
|
215
|
+
navigation,
|
|
216
|
+
openapi: docs.api?.openapi,
|
|
217
|
+
root: "",
|
|
218
|
+
type: "docs",
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
colors: docs.colors,
|
|
222
|
+
contextual: docs.contextual,
|
|
223
|
+
description: docs.description,
|
|
224
|
+
favicon: typeof docs.favicon === "string" ? docs.favicon : docs.favicon?.light,
|
|
225
|
+
features: {
|
|
226
|
+
rightToc: true,
|
|
227
|
+
search: true,
|
|
228
|
+
themeToggle: docs.appearance?.strict !== true,
|
|
229
|
+
toc: true,
|
|
230
|
+
},
|
|
231
|
+
fonts,
|
|
232
|
+
logo: docs.logo
|
|
233
|
+
? {
|
|
234
|
+
dark: typeof docs.logo === "string" ? docs.logo : docs.logo.dark,
|
|
235
|
+
light: typeof docs.logo === "string" ? docs.logo : docs.logo.light,
|
|
236
|
+
}
|
|
237
|
+
: undefined,
|
|
238
|
+
name: docs.name,
|
|
239
|
+
navigation,
|
|
240
|
+
openapiProxy: {
|
|
241
|
+
enabled: docs.api?.playground?.proxy !== false &&
|
|
242
|
+
Boolean(docs.api?.openapi || docs.api?.asyncapi),
|
|
243
|
+
},
|
|
244
|
+
seo: docs.seo,
|
|
245
|
+
theme: docs.theme,
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
const readJsonConfig = async (source, relativePath) => JSON.parse(await source.readFile(relativePath));
|
|
249
|
+
const normalizeRefPath = (baseDirectory, reference) => {
|
|
250
|
+
if (reference.startsWith("/") ||
|
|
251
|
+
reference.startsWith("\\") ||
|
|
252
|
+
reference.startsWith("http://") ||
|
|
253
|
+
reference.startsWith("https://")) {
|
|
254
|
+
throw new Error(`Invalid $ref "${reference}". Only relative JSON files are supported.`);
|
|
255
|
+
}
|
|
256
|
+
const normalized = normalizePath(path.posix.join(baseDirectory, reference));
|
|
257
|
+
if (!normalized ||
|
|
258
|
+
normalized === "." ||
|
|
259
|
+
normalized.startsWith("../") ||
|
|
260
|
+
normalized.includes("/../")) {
|
|
261
|
+
throw new Error(`Invalid $ref "${reference}".`);
|
|
262
|
+
}
|
|
263
|
+
return normalized;
|
|
264
|
+
};
|
|
265
|
+
const resolveJsonRefs = async (source, value, baseDirectory, seen) => {
|
|
266
|
+
if (Array.isArray(value)) {
|
|
267
|
+
return await Promise.all(value.map((item) => resolveJsonRefs(source, item, baseDirectory, seen)));
|
|
268
|
+
}
|
|
269
|
+
if (!value || typeof value !== "object") {
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
const record = value;
|
|
273
|
+
const reference = record.$ref;
|
|
274
|
+
if (typeof reference === "string") {
|
|
275
|
+
const resolvedPath = normalizeRefPath(baseDirectory, reference);
|
|
276
|
+
if (seen.has(resolvedPath)) {
|
|
277
|
+
throw new Error(`Circular $ref detected for "${resolvedPath}".`);
|
|
278
|
+
}
|
|
279
|
+
const nextSeen = new Set(seen);
|
|
280
|
+
// oxlint-disable-next-line eslint-plugin-unicorn/no-immediate-mutation
|
|
281
|
+
nextSeen.add(resolvedPath);
|
|
282
|
+
const referenced = await readJsonConfig(source, resolvedPath);
|
|
283
|
+
const referencedValue = await resolveJsonRefs(source, referenced, path.posix.dirname(resolvedPath) === "."
|
|
284
|
+
? ""
|
|
285
|
+
: normalizePath(path.posix.dirname(resolvedPath)), nextSeen);
|
|
286
|
+
const siblingEntries = Object.entries(record).filter(([key]) => key !== "$ref");
|
|
287
|
+
if (!siblingEntries.length ||
|
|
288
|
+
!referencedValue ||
|
|
289
|
+
typeof referencedValue !== "object" ||
|
|
290
|
+
Array.isArray(referencedValue)) {
|
|
291
|
+
return referencedValue;
|
|
292
|
+
}
|
|
293
|
+
const siblingValue = await resolveJsonRefs(source, Object.fromEntries(siblingEntries), baseDirectory, seen);
|
|
294
|
+
return {
|
|
295
|
+
...referencedValue,
|
|
296
|
+
...siblingValue,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const resolvedEntries = await Promise.all(Object.entries(record).map(async ([key, entryValue]) => [
|
|
300
|
+
key,
|
|
301
|
+
await resolveJsonRefs(source, entryValue, baseDirectory, seen),
|
|
302
|
+
]));
|
|
303
|
+
return Object.fromEntries(resolvedEntries);
|
|
304
|
+
};
|
|
305
|
+
const readResolvedJsonConfig = async (source, relativePath) => await resolveJsonRefs(source, await readJsonConfig(source, relativePath), path.posix.dirname(relativePath) === "."
|
|
306
|
+
? ""
|
|
307
|
+
: normalizePath(path.posix.dirname(relativePath)), new Set([relativePath]));
|
|
308
|
+
const loadDocsConfig = async (source) => {
|
|
309
|
+
if (!(await source.exists(DOCS_CONFIG_FILE))) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
const parsed = await readResolvedJsonConfig(source, DOCS_CONFIG_FILE);
|
|
314
|
+
const result = validateDocsConfig(parsed);
|
|
315
|
+
if (!result.success) {
|
|
316
|
+
return { errors: result.errors, ok: false };
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
config: mapDocsConfig(result.data),
|
|
320
|
+
ok: true,
|
|
321
|
+
warnings: [],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
return {
|
|
326
|
+
errors: [
|
|
327
|
+
error instanceof Error
|
|
328
|
+
? error.message
|
|
329
|
+
: `Failed to load ${DOCS_CONFIG_FILE}`,
|
|
330
|
+
],
|
|
331
|
+
ok: false,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
export const loadSiteConfig = async (source) => {
|
|
336
|
+
const docsConfig = await loadDocsConfig(source);
|
|
337
|
+
if (docsConfig) {
|
|
338
|
+
return docsConfig;
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
errors: [`${DOCS_CONFIG_FILE} not found.`],
|
|
342
|
+
ok: false,
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
export const loadContentSource = async (source, relativePath) => await source.readFile(relativePath);
|
|
346
|
+
const listContentFiles = async (source, directory) => {
|
|
347
|
+
const files = await source.listFiles(directory);
|
|
348
|
+
return files.filter((file) => DOC_FILE_EXTENSION_REGEX.test(file));
|
|
349
|
+
};
|
|
350
|
+
const sortDefaults = {
|
|
351
|
+
blog: { direction: "desc", field: "date" },
|
|
352
|
+
courses: { direction: "asc", field: "order" },
|
|
353
|
+
docs: { direction: "asc", field: "title" },
|
|
354
|
+
forms: { direction: "asc", field: "title" },
|
|
355
|
+
notes: { direction: "desc", field: "date" },
|
|
356
|
+
products: { direction: "asc", field: "title" },
|
|
357
|
+
sheets: { direction: "asc", field: "title" },
|
|
358
|
+
site: { direction: "asc", field: "title" },
|
|
359
|
+
slides: { direction: "asc", field: "title" },
|
|
360
|
+
todos: { direction: "desc", field: "date" },
|
|
361
|
+
};
|
|
362
|
+
const normalizeSortValue = (value) => {
|
|
363
|
+
if (typeof value === "number") {
|
|
364
|
+
return value;
|
|
365
|
+
}
|
|
366
|
+
if (typeof value === "string") {
|
|
367
|
+
const timestamp = Date.parse(value);
|
|
368
|
+
if (!Number.isNaN(timestamp)) {
|
|
369
|
+
return timestamp;
|
|
370
|
+
}
|
|
371
|
+
return value.toLowerCase();
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
};
|
|
375
|
+
const compareValues = (a, b, direction) => {
|
|
376
|
+
const left = normalizeSortValue(a);
|
|
377
|
+
const right = normalizeSortValue(b);
|
|
378
|
+
if (left === null && right === null) {
|
|
379
|
+
return 0;
|
|
380
|
+
}
|
|
381
|
+
if (left === null) {
|
|
382
|
+
return 1;
|
|
383
|
+
}
|
|
384
|
+
if (right === null) {
|
|
385
|
+
return -1;
|
|
386
|
+
}
|
|
387
|
+
const multiplier = direction === "desc" ? -1 : 1;
|
|
388
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
389
|
+
return (left - right) * multiplier;
|
|
390
|
+
}
|
|
391
|
+
if (left < right) {
|
|
392
|
+
return -1 * multiplier;
|
|
393
|
+
}
|
|
394
|
+
if (left > right) {
|
|
395
|
+
return 1 * multiplier;
|
|
396
|
+
}
|
|
397
|
+
return 0;
|
|
398
|
+
};
|
|
399
|
+
const autoIndexTypes = new Set([
|
|
400
|
+
"blog",
|
|
401
|
+
"courses",
|
|
402
|
+
"products",
|
|
403
|
+
"notes",
|
|
404
|
+
"forms",
|
|
405
|
+
"sheets",
|
|
406
|
+
"slides",
|
|
407
|
+
"todos",
|
|
408
|
+
]);
|
|
409
|
+
const getCollectionIndex = (collection, slugPrefix) => {
|
|
410
|
+
if (collection.index) {
|
|
411
|
+
return collection.index;
|
|
412
|
+
}
|
|
413
|
+
if (autoIndexTypes.has(collection.type)) {
|
|
414
|
+
const slug = slugPrefix || collection.id;
|
|
415
|
+
return {
|
|
416
|
+
slug,
|
|
417
|
+
title: titleFromSlug(slug),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
};
|
|
422
|
+
const addEntry = (entry, index, errors) => {
|
|
423
|
+
if (index.bySlug.has(entry.slug)) {
|
|
424
|
+
errors.push(`slug "${entry.slug}" is defined more than once`);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
index.entries.push(entry);
|
|
428
|
+
index.bySlug.set(entry.slug, entry);
|
|
429
|
+
};
|
|
430
|
+
const resolveEntrySlug = (relativeSlug, slugPrefix) => {
|
|
431
|
+
if (!slugPrefix) {
|
|
432
|
+
return relativeSlug;
|
|
433
|
+
}
|
|
434
|
+
if (relativeSlug === "index") {
|
|
435
|
+
return slugPrefix;
|
|
436
|
+
}
|
|
437
|
+
return normalizePath(`${slugPrefix}/${relativeSlug}`);
|
|
438
|
+
};
|
|
439
|
+
const buildEntryFromFile = async ({ collection, errors, file, root, slugPrefix, source, }) => {
|
|
440
|
+
const sourcePath = root
|
|
441
|
+
? normalizePath(path.join(root, file))
|
|
442
|
+
: normalizePath(file);
|
|
443
|
+
let entrySource = "";
|
|
444
|
+
try {
|
|
445
|
+
entrySource = await source.readFile(sourcePath);
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
errors.push(error instanceof Error ? error.message : `Failed to read ${sourcePath}`);
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
const { frontmatter } = parseFrontmatter(entrySource);
|
|
452
|
+
const frontmatterResult = validateFrontmatter(collection.type, frontmatter);
|
|
453
|
+
if (!frontmatterResult.success) {
|
|
454
|
+
for (const issue of frontmatterResult.errors) {
|
|
455
|
+
errors.push(`${sourcePath}: ${issue}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const resolvedFrontmatter = frontmatterResult.success
|
|
459
|
+
? frontmatterResult.data
|
|
460
|
+
: frontmatter;
|
|
461
|
+
const relativeSlug = slugFromFile(file);
|
|
462
|
+
const slug = resolveEntrySlug(relativeSlug, slugPrefix);
|
|
463
|
+
const title = typeof resolvedFrontmatter?.title === "string"
|
|
464
|
+
? resolvedFrontmatter.title
|
|
465
|
+
: titleFromSlug(slug);
|
|
466
|
+
const description = typeof resolvedFrontmatter?.description === "string"
|
|
467
|
+
? resolvedFrontmatter.description
|
|
468
|
+
: undefined;
|
|
469
|
+
const hidden = typeof resolvedFrontmatter?.hidden === "boolean"
|
|
470
|
+
? resolvedFrontmatter.hidden
|
|
471
|
+
: undefined;
|
|
472
|
+
return {
|
|
473
|
+
collectionId: collection.id,
|
|
474
|
+
description,
|
|
475
|
+
frontmatter: resolvedFrontmatter,
|
|
476
|
+
hidden: hidden || undefined,
|
|
477
|
+
kind: "entry",
|
|
478
|
+
relativePath: sourcePath,
|
|
479
|
+
slug,
|
|
480
|
+
sourcePath,
|
|
481
|
+
title,
|
|
482
|
+
type: collection.type,
|
|
483
|
+
};
|
|
484
|
+
};
|
|
485
|
+
export const buildContentIndex = async (source, config) => {
|
|
486
|
+
const errors = [];
|
|
487
|
+
const index = {
|
|
488
|
+
byCollection: new Map(),
|
|
489
|
+
bySlug: new Map(),
|
|
490
|
+
entries: [],
|
|
491
|
+
errors,
|
|
492
|
+
};
|
|
493
|
+
for (const collection of config.collections) {
|
|
494
|
+
const root = normalizePath(collection.root ?? "");
|
|
495
|
+
const slugPrefix = normalizePath(collection.slugPrefix ?? "");
|
|
496
|
+
let files = [];
|
|
497
|
+
try {
|
|
498
|
+
files = await listContentFiles(source, root);
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
errors.push(error instanceof Error ? error.message : `Failed to read ${root || "."}`);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const collectionEntries = [];
|
|
505
|
+
const resolvedEntries = await Promise.all(files.map(async (file) => await buildEntryFromFile({
|
|
506
|
+
collection,
|
|
507
|
+
errors,
|
|
508
|
+
file,
|
|
509
|
+
root,
|
|
510
|
+
slugPrefix,
|
|
511
|
+
source,
|
|
512
|
+
})));
|
|
513
|
+
for (const entry of resolvedEntries) {
|
|
514
|
+
if (!entry) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
collectionEntries.push(entry);
|
|
518
|
+
addEntry(entry, index, errors);
|
|
519
|
+
}
|
|
520
|
+
const sortConfig = {
|
|
521
|
+
...sortDefaults[collection.type],
|
|
522
|
+
...collection.sort,
|
|
523
|
+
};
|
|
524
|
+
const sortField = sortConfig.field ?? "title";
|
|
525
|
+
const sortDirection = sortConfig.direction ?? "asc";
|
|
526
|
+
collectionEntries.sort((left, right) => {
|
|
527
|
+
const leftValue = left.kind === "entry"
|
|
528
|
+
? left.frontmatter[sortField]
|
|
529
|
+
: undefined;
|
|
530
|
+
const rightValue = right.kind === "entry"
|
|
531
|
+
? right.frontmatter[sortField]
|
|
532
|
+
: undefined;
|
|
533
|
+
return compareValues(leftValue, rightValue, sortDirection);
|
|
534
|
+
});
|
|
535
|
+
index.byCollection.set(collection.id, collectionEntries);
|
|
536
|
+
const collectionIndex = getCollectionIndex(collection, slugPrefix);
|
|
537
|
+
if (collectionIndex) {
|
|
538
|
+
const indexEntry = {
|
|
539
|
+
collectionId: collection.id,
|
|
540
|
+
description: collectionIndex.description,
|
|
541
|
+
kind: "index",
|
|
542
|
+
slug: collectionIndex.slug,
|
|
543
|
+
title: collectionIndex.title ?? titleFromSlug(collectionIndex.slug),
|
|
544
|
+
type: collection.type,
|
|
545
|
+
};
|
|
546
|
+
addEntry(indexEntry, index, errors);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return index;
|
|
550
|
+
};
|
|
551
|
+
const NEWLINE_REGEX = /\r?\n/;
|
|
552
|
+
const HEADING_REGEX = /^(#{2,4})\s+(.*)$/;
|
|
553
|
+
const LEADING_H1_REGEX = /^#\s+([^\r\n]+)(?:\r?\n(?:\r?\n)?)?/;
|
|
554
|
+
export const extractToc = (source) => {
|
|
555
|
+
const withoutCode = source.replaceAll(/```[\s\S]*?```/g, "");
|
|
556
|
+
const lines = withoutCode.split(NEWLINE_REGEX);
|
|
557
|
+
const toc = [];
|
|
558
|
+
for (const line of lines) {
|
|
559
|
+
const match = HEADING_REGEX.exec(line.trim());
|
|
560
|
+
if (!match) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
const [, hashes = "", heading = ""] = match;
|
|
564
|
+
if (!(hashes && heading)) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
toc.push({
|
|
568
|
+
id: slugify(heading.trim()),
|
|
569
|
+
level: hashes.length,
|
|
570
|
+
title: heading.trim(),
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return toc;
|
|
574
|
+
};
|
|
575
|
+
const stripMatchingLeadingH1 = (source, title) => {
|
|
576
|
+
const trimmed = source.trimStart();
|
|
577
|
+
const match = LEADING_H1_REGEX.exec(trimmed);
|
|
578
|
+
if (!match) {
|
|
579
|
+
return trimmed.trim();
|
|
580
|
+
}
|
|
581
|
+
const [headingLine = "", headingTitle = ""] = match;
|
|
582
|
+
if (slugify(headingTitle) !== slugify(title)) {
|
|
583
|
+
return trimmed.trim();
|
|
584
|
+
}
|
|
585
|
+
return trimmed.slice(headingLine.length).trim();
|
|
586
|
+
};
|
|
587
|
+
export const formatMarkdownPage = (title, source) => {
|
|
588
|
+
const content = stripMatchingLeadingH1(source, title);
|
|
589
|
+
if (!content) {
|
|
590
|
+
return `# ${title}`;
|
|
591
|
+
}
|
|
592
|
+
return `# ${title}\n\n${content}`;
|
|
593
|
+
};
|
|
594
|
+
export const formatMarkdownPageSection = (title, url, source) => {
|
|
595
|
+
const content = stripMatchingLeadingH1(source, title);
|
|
596
|
+
if (!content) {
|
|
597
|
+
return `# ${title} (${url})`;
|
|
598
|
+
}
|
|
599
|
+
return `# ${title} (${url})\n\n${content}`;
|
|
600
|
+
};
|
|
601
|
+
const shouldIncludeSearchEntry = (entry, pageMetadataMap, config) => {
|
|
602
|
+
const pageMeta = pageMetadataMap.get(entry.slug);
|
|
603
|
+
if (pageMeta?.hidden || pageMeta?.noindex) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
if (config.seo?.indexing !== "all" &&
|
|
607
|
+
entry.kind === "entry" &&
|
|
608
|
+
entry.hidden === true) {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
return true;
|
|
612
|
+
};
|
|
613
|
+
const stripFrontmatter = (source) => parseFrontmatter(source).body.trim();
|
|
614
|
+
const getDocsCollection = (config) => config.collections.find((collection) => collection.type === "docs");
|
|
615
|
+
const getDocsNavigation = (config) => getDocsCollection(config)?.navigation ?? config.navigation;
|
|
616
|
+
const getDocsCollectionWithNavigation = (config) => {
|
|
617
|
+
const docsCollection = getDocsCollection(config);
|
|
618
|
+
const docsNavigation = getDocsNavigation(config);
|
|
619
|
+
return docsCollection &&
|
|
620
|
+
docsNavigation &&
|
|
621
|
+
docsCollection.navigation !== docsNavigation
|
|
622
|
+
? { ...docsCollection, navigation: docsNavigation }
|
|
623
|
+
: docsCollection;
|
|
624
|
+
};
|
|
625
|
+
const getOpenApiSourceKey = (source) => `${source.source}::${source.directory ?? ""}::${(source.include ?? []).join("|")}`;
|
|
626
|
+
const toOpenApiSourceObject = (value) => {
|
|
627
|
+
if (typeof value === "string") {
|
|
628
|
+
return { source: value };
|
|
629
|
+
}
|
|
630
|
+
return value;
|
|
631
|
+
};
|
|
632
|
+
const collectOpenApiSources = (collection) => {
|
|
633
|
+
const sources = [];
|
|
634
|
+
for (const item of ensureArray(collection?.openapi)) {
|
|
635
|
+
if (!item) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
sources.push(toOpenApiSourceObject(item));
|
|
639
|
+
}
|
|
640
|
+
const groups = collection?.navigation?.groups ?? [];
|
|
641
|
+
for (const group of groups) {
|
|
642
|
+
if (!group.openapi) {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
sources.push(toOpenApiSourceObject(group.openapi));
|
|
646
|
+
}
|
|
647
|
+
const seen = new Set();
|
|
648
|
+
return sources.filter((source) => {
|
|
649
|
+
const key = getOpenApiSourceKey(source);
|
|
650
|
+
if (seen.has(key)) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
seen.add(key);
|
|
654
|
+
return true;
|
|
655
|
+
});
|
|
656
|
+
};
|
|
657
|
+
const formatOpenApiPageContent = (operation) => {
|
|
658
|
+
const parts = [`Method: ${operation.method}`, `Path: ${operation.path}`];
|
|
659
|
+
if (operation.description) {
|
|
660
|
+
parts.push(operation.description);
|
|
661
|
+
}
|
|
662
|
+
if (operation.tags.length) {
|
|
663
|
+
parts.push(`Tags: ${operation.tags.join(", ")}`);
|
|
664
|
+
}
|
|
665
|
+
if (operation.parameters.length) {
|
|
666
|
+
parts.push(`Parameters:\n${JSON.stringify(operation.parameters, null, 2)}`);
|
|
667
|
+
}
|
|
668
|
+
if (operation.requestBody) {
|
|
669
|
+
parts.push(`Request Body:\n${JSON.stringify(operation.requestBody, null, 2)}`);
|
|
670
|
+
}
|
|
671
|
+
if (operation.responses) {
|
|
672
|
+
parts.push(`Responses:\n${JSON.stringify(operation.responses, null, 2)}`);
|
|
673
|
+
}
|
|
674
|
+
return parts.join("\n\n");
|
|
675
|
+
};
|
|
676
|
+
const getGroupedOpenApiSourceKey = (source) => getOpenApiSourceKey(toOpenApiSourceObject(source));
|
|
677
|
+
const collectUtilityOpenApiPages = (pagesByIdentifier, pagesBySource, operations, directory, openApiSource, slugPrefix) => {
|
|
678
|
+
const sourceKey = getOpenApiSourceKey(openApiSource);
|
|
679
|
+
const includeIdentifiers = openApiSource.include?.length
|
|
680
|
+
? new Set(openApiSource.include)
|
|
681
|
+
: null;
|
|
682
|
+
for (const operation of operations) {
|
|
683
|
+
const identifier = openApiIdentifier(operation.method, operation.path);
|
|
684
|
+
if (includeIdentifiers && !includeIdentifiers.has(identifier)) {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
const baseSlug = normalizePath(openApiSlug(operation.method, operation.path, directory));
|
|
688
|
+
const slug = slugPrefix
|
|
689
|
+
? normalizePath(`${slugPrefix}/${baseSlug}`)
|
|
690
|
+
: baseSlug;
|
|
691
|
+
const page = {
|
|
692
|
+
content: formatOpenApiPageContent(operation),
|
|
693
|
+
description: operation.description,
|
|
694
|
+
identifier,
|
|
695
|
+
slug,
|
|
696
|
+
sourceKey,
|
|
697
|
+
title: operation.summary ?? identifier,
|
|
698
|
+
};
|
|
699
|
+
pagesByIdentifier.set(identifier, page);
|
|
700
|
+
if (!pagesBySource.has(sourceKey)) {
|
|
701
|
+
pagesBySource.set(sourceKey, []);
|
|
702
|
+
}
|
|
703
|
+
pagesBySource.get(sourceKey)?.push(page);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const addUtilityPagesFromSourceKey = (pages, pagesBySource, sourceKey) => {
|
|
707
|
+
for (const page of pagesBySource.get(sourceKey) ?? []) {
|
|
708
|
+
pages.set(page.slug, page);
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
const addReferencedUtilityPages = (pages, pagesByIdentifier, pageReferences, hiddenPages, groupHidden = false) => {
|
|
712
|
+
for (const pageReference of pageReferences ?? []) {
|
|
713
|
+
if (groupHidden || hiddenPages.has(pageReference)) {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const page = pagesByIdentifier.get(pageReference);
|
|
717
|
+
if (page) {
|
|
718
|
+
pages.set(page.slug, page);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
const buildUtilityOpenApiPages = async (config, collection, source) => {
|
|
723
|
+
if (!collection || collection.type !== "docs") {
|
|
724
|
+
return [];
|
|
725
|
+
}
|
|
726
|
+
const docsNavigation = getDocsNavigation(config);
|
|
727
|
+
const hiddenPages = new Set(docsNavigation?.hidden);
|
|
728
|
+
const slugPrefix = normalizePath(collection.slugPrefix ?? "");
|
|
729
|
+
const byIdentifier = new Map();
|
|
730
|
+
const bySource = new Map();
|
|
731
|
+
const pages = new Map();
|
|
732
|
+
const sources = collectOpenApiSources(collection);
|
|
733
|
+
const resolved = await Promise.all(sources.map(async (item) => {
|
|
734
|
+
const rawSpec = await source.readFile(item.source);
|
|
735
|
+
const spec = parseOpenApiSpec(rawSpec, item.source);
|
|
736
|
+
const directory = item.directory ?? "api";
|
|
737
|
+
const { operations } = extractOpenApiOperations(spec, directory);
|
|
738
|
+
return { directory, operations, source: item };
|
|
739
|
+
}));
|
|
740
|
+
for (const { directory, operations, source: openApiSource } of resolved) {
|
|
741
|
+
collectUtilityOpenApiPages(byIdentifier, bySource, operations, directory, openApiSource, slugPrefix);
|
|
742
|
+
}
|
|
743
|
+
for (const openApiSource of ensureArray(collection.openapi)) {
|
|
744
|
+
if (!openApiSource) {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
addUtilityPagesFromSourceKey(pages, bySource, getGroupedOpenApiSourceKey(openApiSource));
|
|
748
|
+
}
|
|
749
|
+
for (const group of docsNavigation?.groups ?? []) {
|
|
750
|
+
const groupHidden = group.hidden === true;
|
|
751
|
+
addReferencedUtilityPages(pages, byIdentifier, group.pages, hiddenPages, groupHidden);
|
|
752
|
+
if (groupHidden || !group.openapi) {
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
addUtilityPagesFromSourceKey(pages, bySource, getGroupedOpenApiSourceKey(group.openapi));
|
|
756
|
+
}
|
|
757
|
+
addReferencedUtilityPages(pages, byIdentifier, docsNavigation?.pages, hiddenPages);
|
|
758
|
+
return [...pages.values()];
|
|
759
|
+
};
|
|
760
|
+
export const buildSearchIndex = (index, config, utilityIndex) => {
|
|
761
|
+
const pageMetadataMap = buildPageMetadataMap(index);
|
|
762
|
+
const items = new Map();
|
|
763
|
+
for (const page of utilityIndex?.pages ?? []) {
|
|
764
|
+
items.set(page.slug, {
|
|
765
|
+
path: page.slug,
|
|
766
|
+
title: page.title,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
for (const entry of index.entries) {
|
|
770
|
+
if (!shouldIncludeSearchEntry(entry, pageMetadataMap, config)) {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const pageMeta = pageMetadataMap.get(entry.slug);
|
|
774
|
+
items.set(entry.slug, {
|
|
775
|
+
href: pageMeta?.url,
|
|
776
|
+
path: entry.slug,
|
|
777
|
+
title: pageMeta?.sidebarTitle ?? entry.title,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
return [...items.values()];
|
|
781
|
+
};
|
|
782
|
+
export const buildUtilityIndex = async (index, source, config) => {
|
|
783
|
+
const pageMetadataMap = buildPageMetadataMap(index);
|
|
784
|
+
const pages = new Map();
|
|
785
|
+
for (const entry of index.entries) {
|
|
786
|
+
if (entry.kind !== "entry") {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (!shouldIncludeSearchEntry(entry, pageMetadataMap, config)) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const rawContent = await source.readFile(entry.relativePath);
|
|
793
|
+
pages.set(entry.slug, {
|
|
794
|
+
content: stripFrontmatter(rawContent),
|
|
795
|
+
description: entry.description,
|
|
796
|
+
slug: entry.slug,
|
|
797
|
+
title: entry.title,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
for (const page of await buildUtilityOpenApiPages(config, getDocsCollectionWithNavigation(config), source)) {
|
|
801
|
+
pages.set(page.slug, page);
|
|
802
|
+
}
|
|
803
|
+
const sortedPages = [...pages.values()];
|
|
804
|
+
// oxlint-disable-next-line eslint-plugin-unicorn/no-array-sort
|
|
805
|
+
sortedPages.sort((left, right) => left.slug.localeCompare(right.slug));
|
|
806
|
+
return {
|
|
807
|
+
description: config.description,
|
|
808
|
+
name: config.name,
|
|
809
|
+
pages: sortedPages,
|
|
810
|
+
};
|
|
811
|
+
};
|
|
812
|
+
const toUtilityDocPath = (value) => {
|
|
813
|
+
const clean = normalizePath(value);
|
|
814
|
+
if (!clean || clean === "index") {
|
|
815
|
+
return "/";
|
|
816
|
+
}
|
|
817
|
+
return `/${clean}`;
|
|
818
|
+
};
|
|
819
|
+
const toUtilityTemplatedDocUrl = (value) => `${UTILITY_DOCS_ROOT_TOKEN}${toUtilityDocPath(value)}`;
|
|
820
|
+
export const getPrebuiltUtilityLlmPagePath = (slug) => {
|
|
821
|
+
const normalized = normalizePath(slug);
|
|
822
|
+
return `_utility/llms-pages/${normalized || "index"}.mdx`;
|
|
823
|
+
};
|
|
824
|
+
export const buildUtilityArtifacts = (index) => {
|
|
825
|
+
const llmsLines = [
|
|
826
|
+
`# ${index.name}`,
|
|
827
|
+
index.description ? `> ${index.description}` : null,
|
|
828
|
+
"",
|
|
829
|
+
`Sitemap: ${toUtilityTemplatedDocUrl("sitemap.xml")}`,
|
|
830
|
+
"",
|
|
831
|
+
"## Docs",
|
|
832
|
+
...index.pages.map((page) => {
|
|
833
|
+
const description = page.description ? `: ${page.description}` : "";
|
|
834
|
+
return `- [${page.title}](${toUtilityTemplatedDocUrl(page.slug)})${description}`;
|
|
835
|
+
}),
|
|
836
|
+
];
|
|
837
|
+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
838
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
839
|
+
${index.pages
|
|
840
|
+
.map((page) => ` <url><loc>${toUtilityTemplatedDocUrl(page.slug)}</loc></url>`)
|
|
841
|
+
.join("\n")}
|
|
842
|
+
</urlset>`;
|
|
843
|
+
const llmsFull = index.pages
|
|
844
|
+
.map((page) => formatMarkdownPageSection(page.title, toUtilityTemplatedDocUrl(page.slug), page.content))
|
|
845
|
+
.join("\n\n");
|
|
846
|
+
return [
|
|
847
|
+
{
|
|
848
|
+
content: sitemap,
|
|
849
|
+
contentType: "application/xml; charset=utf-8",
|
|
850
|
+
path: PREBUILT_UTILITY_SITEMAP_PATH,
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
content: llmsLines.filter((line) => line !== null).join("\n"),
|
|
854
|
+
contentType: "text/plain; charset=utf-8",
|
|
855
|
+
path: PREBUILT_UTILITY_LLMS_PATH,
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
content: llmsFull,
|
|
859
|
+
contentType: "text/plain; charset=utf-8",
|
|
860
|
+
path: PREBUILT_UTILITY_LLMS_FULL_PATH,
|
|
861
|
+
},
|
|
862
|
+
...index.pages.map((page) => ({
|
|
863
|
+
content: formatMarkdownPage(page.title, page.content),
|
|
864
|
+
contentType: "text/markdown; charset=utf-8",
|
|
865
|
+
path: getPrebuiltUtilityLlmPagePath(page.slug),
|
|
866
|
+
})),
|
|
867
|
+
];
|
|
868
|
+
};
|
|
869
|
+
export const buildTocIndex = async (index, source) => {
|
|
870
|
+
const itemsBySlug = new Map();
|
|
871
|
+
for (const entry of index.entries) {
|
|
872
|
+
if (entry.kind !== "entry") {
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
const rawContent = await source.readFile(entry.relativePath);
|
|
876
|
+
itemsBySlug.set(entry.slug, extractToc(rawContent));
|
|
877
|
+
}
|
|
878
|
+
return itemsBySlug;
|
|
879
|
+
};
|
|
880
|
+
export const serializeContentIndex = (index) => JSON.stringify({
|
|
881
|
+
collections: Object.fromEntries(index.byCollection),
|
|
882
|
+
entries: index.entries,
|
|
883
|
+
version: 1,
|
|
884
|
+
});
|
|
885
|
+
export const serializeOpenApiIndex = (entries) => JSON.stringify({
|
|
886
|
+
entries,
|
|
887
|
+
version: 1,
|
|
888
|
+
});
|
|
889
|
+
export const loadPrebuiltContentIndex = async (source) => {
|
|
890
|
+
try {
|
|
891
|
+
const raw = await source.readFile(PREBUILT_INDEX_PATH);
|
|
892
|
+
const data = JSON.parse(raw);
|
|
893
|
+
if (data.version !== 1 || !Array.isArray(data.entries)) {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
const bySlug = new Map();
|
|
897
|
+
const byCollection = new Map();
|
|
898
|
+
for (const entry of data.entries) {
|
|
899
|
+
bySlug.set(entry.slug, entry);
|
|
900
|
+
}
|
|
901
|
+
for (const [collectionId, entries] of Object.entries(data.collections ?? {})) {
|
|
902
|
+
byCollection.set(collectionId, entries);
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
byCollection,
|
|
906
|
+
bySlug,
|
|
907
|
+
entries: data.entries,
|
|
908
|
+
errors: [],
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
export const loadPrebuiltOpenApiIndex = async (source) => {
|
|
916
|
+
try {
|
|
917
|
+
const raw = await source.readFile(PREBUILT_OPENAPI_INDEX_PATH);
|
|
918
|
+
const data = JSON.parse(raw);
|
|
919
|
+
if (data.version !== 1 || !Array.isArray(data.entries)) {
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
return data.entries;
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
export const serializeSearchIndex = (items) => JSON.stringify({
|
|
929
|
+
items,
|
|
930
|
+
version: 1,
|
|
931
|
+
});
|
|
932
|
+
export const loadPrebuiltSearchIndex = async (source) => {
|
|
933
|
+
try {
|
|
934
|
+
const raw = await source.readFile(PREBUILT_SEARCH_INDEX_PATH);
|
|
935
|
+
const data = JSON.parse(raw);
|
|
936
|
+
if (data.version !== 1 || !Array.isArray(data.items)) {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
return data.items;
|
|
940
|
+
}
|
|
941
|
+
catch {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
export const serializeTocIndex = (itemsBySlug) => JSON.stringify({
|
|
946
|
+
itemsBySlug: Object.fromEntries(itemsBySlug),
|
|
947
|
+
version: 1,
|
|
948
|
+
});
|
|
949
|
+
export const loadPrebuiltTocIndex = async (source) => {
|
|
950
|
+
try {
|
|
951
|
+
const raw = await source.readFile(PREBUILT_TOC_INDEX_PATH);
|
|
952
|
+
const data = JSON.parse(raw);
|
|
953
|
+
if (data.version !== 1 || typeof data.itemsBySlug !== "object") {
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
return new Map(Object.entries(data.itemsBySlug ?? {}));
|
|
957
|
+
}
|
|
958
|
+
catch {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
export const serializeUtilityIndex = (index) => JSON.stringify({
|
|
963
|
+
...index,
|
|
964
|
+
version: 1,
|
|
965
|
+
});
|
|
966
|
+
export const loadPrebuiltUtilityIndex = async (source) => {
|
|
967
|
+
try {
|
|
968
|
+
const raw = await source.readFile(PREBUILT_UTILITY_INDEX_PATH);
|
|
969
|
+
const data = JSON.parse(raw);
|
|
970
|
+
if (data.version !== 1 ||
|
|
971
|
+
typeof data.name !== "string" ||
|
|
972
|
+
!Array.isArray(data.pages)) {
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
return {
|
|
976
|
+
description: data.description,
|
|
977
|
+
name: data.name,
|
|
978
|
+
pages: data.pages,
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
catch {
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
};
|