@raystack/chronicle 0.12.3 → 0.12.5
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/dist/cli/index.js +1763 -252
- package/package.json +8 -2
- package/src/cli/commands/build.ts +36 -7
- package/src/cli/commands/static-generate.ts +1071 -0
- package/src/cli/utils/mdx-error-report.ts +48 -0
- package/src/components/api/playground-dialog.tsx +65 -14
- package/src/components/mdx/code.tsx +8 -3
- package/src/components/mdx/image.tsx +15 -1
- package/src/components/mdx/index.tsx +10 -3
- package/src/components/ui/search.tsx +226 -64
- package/src/lib/data-urls.ts +23 -0
- package/src/lib/head.tsx +2 -1
- package/src/lib/mdx-component-names.ts +15 -0
- package/src/lib/mdx-error.test.ts +78 -0
- package/src/lib/mdx-error.ts +91 -0
- package/src/lib/openapi.ts +1 -1
- package/src/lib/page-context.tsx +29 -14
- package/src/lib/preload.ts +4 -2
- package/src/lib/remark-validate-mdx.test.ts +95 -0
- package/src/lib/remark-validate-mdx.ts +85 -0
- package/src/lib/route-resolver.ts +7 -0
- package/src/lib/static-mode.ts +3 -0
- package/src/pages/DocsPage.tsx +3 -2
- package/src/pages/NotFound.module.css +10 -0
- package/src/pages/RenderError.tsx +18 -0
- package/src/server/App.tsx +16 -12
- package/src/server/api/apis-proxy.ts +32 -7
- package/src/server/dev-error-page.ts +133 -0
- package/src/server/entry-server.tsx +6 -0
- package/src/server/entry-static.tsx +151 -0
- package/src/server/error.ts +13 -1
- package/src/server/plugins/telemetry.ts +3 -2
- package/src/server/vite-config.ts +22 -9
- package/src/themes/default/Page.module.css +2 -0
- package/src/themes/paper/Layout.module.css +0 -4
- package/src/themes/paper/Layout.tsx +0 -2
- package/src/themes/paper/Page.module.css +2 -0
package/dist/cli/index.js
CHANGED
|
@@ -46,6 +46,257 @@ var __export = (target, all) => {
|
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
48
|
|
|
49
|
+
// src/types/config.ts
|
|
50
|
+
import { uniqBy } from "lodash-es";
|
|
51
|
+
import { z } from "zod";
|
|
52
|
+
var logoSchema, themeSchema, navLinkSchema, socialLinkSchema, navigationSchema, searchSchema, apiServerSchema, apiAuthSchema, apiSchema, googleAnalyticsSchema, analyticsSchema, telemetrySchema, siteSchema, DIR_NAME_PATTERN, dirNameSchema, contentEntrySchema, badgeVariantSchema, badgeSchema, latestSchema, versionSchema, allUnique = (items, key) => uniqBy(items, key).length === items.length, RESERVED_ROUTE_SEGMENTS, redirectSchema, chronicleConfigSchema;
|
|
53
|
+
var init_config = __esm(() => {
|
|
54
|
+
logoSchema = z.object({
|
|
55
|
+
light: z.string().optional(),
|
|
56
|
+
dark: z.string().optional()
|
|
57
|
+
});
|
|
58
|
+
themeSchema = z.object({
|
|
59
|
+
name: z.enum(["default", "paper"]),
|
|
60
|
+
colors: z.record(z.string(), z.string()).optional()
|
|
61
|
+
});
|
|
62
|
+
navLinkSchema = z.object({
|
|
63
|
+
label: z.string(),
|
|
64
|
+
href: z.string()
|
|
65
|
+
});
|
|
66
|
+
socialLinkSchema = z.object({
|
|
67
|
+
type: z.string(),
|
|
68
|
+
href: z.string()
|
|
69
|
+
});
|
|
70
|
+
navigationSchema = z.object({
|
|
71
|
+
links: z.array(navLinkSchema).optional(),
|
|
72
|
+
social: z.array(socialLinkSchema).optional()
|
|
73
|
+
});
|
|
74
|
+
searchSchema = z.object({
|
|
75
|
+
enabled: z.boolean().optional(),
|
|
76
|
+
placeholder: z.string().optional()
|
|
77
|
+
});
|
|
78
|
+
apiServerSchema = z.object({
|
|
79
|
+
url: z.string(),
|
|
80
|
+
description: z.string().optional()
|
|
81
|
+
});
|
|
82
|
+
apiAuthSchema = z.object({
|
|
83
|
+
type: z.string(),
|
|
84
|
+
header: z.string(),
|
|
85
|
+
placeholder: z.string().optional()
|
|
86
|
+
});
|
|
87
|
+
apiSchema = z.object({
|
|
88
|
+
name: z.string(),
|
|
89
|
+
spec: z.string(),
|
|
90
|
+
basePath: z.string(),
|
|
91
|
+
icon: z.string().optional(),
|
|
92
|
+
server: apiServerSchema,
|
|
93
|
+
auth: apiAuthSchema.optional()
|
|
94
|
+
});
|
|
95
|
+
googleAnalyticsSchema = z.object({
|
|
96
|
+
measurementId: z.string()
|
|
97
|
+
});
|
|
98
|
+
analyticsSchema = z.object({
|
|
99
|
+
enabled: z.boolean().optional(),
|
|
100
|
+
googleAnalytics: googleAnalyticsSchema.optional()
|
|
101
|
+
});
|
|
102
|
+
telemetrySchema = z.object({
|
|
103
|
+
enabled: z.boolean().optional(),
|
|
104
|
+
serviceName: z.string().optional(),
|
|
105
|
+
port: z.number().int().min(1).max(65535).default(9090)
|
|
106
|
+
});
|
|
107
|
+
siteSchema = z.object({
|
|
108
|
+
title: z.string(),
|
|
109
|
+
description: z.string().optional()
|
|
110
|
+
});
|
|
111
|
+
DIR_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
112
|
+
dirNameSchema = z.string().min(1).refine((s) => DIR_NAME_PATTERN.test(s) && s !== "." && s !== "..", {
|
|
113
|
+
message: 'dir must start with a letter or digit and contain only letters, digits, ".", "_", or "-"'
|
|
114
|
+
});
|
|
115
|
+
contentEntrySchema = z.object({
|
|
116
|
+
dir: dirNameSchema,
|
|
117
|
+
label: z.string().min(1),
|
|
118
|
+
description: z.string().optional(),
|
|
119
|
+
icon: z.string().optional(),
|
|
120
|
+
index_page: z.string().optional()
|
|
121
|
+
});
|
|
122
|
+
badgeVariantSchema = z.enum([
|
|
123
|
+
"accent",
|
|
124
|
+
"warning",
|
|
125
|
+
"danger",
|
|
126
|
+
"success",
|
|
127
|
+
"neutral",
|
|
128
|
+
"gradient"
|
|
129
|
+
]);
|
|
130
|
+
badgeSchema = z.object({
|
|
131
|
+
label: z.string().min(1),
|
|
132
|
+
variant: badgeVariantSchema.default("accent")
|
|
133
|
+
});
|
|
134
|
+
latestSchema = z.object({
|
|
135
|
+
label: z.string().min(1),
|
|
136
|
+
landing: z.boolean().optional()
|
|
137
|
+
});
|
|
138
|
+
versionSchema = z.object({
|
|
139
|
+
dir: dirNameSchema,
|
|
140
|
+
label: z.string().min(1),
|
|
141
|
+
badge: badgeSchema.optional(),
|
|
142
|
+
landing: z.boolean().optional(),
|
|
143
|
+
content: z.array(contentEntrySchema).min(1),
|
|
144
|
+
api: z.array(apiSchema).optional()
|
|
145
|
+
});
|
|
146
|
+
RESERVED_ROUTE_SEGMENTS = [
|
|
147
|
+
"api",
|
|
148
|
+
"apis",
|
|
149
|
+
"og",
|
|
150
|
+
"llms.txt",
|
|
151
|
+
"robots.txt",
|
|
152
|
+
"sitemap.xml"
|
|
153
|
+
];
|
|
154
|
+
redirectSchema = z.object({
|
|
155
|
+
from: z.string(),
|
|
156
|
+
to: z.string(),
|
|
157
|
+
permanent: z.boolean().optional()
|
|
158
|
+
});
|
|
159
|
+
chronicleConfigSchema = z.object({
|
|
160
|
+
site: siteSchema,
|
|
161
|
+
url: z.string().optional(),
|
|
162
|
+
content: z.array(contentEntrySchema).min(1),
|
|
163
|
+
latest: latestSchema.optional(),
|
|
164
|
+
versions: z.array(versionSchema).optional(),
|
|
165
|
+
preset: z.string().optional(),
|
|
166
|
+
logo: logoSchema.optional(),
|
|
167
|
+
theme: themeSchema.optional(),
|
|
168
|
+
navigation: navigationSchema.optional(),
|
|
169
|
+
search: searchSchema.optional(),
|
|
170
|
+
api: z.array(apiSchema).optional(),
|
|
171
|
+
redirects: z.array(redirectSchema).optional(),
|
|
172
|
+
analytics: analyticsSchema.optional(),
|
|
173
|
+
telemetry: telemetrySchema.optional()
|
|
174
|
+
}).strict().refine((cfg) => allUnique(cfg.content, (c) => c.dir), {
|
|
175
|
+
message: "content[].dir must be unique",
|
|
176
|
+
path: ["content"]
|
|
177
|
+
}).refine((cfg) => !cfg.versions || allUnique(cfg.versions, (v) => v.dir), {
|
|
178
|
+
message: "versions[].dir must be unique",
|
|
179
|
+
path: ["versions"]
|
|
180
|
+
}).refine((cfg) => !cfg.versions || cfg.versions.every((v) => allUnique(v.content, (c) => c.dir)), {
|
|
181
|
+
message: "versions[].content[].dir must be unique within each version",
|
|
182
|
+
path: ["versions"]
|
|
183
|
+
}).refine((cfg) => !cfg.versions || cfg.versions.length === 0 || !!cfg.latest, {
|
|
184
|
+
message: "latest is required when versions are declared",
|
|
185
|
+
path: ["latest"]
|
|
186
|
+
}).refine((cfg) => {
|
|
187
|
+
if (!cfg.versions)
|
|
188
|
+
return true;
|
|
189
|
+
const contentDirs = new Set(cfg.content.map((c) => c.dir));
|
|
190
|
+
return !cfg.versions.some((v) => contentDirs.has(v.dir));
|
|
191
|
+
}, {
|
|
192
|
+
message: "versions[].dir must not overlap with content[].dir — the URL segment would be shadowed",
|
|
193
|
+
path: ["versions"]
|
|
194
|
+
}).superRefine((cfg, ctx) => {
|
|
195
|
+
const reserved = new Set(RESERVED_ROUTE_SEGMENTS);
|
|
196
|
+
const message = `dir must not be a reserved route segment: ${RESERVED_ROUTE_SEGMENTS.join(", ")}`;
|
|
197
|
+
cfg.content.forEach((c, i) => {
|
|
198
|
+
if (reserved.has(c.dir)) {
|
|
199
|
+
ctx.addIssue({ code: "custom", message, path: ["content", i, "dir"] });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
cfg.versions?.forEach((v, vi) => {
|
|
203
|
+
if (reserved.has(v.dir)) {
|
|
204
|
+
ctx.addIssue({
|
|
205
|
+
code: "custom",
|
|
206
|
+
message,
|
|
207
|
+
path: ["versions", vi, "dir"]
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
v.content.forEach((c, ci) => {
|
|
211
|
+
if (reserved.has(c.dir)) {
|
|
212
|
+
ctx.addIssue({
|
|
213
|
+
code: "custom",
|
|
214
|
+
message,
|
|
215
|
+
path: ["versions", vi, "content", ci, "dir"]
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// src/types/content.ts
|
|
224
|
+
var SearchResultType;
|
|
225
|
+
var init_content = __esm(() => {
|
|
226
|
+
SearchResultType = {
|
|
227
|
+
Page: "page",
|
|
228
|
+
Api: "api"
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
// src/types/index.ts
|
|
232
|
+
var init_types = __esm(() => {
|
|
233
|
+
init_config();
|
|
234
|
+
init_content();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// src/lib/config.ts
|
|
238
|
+
import { parse as parse2 } from "yaml";
|
|
239
|
+
function getLatestContentRoots(config2) {
|
|
240
|
+
return config2.content.map((c) => ({
|
|
241
|
+
versionDir: null,
|
|
242
|
+
versionLabel: config2.latest?.label ?? null,
|
|
243
|
+
contentDir: c.dir,
|
|
244
|
+
contentLabel: c.label,
|
|
245
|
+
contentDescription: c.description,
|
|
246
|
+
contentIcon: c.icon,
|
|
247
|
+
fsPath: `content/${c.dir}`,
|
|
248
|
+
urlPrefix: `/${c.dir}`
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
function getVersionContentRoots(config2, versionDir) {
|
|
252
|
+
const version = config2.versions?.find((v) => v.dir === versionDir);
|
|
253
|
+
if (!version)
|
|
254
|
+
return [];
|
|
255
|
+
return version.content.map((c) => ({
|
|
256
|
+
versionDir: version.dir,
|
|
257
|
+
versionLabel: version.label,
|
|
258
|
+
contentDir: c.dir,
|
|
259
|
+
contentLabel: c.label,
|
|
260
|
+
contentDescription: c.description,
|
|
261
|
+
contentIcon: c.icon,
|
|
262
|
+
fsPath: `versions/${version.dir}/${c.dir}`,
|
|
263
|
+
urlPrefix: `/${version.dir}/${c.dir}`
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
function getApiConfigsForVersion(config2, versionDir) {
|
|
267
|
+
if (versionDir === null)
|
|
268
|
+
return config2.api ?? [];
|
|
269
|
+
return config2.versions?.find((v) => v.dir === versionDir)?.api ?? [];
|
|
270
|
+
}
|
|
271
|
+
function getAllVersions(config2) {
|
|
272
|
+
const result = [
|
|
273
|
+
{
|
|
274
|
+
dir: null,
|
|
275
|
+
label: config2.latest?.label ?? "",
|
|
276
|
+
isLatest: true
|
|
277
|
+
}
|
|
278
|
+
];
|
|
279
|
+
for (const v of config2.versions ?? []) {
|
|
280
|
+
result.push({
|
|
281
|
+
dir: v.dir,
|
|
282
|
+
label: v.label,
|
|
283
|
+
badge: v.badge,
|
|
284
|
+
isLatest: false
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
var defaultConfig;
|
|
290
|
+
var init_config2 = __esm(() => {
|
|
291
|
+
init_types();
|
|
292
|
+
defaultConfig = chronicleConfigSchema.parse({
|
|
293
|
+
site: { title: "Documentation" },
|
|
294
|
+
content: [{ dir: "docs", label: "Docs" }],
|
|
295
|
+
theme: { name: "default" },
|
|
296
|
+
search: { enabled: true, placeholder: "Search..." }
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
49
300
|
// src/lib/mdx-utils.ts
|
|
50
301
|
var MdxNodeType;
|
|
51
302
|
var init_mdx_utils = __esm(() => {
|
|
@@ -379,9 +630,82 @@ var init_remark_unused_directives = __esm(() => {
|
|
|
379
630
|
remark_unused_directives_default = remarkUnusedDirectives;
|
|
380
631
|
});
|
|
381
632
|
|
|
633
|
+
// src/lib/mdx-component-names.ts
|
|
634
|
+
import { htmlTagNames } from "html-tag-names";
|
|
635
|
+
import { svgTagNames } from "svg-tag-names";
|
|
636
|
+
var MDX_COMPONENT_NAMES, KNOWN_TAGS;
|
|
637
|
+
var init_mdx_component_names = __esm(() => {
|
|
638
|
+
MDX_COMPONENT_NAMES = [
|
|
639
|
+
"Callout",
|
|
640
|
+
"CalloutTitle",
|
|
641
|
+
"CalloutDescription",
|
|
642
|
+
"Tabs",
|
|
643
|
+
"Mermaid"
|
|
644
|
+
];
|
|
645
|
+
KNOWN_TAGS = new Set([...htmlTagNames, ...svgTagNames]);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// src/lib/remark-validate-mdx.ts
|
|
649
|
+
import { visit as visit5 } from "unist-util-visit";
|
|
650
|
+
function collectLocalNames(tree) {
|
|
651
|
+
const names = new Set;
|
|
652
|
+
visit5(tree, "mdxjsEsm", (node) => {
|
|
653
|
+
const program = node.data?.estree;
|
|
654
|
+
for (const stmt of program?.body ?? []) {
|
|
655
|
+
if (stmt.type === "ImportDeclaration") {
|
|
656
|
+
for (const spec of stmt.specifiers ?? []) {
|
|
657
|
+
if (spec.local?.name)
|
|
658
|
+
names.add(spec.local.name);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const decl = stmt.type === "ExportNamedDeclaration" ? stmt.declaration : stmt;
|
|
662
|
+
if (!decl)
|
|
663
|
+
continue;
|
|
664
|
+
if (decl.type === "VariableDeclaration") {
|
|
665
|
+
for (const d of decl.declarations ?? []) {
|
|
666
|
+
if (d.id?.name)
|
|
667
|
+
names.add(d.id.name);
|
|
668
|
+
}
|
|
669
|
+
} else if ((decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") && decl.id?.name) {
|
|
670
|
+
names.add(decl.id.name);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
return names;
|
|
675
|
+
}
|
|
676
|
+
var remarkValidateMdx = (options = {}) => {
|
|
677
|
+
const allowed = new Set(options.components ?? MDX_COMPONENT_NAMES);
|
|
678
|
+
return (tree, file) => {
|
|
679
|
+
const locals = collectLocalNames(tree);
|
|
680
|
+
visit5(tree, [MdxNodeType.JsxFlow, MdxNodeType.JsxText], (node) => {
|
|
681
|
+
const { name, position } = node;
|
|
682
|
+
if (!name)
|
|
683
|
+
return;
|
|
684
|
+
const root = name.split(".")[0];
|
|
685
|
+
if (root.includes("-"))
|
|
686
|
+
return;
|
|
687
|
+
if (locals.has(root))
|
|
688
|
+
return;
|
|
689
|
+
if (/^[a-z]/.test(root)) {
|
|
690
|
+
if (KNOWN_TAGS.has(root))
|
|
691
|
+
return;
|
|
692
|
+
file.fail(`Unknown HTML tag <${name}> — not a standard HTML/SVG element. If it is a custom component, register it in Chronicle's MDX components.`, position);
|
|
693
|
+
} else if (!allowed.has(root)) {
|
|
694
|
+
file.fail(`Unknown component <${name}> — available components: ${[...allowed].join(", ")}. Import it in this file or register it in Chronicle's MDX components.`, position);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
};
|
|
698
|
+
}, remark_validate_mdx_default;
|
|
699
|
+
var init_remark_validate_mdx = __esm(() => {
|
|
700
|
+
init_mdx_component_names();
|
|
701
|
+
init_mdx_utils();
|
|
702
|
+
remark_validate_mdx_default = remarkValidateMdx;
|
|
703
|
+
});
|
|
704
|
+
|
|
382
705
|
// src/server/vite-config.ts
|
|
383
706
|
var exports_vite_config = {};
|
|
384
707
|
__export(exports_vite_config, {
|
|
708
|
+
isStaticPreset: () => isStaticPreset,
|
|
385
709
|
createViteConfig: () => createViteConfig
|
|
386
710
|
});
|
|
387
711
|
import react from "@vitejs/plugin-react";
|
|
@@ -437,8 +761,9 @@ async function createViteConfig(options) {
|
|
|
437
761
|
plugins: [
|
|
438
762
|
nitro({
|
|
439
763
|
serverDir: path6.resolve(packageRoot, "src/server"),
|
|
440
|
-
|
|
441
|
-
ignore: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"]
|
|
764
|
+
...!isStaticPreset(preset) && preset && { preset },
|
|
765
|
+
ignore: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"],
|
|
766
|
+
...isStaticPreset(preset) && { prerender: { routes: [] } }
|
|
442
767
|
}),
|
|
443
768
|
mdx({
|
|
444
769
|
default: defineFumadocsConfig({
|
|
@@ -468,7 +793,8 @@ async function createViteConfig(options) {
|
|
|
468
793
|
remark_resolve_links_default,
|
|
469
794
|
[remark_resolve_images_default, { optimize: !isStaticPreset(preset) }],
|
|
470
795
|
remarkMdxMermaid,
|
|
471
|
-
readingTime
|
|
796
|
+
readingTime,
|
|
797
|
+
remark_validate_mdx_default
|
|
472
798
|
]
|
|
473
799
|
}
|
|
474
800
|
})
|
|
@@ -511,8 +837,9 @@ async function createViteConfig(options) {
|
|
|
511
837
|
environments: {
|
|
512
838
|
client: {
|
|
513
839
|
build: {
|
|
840
|
+
manifest: isStaticPreset(preset),
|
|
514
841
|
rollupOptions: {
|
|
515
|
-
input: path6.resolve(packageRoot, "src/server/entry-client.tsx")
|
|
842
|
+
input: path6.resolve(packageRoot, isStaticPreset(preset) ? "src/server/entry-static.tsx" : "src/server/entry-client.tsx")
|
|
516
843
|
}
|
|
517
844
|
}
|
|
518
845
|
}
|
|
@@ -531,11 +858,13 @@ async function createViteConfig(options) {
|
|
|
531
858
|
base: path6.resolve(projectRoot, ".cache/images")
|
|
532
859
|
}
|
|
533
860
|
},
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
861
|
+
...isStaticPreset(preset) ? {} : {
|
|
862
|
+
experimental: {
|
|
863
|
+
database: true
|
|
864
|
+
},
|
|
865
|
+
database: {
|
|
866
|
+
default: getDatabaseConnector(preset)
|
|
867
|
+
}
|
|
539
868
|
}
|
|
540
869
|
}
|
|
541
870
|
};
|
|
@@ -546,194 +875,1387 @@ var init_vite_config = __esm(() => {
|
|
|
546
875
|
init_remark_resolve_links();
|
|
547
876
|
init_remark_reading_time();
|
|
548
877
|
init_remark_unused_directives();
|
|
878
|
+
init_remark_validate_mdx();
|
|
549
879
|
STATIC_PRESETS = new Set(["static", "vercel-static", "cloudflare-pages", "github-pages"]);
|
|
550
880
|
});
|
|
551
881
|
|
|
552
|
-
// src/
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
import chalk from "chalk";
|
|
563
|
-
import { parse } from "yaml";
|
|
882
|
+
// src/lib/env.ts
|
|
883
|
+
function substituteEnvVars(value) {
|
|
884
|
+
return value.replace(/\$\{(\w+)\}/g, (_, name) => {
|
|
885
|
+
const val = process.env[name];
|
|
886
|
+
if (val === undefined) {
|
|
887
|
+
throw new Error(`Environment variable '${name}' is not set`);
|
|
888
|
+
}
|
|
889
|
+
return val;
|
|
890
|
+
});
|
|
891
|
+
}
|
|
564
892
|
|
|
565
|
-
// src/
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
});
|
|
572
|
-
var themeSchema = z.object({
|
|
573
|
-
name: z.enum(["default", "paper"]),
|
|
574
|
-
colors: z.record(z.string(), z.string()).optional()
|
|
575
|
-
});
|
|
576
|
-
var navLinkSchema = z.object({
|
|
577
|
-
label: z.string(),
|
|
578
|
-
href: z.string()
|
|
579
|
-
});
|
|
580
|
-
var socialLinkSchema = z.object({
|
|
581
|
-
type: z.string(),
|
|
582
|
-
href: z.string()
|
|
583
|
-
});
|
|
584
|
-
var navigationSchema = z.object({
|
|
585
|
-
links: z.array(navLinkSchema).optional(),
|
|
586
|
-
social: z.array(socialLinkSchema).optional()
|
|
587
|
-
});
|
|
588
|
-
var searchSchema = z.object({
|
|
589
|
-
enabled: z.boolean().optional(),
|
|
590
|
-
placeholder: z.string().optional()
|
|
591
|
-
});
|
|
592
|
-
var apiServerSchema = z.object({
|
|
593
|
-
url: z.string(),
|
|
594
|
-
description: z.string().optional()
|
|
595
|
-
});
|
|
596
|
-
var apiAuthSchema = z.object({
|
|
597
|
-
type: z.string(),
|
|
598
|
-
header: z.string(),
|
|
599
|
-
placeholder: z.string().optional()
|
|
893
|
+
// src/lib/schema.ts
|
|
894
|
+
var exports_schema = {};
|
|
895
|
+
__export(exports_schema, {
|
|
896
|
+
toKind: () => toKind,
|
|
897
|
+
generateExampleJson: () => generateExampleJson,
|
|
898
|
+
flattenSchema: () => flattenSchema
|
|
600
899
|
});
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
900
|
+
function toKind(type) {
|
|
901
|
+
if (typeof type === "string" && type in schemaFieldKinds)
|
|
902
|
+
return type;
|
|
903
|
+
return "object";
|
|
904
|
+
}
|
|
905
|
+
function mergeAllOf(schema) {
|
|
906
|
+
const composed = schema.allOf ?? schema.oneOf ?? schema.anyOf;
|
|
907
|
+
if (!composed)
|
|
908
|
+
return schema;
|
|
909
|
+
const merged = { ...schema };
|
|
910
|
+
delete merged.allOf;
|
|
911
|
+
delete merged.oneOf;
|
|
912
|
+
delete merged.anyOf;
|
|
913
|
+
for (const sub of composed) {
|
|
914
|
+
if (sub.type)
|
|
915
|
+
merged.type = sub.type;
|
|
916
|
+
if (sub.properties) {
|
|
917
|
+
merged.properties = { ...merged.properties ?? {}, ...sub.properties };
|
|
918
|
+
}
|
|
919
|
+
if (sub.required) {
|
|
920
|
+
merged.required = [...merged.required ?? [], ...sub.required];
|
|
921
|
+
}
|
|
922
|
+
if (sub.description && !merged.description)
|
|
923
|
+
merged.description = sub.description;
|
|
924
|
+
}
|
|
925
|
+
return merged;
|
|
926
|
+
}
|
|
927
|
+
function flattenSchema(schema, requiredFields = []) {
|
|
928
|
+
const resolved = mergeAllOf(schema);
|
|
929
|
+
if (resolved !== schema)
|
|
930
|
+
return flattenSchema(resolved, requiredFields);
|
|
931
|
+
if (schema.type === "array" && schema.items) {
|
|
932
|
+
const items = schema.items;
|
|
933
|
+
const itemType = inferType(items);
|
|
934
|
+
const children = itemType === "object" || items.properties ? flattenSchema(items, items.required ?? []) : itemType.endsWith("[]") && items.items ? flattenSchema(items.items) : undefined;
|
|
935
|
+
return [{
|
|
936
|
+
name: "items",
|
|
937
|
+
type: `${itemType}[]`,
|
|
938
|
+
kind: "array",
|
|
939
|
+
required: true,
|
|
940
|
+
description: items.description,
|
|
941
|
+
children: children?.length ? children : undefined
|
|
942
|
+
}];
|
|
943
|
+
}
|
|
944
|
+
if (schema.type === "object" || schema.properties) {
|
|
945
|
+
const properties = schema.properties ?? {};
|
|
946
|
+
const required = schema.required ?? requiredFields;
|
|
947
|
+
return Object.entries(properties).map(([name, rawProp]) => {
|
|
948
|
+
const prop = mergeAllOf(rawProp);
|
|
949
|
+
const fieldType = inferType(prop);
|
|
950
|
+
const children = fieldType === "object" || prop.properties ? flattenSchema(prop, prop.required) : fieldType.endsWith("[]") && prop.items ? flattenSchema(prop.items) : undefined;
|
|
951
|
+
return {
|
|
952
|
+
name,
|
|
953
|
+
type: fieldType,
|
|
954
|
+
kind: toKind(prop.type),
|
|
955
|
+
required: required.includes(name),
|
|
956
|
+
description: rawProp.description ?? prop.description,
|
|
957
|
+
default: prop.default,
|
|
958
|
+
example: prop.example,
|
|
959
|
+
enum: prop.enum,
|
|
960
|
+
children: children?.length ? children : undefined
|
|
961
|
+
};
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return [];
|
|
965
|
+
}
|
|
966
|
+
function generateExampleJson(schema) {
|
|
967
|
+
if (schema.example !== undefined)
|
|
968
|
+
return schema.example;
|
|
969
|
+
if (schema.default !== undefined)
|
|
970
|
+
return schema.default;
|
|
971
|
+
if (schema.type === "array") {
|
|
972
|
+
const items = schema.items;
|
|
973
|
+
return items ? [generateExampleJson(items)] : [];
|
|
974
|
+
}
|
|
975
|
+
if (schema.type === "object" || schema.properties) {
|
|
976
|
+
const properties = schema.properties ?? {};
|
|
977
|
+
const result = {};
|
|
978
|
+
for (const [name, prop] of Object.entries(properties)) {
|
|
979
|
+
result[name] = generateExampleJson(prop);
|
|
980
|
+
}
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
const defaults = {
|
|
984
|
+
string: "string",
|
|
985
|
+
integer: 0,
|
|
986
|
+
number: 0,
|
|
987
|
+
boolean: true
|
|
988
|
+
};
|
|
989
|
+
return defaults[schema.type] ?? null;
|
|
990
|
+
}
|
|
991
|
+
function inferType(rawSchema) {
|
|
992
|
+
const schema = mergeAllOf(rawSchema);
|
|
993
|
+
if (schema.type === "array") {
|
|
994
|
+
const items = schema.items;
|
|
995
|
+
const itemType = items ? inferType(items) : "unknown";
|
|
996
|
+
return `${itemType}[]`;
|
|
997
|
+
}
|
|
998
|
+
if (schema.format)
|
|
999
|
+
return `${schema.type}(${schema.format})`;
|
|
1000
|
+
return schema.type ?? "object";
|
|
1001
|
+
}
|
|
1002
|
+
var schemaFieldKinds;
|
|
1003
|
+
var init_schema = __esm(() => {
|
|
1004
|
+
schemaFieldKinds = {
|
|
1005
|
+
string: "string",
|
|
1006
|
+
integer: "integer",
|
|
1007
|
+
number: "number",
|
|
1008
|
+
boolean: "boolean",
|
|
1009
|
+
array: "array",
|
|
1010
|
+
object: "object"
|
|
1011
|
+
};
|
|
628
1012
|
});
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1013
|
+
|
|
1014
|
+
// src/lib/openapi.ts
|
|
1015
|
+
import fs4 from "node:fs/promises";
|
|
1016
|
+
import path7 from "node:path";
|
|
1017
|
+
import { parse as parseYaml } from "yaml";
|
|
1018
|
+
async function loadApiSpec(config2, projectRoot) {
|
|
1019
|
+
const specPath = path7.resolve(projectRoot, config2.spec);
|
|
1020
|
+
const raw = await fs4.readFile(specPath, "utf-8");
|
|
1021
|
+
const isYaml = specPath.endsWith(".yaml") || specPath.endsWith(".yml");
|
|
1022
|
+
const doc = isYaml ? parseYaml(raw) : JSON.parse(raw);
|
|
1023
|
+
let v3Doc;
|
|
1024
|
+
if ("swagger" in doc && doc.swagger === "2.0") {
|
|
1025
|
+
v3Doc = convertV2toV3(doc);
|
|
1026
|
+
} else if ("openapi" in doc && doc.openapi.startsWith("3.")) {
|
|
1027
|
+
v3Doc = resolveDocument(doc);
|
|
1028
|
+
} else {
|
|
1029
|
+
throw new Error(`Unsupported spec version in ${config2.spec}`);
|
|
1030
|
+
}
|
|
1031
|
+
return {
|
|
1032
|
+
name: config2.name,
|
|
1033
|
+
basePath: config2.basePath,
|
|
1034
|
+
server: { ...config2.server, url: substituteEnvVars(config2.server.url) },
|
|
1035
|
+
auth: config2.auth,
|
|
1036
|
+
document: v3Doc
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function resolveRef(ref, root) {
|
|
1040
|
+
const parts = ref.replace(/^#\//, "").split("/");
|
|
1041
|
+
let current = root;
|
|
1042
|
+
for (const part of parts) {
|
|
1043
|
+
if (current && typeof current === "object" && !Array.isArray(current)) {
|
|
1044
|
+
current = current[part];
|
|
1045
|
+
} else {
|
|
1046
|
+
throw new Error(`Cannot resolve $ref: ${ref}`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return current;
|
|
1050
|
+
}
|
|
1051
|
+
function deepResolveRefs(obj, root, stack = new Set, cache = new Map) {
|
|
1052
|
+
if (obj === null || obj === undefined || typeof obj !== "object")
|
|
1053
|
+
return obj;
|
|
1054
|
+
if (Array.isArray(obj)) {
|
|
1055
|
+
return obj.map((item) => deepResolveRefs(item, root, stack, cache));
|
|
1056
|
+
}
|
|
1057
|
+
const record = obj;
|
|
1058
|
+
if (typeof record.$ref === "string") {
|
|
1059
|
+
const ref = record.$ref;
|
|
1060
|
+
if (cache.has(ref))
|
|
1061
|
+
return cache.get(ref);
|
|
1062
|
+
if (stack.has(ref))
|
|
1063
|
+
return { type: "object", description: "[circular]" };
|
|
1064
|
+
stack.add(ref);
|
|
1065
|
+
const resolved = deepResolveRefs(resolveRef(ref, root), root, stack, cache);
|
|
1066
|
+
stack.delete(ref);
|
|
1067
|
+
cache.set(ref, resolved);
|
|
1068
|
+
return resolved;
|
|
1069
|
+
}
|
|
1070
|
+
const result = {};
|
|
1071
|
+
for (const [key, value] of Object.entries(record)) {
|
|
1072
|
+
result[key] = deepResolveRefs(value, root, stack, cache);
|
|
1073
|
+
}
|
|
1074
|
+
return result;
|
|
1075
|
+
}
|
|
1076
|
+
function resolveDocument(doc) {
|
|
1077
|
+
const root = doc;
|
|
1078
|
+
return deepResolveRefs(doc, root);
|
|
1079
|
+
}
|
|
1080
|
+
function convertV2toV3(doc) {
|
|
1081
|
+
const root = doc;
|
|
1082
|
+
const resolved = deepResolveRefs(doc, root);
|
|
1083
|
+
const v3Paths = {};
|
|
1084
|
+
for (const [pathStr, pathItem] of Object.entries(resolved.paths ?? {})) {
|
|
1085
|
+
if (!pathItem)
|
|
1086
|
+
continue;
|
|
1087
|
+
const v3PathItem = {};
|
|
1088
|
+
for (const method of ["get", "post", "put", "delete", "patch"]) {
|
|
1089
|
+
const op = pathItem[method];
|
|
1090
|
+
if (!op)
|
|
1091
|
+
continue;
|
|
1092
|
+
v3PathItem[method] = convertV2Operation(op);
|
|
1093
|
+
}
|
|
1094
|
+
v3Paths[pathStr] = v3PathItem;
|
|
1095
|
+
}
|
|
1096
|
+
const securitySchemes = convertV2SecurityDefs(resolved.securityDefinitions);
|
|
1097
|
+
return {
|
|
1098
|
+
openapi: "3.0.0",
|
|
1099
|
+
info: resolved.info,
|
|
1100
|
+
paths: v3Paths,
|
|
1101
|
+
tags: resolved.tags ?? [],
|
|
1102
|
+
...resolved.externalDocs ? { externalDocs: resolved.externalDocs } : {},
|
|
1103
|
+
...Object.keys(securitySchemes).length > 0 ? { components: { securitySchemes } } : {}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function convertV2SecurityDefs(defs) {
|
|
1107
|
+
if (!defs)
|
|
1108
|
+
return {};
|
|
1109
|
+
const result = {};
|
|
1110
|
+
for (const [name, def] of Object.entries(defs)) {
|
|
1111
|
+
if (def.type === "apiKey") {
|
|
1112
|
+
result[name] = { type: "apiKey", name: def.name, in: def.in };
|
|
1113
|
+
} else if (def.type === "basic") {
|
|
1114
|
+
result[name] = { type: "http", scheme: "basic" };
|
|
1115
|
+
} else if (def.type === "oauth2") {
|
|
1116
|
+
const v2 = def;
|
|
1117
|
+
const flow = { authorizationUrl: v2.authorizationUrl ?? "", tokenUrl: v2.tokenUrl ?? "", scopes: v2.scopes ?? {} };
|
|
1118
|
+
const flows = {};
|
|
1119
|
+
if (v2.flow === "implicit")
|
|
1120
|
+
flows.implicit = { authorizationUrl: flow.authorizationUrl, scopes: flow.scopes };
|
|
1121
|
+
else if (v2.flow === "password")
|
|
1122
|
+
flows.password = { tokenUrl: flow.tokenUrl, scopes: flow.scopes };
|
|
1123
|
+
else if (v2.flow === "application")
|
|
1124
|
+
flows.clientCredentials = { tokenUrl: flow.tokenUrl, scopes: flow.scopes };
|
|
1125
|
+
else if (v2.flow === "accessCode")
|
|
1126
|
+
flows.authorizationCode = { authorizationUrl: flow.authorizationUrl, tokenUrl: flow.tokenUrl, scopes: flow.scopes };
|
|
1127
|
+
result[name] = { type: "oauth2", flows };
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return result;
|
|
1131
|
+
}
|
|
1132
|
+
function convertV2Operation(op) {
|
|
1133
|
+
const params = op.parameters ?? [];
|
|
1134
|
+
const v3Params = params.filter((p) => p.in !== "body").map((p) => ({
|
|
1135
|
+
name: p.name,
|
|
1136
|
+
in: p.in,
|
|
1137
|
+
required: p.required ?? false,
|
|
1138
|
+
description: p.description,
|
|
1139
|
+
schema: { type: p.type ?? "string", format: p.format }
|
|
1140
|
+
}));
|
|
1141
|
+
const bodyParam = params.find((p) => p.in === "body");
|
|
1142
|
+
let requestBody;
|
|
1143
|
+
if (bodyParam?.schema) {
|
|
1144
|
+
requestBody = {
|
|
1145
|
+
required: bodyParam.required ?? false,
|
|
1146
|
+
content: {
|
|
1147
|
+
"application/json": {
|
|
1148
|
+
schema: bodyParam.schema
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
const v3Responses = {};
|
|
1154
|
+
for (const [status, resp] of Object.entries(op.responses ?? {})) {
|
|
1155
|
+
const v2Resp = resp;
|
|
1156
|
+
const v3Resp = {
|
|
1157
|
+
description: v2Resp.description ?? ""
|
|
1158
|
+
};
|
|
1159
|
+
if (v2Resp.schema) {
|
|
1160
|
+
v3Resp.content = {
|
|
1161
|
+
"application/json": {
|
|
1162
|
+
schema: v2Resp.schema
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
v3Responses[status] = v3Resp;
|
|
1167
|
+
}
|
|
1168
|
+
const result = {
|
|
1169
|
+
operationId: op.operationId,
|
|
1170
|
+
summary: op.summary,
|
|
1171
|
+
description: op.description,
|
|
1172
|
+
tags: op.tags,
|
|
1173
|
+
parameters: v3Params,
|
|
1174
|
+
responses: v3Responses
|
|
1175
|
+
};
|
|
1176
|
+
if (requestBody) {
|
|
1177
|
+
result.requestBody = requestBody;
|
|
1178
|
+
}
|
|
1179
|
+
return result;
|
|
1180
|
+
}
|
|
1181
|
+
var init_openapi = __esm(() => {
|
|
1182
|
+
init_schema();
|
|
635
1183
|
});
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
]);
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
1184
|
+
|
|
1185
|
+
// src/lib/api-routes.ts
|
|
1186
|
+
import slugify from "slugify";
|
|
1187
|
+
function getSpecSlug(spec) {
|
|
1188
|
+
return slugify(spec.name, { lower: true, strict: true });
|
|
1189
|
+
}
|
|
1190
|
+
function deriveOperationId(method, path8) {
|
|
1191
|
+
const slug = path8.replace(/[/{}\-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
1192
|
+
return `${method}_${slug || "root"}`;
|
|
1193
|
+
}
|
|
1194
|
+
function getOperationId(op, method, path8) {
|
|
1195
|
+
return op.operationId || deriveOperationId(method, path8);
|
|
1196
|
+
}
|
|
1197
|
+
function buildApiRoutes(specs) {
|
|
1198
|
+
const routes = [];
|
|
1199
|
+
for (const spec of specs) {
|
|
1200
|
+
const specSlug = getSpecSlug(spec);
|
|
1201
|
+
const paths = spec.document.paths ?? {};
|
|
1202
|
+
for (const [pathStr, pathItem] of Object.entries(paths)) {
|
|
1203
|
+
if (!pathItem)
|
|
1204
|
+
continue;
|
|
1205
|
+
for (const method of ["get", "post", "put", "delete", "patch"]) {
|
|
1206
|
+
const op = pathItem[method];
|
|
1207
|
+
if (!op)
|
|
1208
|
+
continue;
|
|
1209
|
+
const opId = getOperationId(op, method, pathStr);
|
|
1210
|
+
routes.push({ slug: [specSlug, encodeURIComponent(opId)] });
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return routes;
|
|
1215
|
+
}
|
|
1216
|
+
var init_api_routes = () => {};
|
|
1217
|
+
|
|
1218
|
+
// src/lib/llms.ts
|
|
1219
|
+
function buildLlmsTxt(config2, pages, version) {
|
|
1220
|
+
const versionLabel = getVersionLabel(config2, version);
|
|
1221
|
+
const heading = versionLabel ? `# ${config2.site.title} — ${versionLabel}` : `# ${config2.site.title}`;
|
|
1222
|
+
const description = config2.site.description ?? "";
|
|
1223
|
+
const index = pages.map((p) => {
|
|
1224
|
+
const mdUrl = p.url === "/" ? "/index.md" : `${p.url}.md`;
|
|
1225
|
+
const title = p.title?.trim() || p.url;
|
|
1226
|
+
return `- [${title}](${mdUrl})`;
|
|
1227
|
+
}).join(`
|
|
1228
|
+
`);
|
|
1229
|
+
const parts = [heading];
|
|
1230
|
+
if (description)
|
|
1231
|
+
parts.push(description);
|
|
1232
|
+
parts.push(index);
|
|
1233
|
+
return parts.join(`
|
|
1234
|
+
|
|
1235
|
+
`);
|
|
1236
|
+
}
|
|
1237
|
+
function getVersionLabel(config2, version) {
|
|
1238
|
+
if (version.dir === null)
|
|
1239
|
+
return config2.latest?.label ?? null;
|
|
1240
|
+
return config2.versions?.find((v) => v.dir === version.dir)?.label ?? null;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// src/server/routes/og-utils.ts
|
|
1244
|
+
var exports_og_utils = {};
|
|
1245
|
+
__export(exports_og_utils, {
|
|
1246
|
+
loadLogo: () => loadLogo,
|
|
1247
|
+
loadFont: () => loadFont,
|
|
1248
|
+
getLogoDataUri: () => getLogoDataUri
|
|
647
1249
|
});
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1250
|
+
import fs5 from "node:fs/promises";
|
|
1251
|
+
import path8 from "node:path";
|
|
1252
|
+
function getLogoDataUri(data, filePath) {
|
|
1253
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
1254
|
+
const mime = MIME_MAP[ext];
|
|
1255
|
+
if (!mime)
|
|
1256
|
+
return null;
|
|
1257
|
+
return `data:${mime};base64,${data.toString("base64")}`;
|
|
1258
|
+
}
|
|
1259
|
+
async function loadLogo(projectRoot, logoPath) {
|
|
1260
|
+
try {
|
|
1261
|
+
const filePath = path8.resolve(projectRoot, "public", logoPath.replace(/^\//, ""));
|
|
1262
|
+
const data = await fs5.readFile(filePath);
|
|
1263
|
+
return getLogoDataUri(data, filePath);
|
|
1264
|
+
} catch {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
async function loadFont(packageRoot) {
|
|
1269
|
+
const fontPath = path8.resolve(packageRoot, "src/fonts/Inter-Regular.ttf");
|
|
1270
|
+
const buffer = await fs5.readFile(fontPath);
|
|
1271
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
1272
|
+
}
|
|
1273
|
+
var MIME_MAP;
|
|
1274
|
+
var init_og_utils = __esm(() => {
|
|
1275
|
+
MIME_MAP = {
|
|
1276
|
+
".svg": "image/svg+xml",
|
|
1277
|
+
".png": "image/png",
|
|
1278
|
+
".jpg": "image/jpeg",
|
|
1279
|
+
".jpeg": "image/jpeg"
|
|
1280
|
+
};
|
|
651
1281
|
});
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1282
|
+
|
|
1283
|
+
// src/lib/snippet-generators.ts
|
|
1284
|
+
var exports_snippet_generators = {};
|
|
1285
|
+
__export(exports_snippet_generators, {
|
|
1286
|
+
generateTypeScript: () => generateTypeScript,
|
|
1287
|
+
generatePython: () => generatePython,
|
|
1288
|
+
generateGo: () => generateGo,
|
|
1289
|
+
generateCurl: () => generateCurl
|
|
659
1290
|
});
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1291
|
+
function generateCurl({ method, url, headers, body }) {
|
|
1292
|
+
const parts = [`curl -X ${method} '${url}'`];
|
|
1293
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1294
|
+
parts.push(` -H '${key}: ${value}'`);
|
|
1295
|
+
}
|
|
1296
|
+
if (body) {
|
|
1297
|
+
parts.push(` -d '${body}'`);
|
|
1298
|
+
}
|
|
1299
|
+
return parts.join(" \\\n");
|
|
1300
|
+
}
|
|
1301
|
+
function generatePython({ method, url, headers, body }) {
|
|
1302
|
+
const lines = ["import requests", ""];
|
|
1303
|
+
const methodLower = method.toLowerCase();
|
|
1304
|
+
const headerEntries = Object.entries(headers);
|
|
1305
|
+
lines.push(`response = requests.${methodLower}(`);
|
|
1306
|
+
lines.push(` "${url}",`);
|
|
1307
|
+
if (headerEntries.length > 0) {
|
|
1308
|
+
lines.push(" headers={");
|
|
1309
|
+
for (const [key, value] of headerEntries) {
|
|
1310
|
+
lines.push(` "${key}": "${value}",`);
|
|
1311
|
+
}
|
|
1312
|
+
lines.push(" },");
|
|
1313
|
+
}
|
|
1314
|
+
if (body) {
|
|
1315
|
+
lines.push(` json=${body},`);
|
|
1316
|
+
}
|
|
1317
|
+
lines.push(")");
|
|
1318
|
+
lines.push("print(response.json())");
|
|
1319
|
+
return lines.join(`
|
|
1320
|
+
`);
|
|
1321
|
+
}
|
|
1322
|
+
function generateGo({ method, url, headers, body }) {
|
|
1323
|
+
const lines = [];
|
|
1324
|
+
if (body) {
|
|
1325
|
+
lines.push("payload := strings.NewReader(`" + body + "`)");
|
|
1326
|
+
lines.push("");
|
|
1327
|
+
lines.push(`req, _ := http.NewRequest("${method}", "${url}", payload)`);
|
|
1328
|
+
} else {
|
|
1329
|
+
lines.push(`req, _ := http.NewRequest("${method}", "${url}", nil)`);
|
|
1330
|
+
}
|
|
1331
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1332
|
+
lines.push(`req.Header.Set("${key}", "${value}")`);
|
|
1333
|
+
}
|
|
1334
|
+
lines.push("");
|
|
1335
|
+
lines.push("resp, _ := http.DefaultClient.Do(req)");
|
|
1336
|
+
lines.push("defer resp.Body.Close()");
|
|
1337
|
+
return lines.join(`
|
|
1338
|
+
`);
|
|
1339
|
+
}
|
|
1340
|
+
function generateTypeScript({ method, url, headers, body }) {
|
|
1341
|
+
const lines = [];
|
|
1342
|
+
const headerEntries = Object.entries(headers);
|
|
1343
|
+
lines.push(`const response = await fetch("${url}", {`);
|
|
1344
|
+
lines.push(` method: "${method}",`);
|
|
1345
|
+
if (headerEntries.length > 0) {
|
|
1346
|
+
lines.push(" headers: {");
|
|
1347
|
+
for (const [key, value] of headerEntries) {
|
|
1348
|
+
lines.push(` "${key}": "${value}",`);
|
|
1349
|
+
}
|
|
1350
|
+
lines.push(" },");
|
|
1351
|
+
}
|
|
1352
|
+
if (body) {
|
|
1353
|
+
lines.push(` body: JSON.stringify(${body}),`);
|
|
1354
|
+
}
|
|
1355
|
+
lines.push("});");
|
|
1356
|
+
lines.push("const data = await response.json();");
|
|
1357
|
+
return lines.join(`
|
|
1358
|
+
`);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// src/cli/commands/static-generate.ts
|
|
1362
|
+
var exports_static_generate = {};
|
|
1363
|
+
__export(exports_static_generate, {
|
|
1364
|
+
generateStaticSite: () => generateStaticSite
|
|
673
1365
|
});
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
path: ["versions"]
|
|
695
|
-
}).refine((cfg) => !cfg.versions || cfg.versions.every((v) => allUnique(v.content, (c) => c.dir)), {
|
|
696
|
-
message: "versions[].content[].dir must be unique within each version",
|
|
697
|
-
path: ["versions"]
|
|
698
|
-
}).refine((cfg) => !cfg.versions || cfg.versions.length === 0 || !!cfg.latest, {
|
|
699
|
-
message: "latest is required when versions are declared",
|
|
700
|
-
path: ["latest"]
|
|
701
|
-
}).refine((cfg) => {
|
|
702
|
-
if (!cfg.versions)
|
|
703
|
-
return true;
|
|
704
|
-
const contentDirs = new Set(cfg.content.map((c) => c.dir));
|
|
705
|
-
return !cfg.versions.some((v) => contentDirs.has(v.dir));
|
|
706
|
-
}, {
|
|
707
|
-
message: "versions[].dir must not overlap with content[].dir — the URL segment would be shadowed",
|
|
708
|
-
path: ["versions"]
|
|
709
|
-
}).superRefine((cfg, ctx) => {
|
|
710
|
-
const reserved = new Set(RESERVED_ROUTE_SEGMENTS);
|
|
711
|
-
const message = `dir must not be a reserved route segment: ${RESERVED_ROUTE_SEGMENTS.join(", ")}`;
|
|
712
|
-
cfg.content.forEach((c, i) => {
|
|
713
|
-
if (reserved.has(c.dir)) {
|
|
714
|
-
ctx.addIssue({ code: "custom", message, path: ["content", i, "dir"] });
|
|
1366
|
+
import fs6 from "node:fs/promises";
|
|
1367
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1368
|
+
import path9 from "node:path";
|
|
1369
|
+
import matter from "gray-matter";
|
|
1370
|
+
import chalk2 from "chalk";
|
|
1371
|
+
import satori from "satori";
|
|
1372
|
+
import sharp from "sharp";
|
|
1373
|
+
function extractHeadingsAndBody(markdown) {
|
|
1374
|
+
const withoutFrontmatter = markdown.replace(/^---[\s\S]*?---/m, "");
|
|
1375
|
+
const headings = [];
|
|
1376
|
+
const lines = [];
|
|
1377
|
+
for (const line of withoutFrontmatter.split(`
|
|
1378
|
+
`)) {
|
|
1379
|
+
const headingMatch = line.match(/^#{1,6}\s+(.+)/);
|
|
1380
|
+
if (headingMatch) {
|
|
1381
|
+
headings.push(headingMatch[1]);
|
|
1382
|
+
} else if (!line.startsWith("import ") && !line.startsWith("export ") && !line.startsWith("```")) {
|
|
1383
|
+
const cleaned = line.replace(/<[^>]+>/g, "").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[*_~`]+/g, "").trim();
|
|
1384
|
+
if (cleaned)
|
|
1385
|
+
lines.push(cleaned);
|
|
715
1386
|
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1387
|
+
}
|
|
1388
|
+
return { headings: headings.join(`
|
|
1389
|
+
`), body: lines.join(" ") };
|
|
1390
|
+
}
|
|
1391
|
+
function extractImages(markdown) {
|
|
1392
|
+
const images = [];
|
|
1393
|
+
const seen = new Set;
|
|
1394
|
+
function add(src) {
|
|
1395
|
+
const clean = src.split("?")[0];
|
|
1396
|
+
if (clean && !seen.has(clean)) {
|
|
1397
|
+
seen.add(clean);
|
|
1398
|
+
images.push(clean);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
const mdRegex = /!\[[^\]]*\]\(([^)]+)\)/g;
|
|
1402
|
+
let match;
|
|
1403
|
+
while ((match = mdRegex.exec(markdown)) !== null)
|
|
1404
|
+
add(match[1]);
|
|
1405
|
+
const htmlRegex = /<img\b[^>]*\bsrc=["']([^"']+)["']/gi;
|
|
1406
|
+
while ((match = htmlRegex.exec(markdown)) !== null)
|
|
1407
|
+
add(match[1]);
|
|
1408
|
+
return images;
|
|
1409
|
+
}
|
|
1410
|
+
function titleFromSlug(slug) {
|
|
1411
|
+
return slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1412
|
+
}
|
|
1413
|
+
async function scanContentDir(contentDir, contentMirrorRoot, prefix = []) {
|
|
1414
|
+
const pages = [];
|
|
1415
|
+
async function scan(dir, slugPrefix) {
|
|
1416
|
+
let entries;
|
|
1417
|
+
try {
|
|
1418
|
+
entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
1419
|
+
} catch {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
for (const entry of entries) {
|
|
1423
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
1424
|
+
continue;
|
|
1425
|
+
const fullPath = path9.join(dir, entry.name);
|
|
1426
|
+
if (entry.isDirectory()) {
|
|
1427
|
+
await scan(fullPath, [...slugPrefix, entry.name]);
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
if (!entry.name.endsWith(".mdx") && !entry.name.endsWith(".md"))
|
|
1431
|
+
continue;
|
|
1432
|
+
const raw = await fs6.readFile(fullPath, "utf-8");
|
|
1433
|
+
const { data: fm, content: content2 } = matter(raw);
|
|
1434
|
+
if (fm.draft === true)
|
|
1435
|
+
continue;
|
|
1436
|
+
const baseName = entry.name.replace(/\.(mdx|md)$/, "");
|
|
1437
|
+
const isIndex = baseName === "index" || baseName.toLowerCase() === "readme";
|
|
1438
|
+
const slugs = isIndex ? slugPrefix : [...slugPrefix, baseName];
|
|
1439
|
+
const url = slugs.length === 0 ? "/" : `/${slugs.join("/")}`;
|
|
1440
|
+
const originalRelative = path9.relative(contentMirrorRoot, fullPath);
|
|
1441
|
+
const normalizedRelative = isIndex && baseName.toLowerCase() === "readme" ? originalRelative.replace(/readme\.(mdx?)$/i, `index.$1`) : originalRelative;
|
|
1442
|
+
pages.push({
|
|
1443
|
+
slugs,
|
|
1444
|
+
url,
|
|
1445
|
+
relativePath: normalizedRelative,
|
|
1446
|
+
originalPath: originalRelative,
|
|
1447
|
+
frontmatter: {
|
|
1448
|
+
title: fm.title ?? titleFromSlug(slugs[slugs.length - 1] ?? "Home"),
|
|
1449
|
+
description: fm.description,
|
|
1450
|
+
order: fm.order,
|
|
1451
|
+
icon: fm.icon,
|
|
1452
|
+
lastModified: fm.lastModified,
|
|
1453
|
+
draft: fm.draft
|
|
1454
|
+
},
|
|
1455
|
+
rawContent: content2,
|
|
1456
|
+
images: extractImages(content2).map((img) => {
|
|
1457
|
+
if (img.startsWith("http"))
|
|
1458
|
+
return img;
|
|
1459
|
+
if (img.startsWith("/"))
|
|
1460
|
+
return `/_content${img}`;
|
|
1461
|
+
const dirRelative = path9.dirname(normalizedRelative);
|
|
1462
|
+
return `/_content/${path9.join(dirRelative, img).replace(/\\/g, "/")}`;
|
|
1463
|
+
})
|
|
723
1464
|
});
|
|
724
1465
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1466
|
+
}
|
|
1467
|
+
await scan(contentDir, prefix);
|
|
1468
|
+
pages.sort((a, b) => {
|
|
1469
|
+
const orderA = a.frontmatter.order ?? Number.MAX_SAFE_INTEGER;
|
|
1470
|
+
const orderB = b.frontmatter.order ?? Number.MAX_SAFE_INTEGER;
|
|
1471
|
+
if (orderA !== orderB)
|
|
1472
|
+
return orderA - orderB;
|
|
1473
|
+
return a.url.localeCompare(b.url);
|
|
1474
|
+
});
|
|
1475
|
+
return pages;
|
|
1476
|
+
}
|
|
1477
|
+
async function scanAllContent(projectRoot, config2, packageRoot) {
|
|
1478
|
+
const contentMirror = path9.resolve(packageRoot, ".content");
|
|
1479
|
+
const pages = [];
|
|
1480
|
+
for (const root of getLatestContentRoots(config2)) {
|
|
1481
|
+
const contentDir = path9.resolve(contentMirror, root.contentDir);
|
|
1482
|
+
const scanned = await scanContentDir(contentDir, contentMirror, [root.contentDir]);
|
|
1483
|
+
pages.push(...scanned);
|
|
1484
|
+
}
|
|
1485
|
+
for (const version of config2.versions ?? []) {
|
|
1486
|
+
for (const root of getVersionContentRoots(config2, version.dir)) {
|
|
1487
|
+
const contentDir = path9.resolve(contentMirror, version.dir, root.contentDir);
|
|
1488
|
+
const scanned = await scanContentDir(contentDir, contentMirror, [version.dir, root.contentDir]);
|
|
1489
|
+
pages.push(...scanned);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return pages;
|
|
1493
|
+
}
|
|
1494
|
+
async function scanFolderMeta(contentMirrorRoot, config2) {
|
|
1495
|
+
const metaMap = new Map;
|
|
1496
|
+
async function scanDir(dir, slugPrefix) {
|
|
1497
|
+
let entries;
|
|
1498
|
+
try {
|
|
1499
|
+
entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
1500
|
+
} catch {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
const metaPath = path9.join(dir, "meta.json");
|
|
1504
|
+
try {
|
|
1505
|
+
const raw = await fs6.readFile(metaPath, "utf-8");
|
|
1506
|
+
const meta = JSON.parse(raw);
|
|
1507
|
+
const folderPath = "/" + slugPrefix.join("/");
|
|
1508
|
+
metaMap.set(folderPath, meta);
|
|
1509
|
+
} catch {}
|
|
1510
|
+
for (const entry of entries) {
|
|
1511
|
+
if (!entry.isDirectory() || entry.name.startsWith("."))
|
|
1512
|
+
continue;
|
|
1513
|
+
await scanDir(path9.join(dir, entry.name), [...slugPrefix, entry.name]);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
for (const root of getLatestContentRoots(config2)) {
|
|
1517
|
+
const contentDir = path9.resolve(contentMirrorRoot, root.contentDir);
|
|
1518
|
+
await scanDir(contentDir, [root.contentDir]);
|
|
1519
|
+
}
|
|
1520
|
+
for (const version of config2.versions ?? []) {
|
|
1521
|
+
for (const root of getVersionContentRoots(config2, version.dir)) {
|
|
1522
|
+
const contentDir = path9.resolve(contentMirrorRoot, version.dir, root.contentDir);
|
|
1523
|
+
await scanDir(contentDir, [version.dir, root.contentDir]);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
return metaMap;
|
|
1527
|
+
}
|
|
1528
|
+
function buildPageTree(pages, config2, folderMeta) {
|
|
1529
|
+
const tree = { name: "root", children: [] };
|
|
1530
|
+
const folderMap = new Map;
|
|
1531
|
+
for (const page of pages) {
|
|
1532
|
+
const segments = page.slugs;
|
|
1533
|
+
if (segments.length === 0)
|
|
1534
|
+
continue;
|
|
1535
|
+
let current = tree.children;
|
|
1536
|
+
for (let i = 0;i < segments.length - 1; i++) {
|
|
1537
|
+
const folderPath2 = "/" + segments.slice(0, i + 1).join("/");
|
|
1538
|
+
let folder = folderMap.get(folderPath2);
|
|
1539
|
+
if (!folder) {
|
|
1540
|
+
const meta = folderMeta.get(folderPath2);
|
|
1541
|
+
folder = {
|
|
1542
|
+
type: "folder",
|
|
1543
|
+
name: meta?.title ?? titleFromSlug(segments[i]),
|
|
1544
|
+
url: folderPath2,
|
|
1545
|
+
$order: meta?.order,
|
|
1546
|
+
children: []
|
|
1547
|
+
};
|
|
1548
|
+
folderMap.set(folderPath2, folder);
|
|
1549
|
+
current.push(folder);
|
|
1550
|
+
}
|
|
1551
|
+
current = folder.children;
|
|
1552
|
+
}
|
|
1553
|
+
const pageNode = {
|
|
1554
|
+
type: "page",
|
|
1555
|
+
name: page.frontmatter.title,
|
|
1556
|
+
url: page.url,
|
|
1557
|
+
icon: page.frontmatter.icon,
|
|
1558
|
+
$order: page.frontmatter.order
|
|
1559
|
+
};
|
|
1560
|
+
const folderPath = "/" + segments.slice(0, -1).join("/");
|
|
1561
|
+
const parentFolder = folderMap.get(folderPath);
|
|
1562
|
+
if (parentFolder && segments.length > 1) {
|
|
1563
|
+
const isIndex = page.relativePath.match(/(index|readme)\.(mdx|md)$/i);
|
|
1564
|
+
if (isIndex) {
|
|
1565
|
+
parentFolder.index = pageNode;
|
|
1566
|
+
parentFolder.name = page.frontmatter.title;
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
current.push(pageNode);
|
|
1571
|
+
}
|
|
1572
|
+
for (const root of getLatestContentRoots(config2)) {
|
|
1573
|
+
const rootFolder = folderMap.get(`/${root.contentDir}`);
|
|
1574
|
+
if (rootFolder) {
|
|
1575
|
+
rootFolder.name = root.contentLabel;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
function sortChildren(children) {
|
|
1579
|
+
children.sort((a, b) => {
|
|
1580
|
+
const orderA = a.$order ?? Number.MAX_SAFE_INTEGER;
|
|
1581
|
+
const orderB = b.$order ?? Number.MAX_SAFE_INTEGER;
|
|
1582
|
+
if (orderA !== orderB)
|
|
1583
|
+
return orderA - orderB;
|
|
1584
|
+
return a.name.localeCompare(b.name);
|
|
1585
|
+
});
|
|
1586
|
+
for (const child of children) {
|
|
1587
|
+
if (child.children)
|
|
1588
|
+
sortChildren(child.children);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
sortChildren(tree.children);
|
|
1592
|
+
function stripOrder(node) {
|
|
1593
|
+
delete node.$order;
|
|
1594
|
+
if (node.children)
|
|
1595
|
+
node.children.forEach(stripOrder);
|
|
1596
|
+
if (node.index)
|
|
1597
|
+
delete node.index.$order;
|
|
1598
|
+
}
|
|
1599
|
+
tree.children.forEach(stripOrder);
|
|
1600
|
+
return tree;
|
|
1601
|
+
}
|
|
1602
|
+
function flattenTreeUrls(tree) {
|
|
1603
|
+
const result = [];
|
|
1604
|
+
function walk(nodes) {
|
|
1605
|
+
for (const node of nodes) {
|
|
1606
|
+
if (node.type === "folder") {
|
|
1607
|
+
if (node.index)
|
|
1608
|
+
result.push({ url: node.index.url, title: node.index.name });
|
|
1609
|
+
if (node.children)
|
|
1610
|
+
walk(node.children);
|
|
1611
|
+
} else if (node.type === "page") {
|
|
1612
|
+
result.push({ url: node.url, title: node.name });
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
walk(tree.children);
|
|
1617
|
+
return result;
|
|
1618
|
+
}
|
|
1619
|
+
function computeNavigation(tree) {
|
|
1620
|
+
const navMap = new Map;
|
|
1621
|
+
const ordered = flattenTreeUrls(tree);
|
|
1622
|
+
for (let i = 0;i < ordered.length; i++) {
|
|
1623
|
+
navMap.set(ordered[i].url, {
|
|
1624
|
+
prev: i > 0 ? { url: ordered[i - 1].url, title: ordered[i - 1].title } : null,
|
|
1625
|
+
next: i < ordered.length - 1 ? { url: ordered[i + 1].url, title: ordered[i + 1].title } : null
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
return navMap;
|
|
1629
|
+
}
|
|
1630
|
+
async function loadSpecs(configs, projectRoot) {
|
|
1631
|
+
const results = [];
|
|
1632
|
+
for (const c of configs) {
|
|
1633
|
+
try {
|
|
1634
|
+
results.push(await loadApiSpec(c, projectRoot));
|
|
1635
|
+
} catch {
|
|
1636
|
+
try {
|
|
1637
|
+
const specPath = path9.resolve(projectRoot, c.spec);
|
|
1638
|
+
const raw = await fs6.readFile(specPath, "utf-8");
|
|
1639
|
+
const isYaml = specPath.endsWith(".yaml") || specPath.endsWith(".yml");
|
|
1640
|
+
const rawDoc = isYaml ? (await import("yaml")).parse(raw) : JSON.parse(raw);
|
|
1641
|
+
const doc = rawDoc.openapi?.startsWith("3.") ? resolveDocument(rawDoc) : rawDoc;
|
|
1642
|
+
results.push({
|
|
1643
|
+
name: c.name,
|
|
1644
|
+
basePath: c.basePath,
|
|
1645
|
+
server: { ...c.server, url: c.server.url },
|
|
1646
|
+
auth: c.auth,
|
|
1647
|
+
document: doc
|
|
731
1648
|
});
|
|
1649
|
+
} catch {
|
|
1650
|
+
console.log(chalk2.yellow(` Warning: Skipping spec ${c.name}`));
|
|
732
1651
|
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return results;
|
|
1655
|
+
}
|
|
1656
|
+
async function generatePageDataFiles(pages, navMap, outputDir) {
|
|
1657
|
+
const dataDir = path9.join(outputDir, "data", "pages");
|
|
1658
|
+
await fs6.mkdir(dataDir, { recursive: true });
|
|
1659
|
+
for (const page of pages) {
|
|
1660
|
+
const slugKey = page.slugs.join(",") || "index";
|
|
1661
|
+
const nav = navMap.get(page.url) ?? { prev: null, next: null };
|
|
1662
|
+
const data = {
|
|
1663
|
+
frontmatter: page.frontmatter,
|
|
1664
|
+
relativePath: page.relativePath,
|
|
1665
|
+
originalPath: page.originalPath,
|
|
1666
|
+
images: page.images,
|
|
1667
|
+
prev: nav.prev,
|
|
1668
|
+
next: nav.next
|
|
1669
|
+
};
|
|
1670
|
+
const filePath = path9.join(dataDir, `${slugKey}.json`);
|
|
1671
|
+
await fs6.writeFile(filePath, JSON.stringify(data));
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
async function generateSearchIndex(pages, config2, outputDir, projectRoot) {
|
|
1675
|
+
const docs = [];
|
|
1676
|
+
const contentEntries = config2.content ?? [];
|
|
1677
|
+
for (const page of pages) {
|
|
1678
|
+
const { headings, body } = extractHeadingsAndBody(page.rawContent);
|
|
1679
|
+
const dir = page.url.replace(/^\//, "").split("/")[0];
|
|
1680
|
+
const entry = contentEntries.find((c) => c.dir === dir);
|
|
1681
|
+
docs.push({
|
|
1682
|
+
id: page.url,
|
|
1683
|
+
url: page.url,
|
|
1684
|
+
title: page.frontmatter.title,
|
|
1685
|
+
headings,
|
|
1686
|
+
body: [page.frontmatter.description ?? "", body].join(" "),
|
|
1687
|
+
type: SearchResultType.Page,
|
|
1688
|
+
section: entry?.label ?? dir ?? ""
|
|
733
1689
|
});
|
|
1690
|
+
}
|
|
1691
|
+
const apiConfigs = config2.api ?? [];
|
|
1692
|
+
if (apiConfigs.length) {
|
|
1693
|
+
try {
|
|
1694
|
+
const specs = await loadSpecs(apiConfigs, projectRoot);
|
|
1695
|
+
for (const spec of specs) {
|
|
1696
|
+
const specSlug = getSpecSlug(spec);
|
|
1697
|
+
const paths = spec.document.paths ?? {};
|
|
1698
|
+
for (const [pathStr, pathItem] of Object.entries(paths)) {
|
|
1699
|
+
if (!pathItem)
|
|
1700
|
+
continue;
|
|
1701
|
+
for (const method of ["get", "post", "put", "delete", "patch"]) {
|
|
1702
|
+
const op = pathItem[method];
|
|
1703
|
+
if (!op)
|
|
1704
|
+
continue;
|
|
1705
|
+
const opId = op.operationId ?? `${method}_${pathStr.replace(/[/{}\-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "")}`;
|
|
1706
|
+
const url = `/apis/${specSlug}/${encodeURIComponent(opId)}`;
|
|
1707
|
+
docs.push({
|
|
1708
|
+
id: url,
|
|
1709
|
+
url,
|
|
1710
|
+
title: `${method.toUpperCase()} ${op.summary ?? opId}`,
|
|
1711
|
+
headings: op.summary ?? opId,
|
|
1712
|
+
body: [op.description ?? "", pathStr, method.toUpperCase()].join(" "),
|
|
1713
|
+
type: SearchResultType.Api,
|
|
1714
|
+
section: spec.name
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
} catch {
|
|
1720
|
+
console.log(chalk2.yellow(" Warning: Failed to load API specs for search index"));
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
const dataDir = path9.join(outputDir, "data");
|
|
1724
|
+
await fs6.mkdir(dataDir, { recursive: true });
|
|
1725
|
+
await fs6.writeFile(path9.join(dataDir, "search.json"), JSON.stringify(docs));
|
|
1726
|
+
}
|
|
1727
|
+
async function generateApiSpecs(config2, outputDir, projectRoot) {
|
|
1728
|
+
const specsDir = path9.join(outputDir, "data", "specs");
|
|
1729
|
+
await fs6.mkdir(specsDir, { recursive: true });
|
|
1730
|
+
const latestConfigs = getApiConfigsForVersion(config2, null);
|
|
1731
|
+
if (latestConfigs.length) {
|
|
1732
|
+
try {
|
|
1733
|
+
const specs = await loadSpecs(latestConfigs, projectRoot);
|
|
1734
|
+
await fs6.writeFile(path9.join(specsDir, "latest.json"), JSON.stringify(specs));
|
|
1735
|
+
} catch {
|
|
1736
|
+
console.log(chalk2.yellow(" Warning: Failed to load latest API specs"));
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
for (const version of config2.versions ?? []) {
|
|
1740
|
+
const versionConfigs = getApiConfigsForVersion(config2, version.dir);
|
|
1741
|
+
if (!versionConfigs.length)
|
|
1742
|
+
continue;
|
|
1743
|
+
try {
|
|
1744
|
+
const specs = await loadSpecs(versionConfigs, projectRoot);
|
|
1745
|
+
await fs6.writeFile(path9.join(specsDir, `${version.dir}.json`), JSON.stringify(specs));
|
|
1746
|
+
} catch {
|
|
1747
|
+
console.log(chalk2.yellow(` Warning: Failed to load API specs for version ${version.dir}`));
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
async function generateSitemap(pages, config2, outputDir, projectRoot) {
|
|
1752
|
+
if (!config2.url) {
|
|
1753
|
+
await fs6.writeFile(path9.join(outputDir, "sitemap.xml"), '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>');
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const baseUrl = config2.url.replace(/\/$/, "");
|
|
1757
|
+
const docPages = pages.map((page) => {
|
|
1758
|
+
let lastmod = "";
|
|
1759
|
+
if (page.frontmatter.lastModified) {
|
|
1760
|
+
const d = new Date(page.frontmatter.lastModified);
|
|
1761
|
+
if (!Number.isNaN(d.getTime()))
|
|
1762
|
+
lastmod = `<lastmod>${d.toISOString()}</lastmod>`;
|
|
1763
|
+
}
|
|
1764
|
+
return `<url><loc>${baseUrl}/${page.slugs.join("/")}</loc>${lastmod}</url>`;
|
|
734
1765
|
});
|
|
1766
|
+
const apiPages = [];
|
|
1767
|
+
for (const v of getAllVersions(config2)) {
|
|
1768
|
+
const versionDir = v.isLatest ? null : v.dir;
|
|
1769
|
+
const apiConfigs = getApiConfigsForVersion(config2, versionDir);
|
|
1770
|
+
if (!apiConfigs.length)
|
|
1771
|
+
continue;
|
|
1772
|
+
const prefix = versionDir ? `/${versionDir}` : "";
|
|
1773
|
+
try {
|
|
1774
|
+
const routes = buildApiRoutes(await loadSpecs(apiConfigs, projectRoot));
|
|
1775
|
+
for (const route of routes) {
|
|
1776
|
+
apiPages.push(`<url><loc>${baseUrl}${prefix}/apis/${route.slug.join("/")}</loc></url>`);
|
|
1777
|
+
}
|
|
1778
|
+
} catch {}
|
|
1779
|
+
}
|
|
1780
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1781
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
1782
|
+
<url><loc>${baseUrl}</loc></url>
|
|
1783
|
+
${[...docPages, ...apiPages].join(`
|
|
1784
|
+
`)}
|
|
1785
|
+
</urlset>`;
|
|
1786
|
+
await fs6.writeFile(path9.join(outputDir, "sitemap.xml"), xml);
|
|
1787
|
+
}
|
|
1788
|
+
async function generateRobotsTxt(config2, outputDir) {
|
|
1789
|
+
const sitemap = config2.url ? `
|
|
1790
|
+
Sitemap: ${config2.url}/sitemap.xml` : "";
|
|
1791
|
+
const body = `User-agent: *
|
|
1792
|
+
Allow: /${sitemap}`;
|
|
1793
|
+
await fs6.writeFile(path9.join(outputDir, "robots.txt"), body);
|
|
1794
|
+
}
|
|
1795
|
+
async function generateLlmsTxt(pages, config2, outputDir) {
|
|
1796
|
+
const latestCtx = { dir: null, urlPrefix: "" };
|
|
1797
|
+
const versionPrefixes = (config2.versions ?? []).map((v) => `/${v.dir}`);
|
|
1798
|
+
const latestPages = pages.filter((p) => !versionPrefixes.some((pre) => p.url === pre || p.url.startsWith(`${pre}/`)));
|
|
1799
|
+
const llmsPages = latestPages.map((p) => ({
|
|
1800
|
+
url: p.url,
|
|
1801
|
+
title: p.frontmatter.title
|
|
1802
|
+
}));
|
|
1803
|
+
const body = buildLlmsTxt(config2, llmsPages, latestCtx);
|
|
1804
|
+
await fs6.writeFile(path9.join(outputDir, "llms.txt"), body);
|
|
1805
|
+
}
|
|
1806
|
+
async function generateOgImages(pages, config2, outputDir, packageRoot) {
|
|
1807
|
+
const ogDir = path9.join(outputDir, "og");
|
|
1808
|
+
await fs6.mkdir(ogDir, { recursive: true });
|
|
1809
|
+
const { loadFont: loadFont2 } = await Promise.resolve().then(() => (init_og_utils(), exports_og_utils));
|
|
1810
|
+
const fontData = await loadFont2(packageRoot);
|
|
1811
|
+
const siteName = config2.site.title;
|
|
1812
|
+
for (const page of pages) {
|
|
1813
|
+
const title = page.frontmatter.title;
|
|
1814
|
+
const description = page.frontmatter.description ?? "";
|
|
1815
|
+
const slugKey = page.slugs.join(",") || "index";
|
|
1816
|
+
try {
|
|
1817
|
+
const { createElement: h } = await import("react");
|
|
1818
|
+
const svg = await satori(h("div", {
|
|
1819
|
+
style: {
|
|
1820
|
+
height: "100%",
|
|
1821
|
+
width: "100%",
|
|
1822
|
+
display: "flex",
|
|
1823
|
+
flexDirection: "column",
|
|
1824
|
+
justifyContent: "center",
|
|
1825
|
+
padding: "60px 80px",
|
|
1826
|
+
backgroundColor: "#0a0a0a",
|
|
1827
|
+
color: "#fafafa"
|
|
1828
|
+
}
|
|
1829
|
+
}, h("div", { style: { fontSize: 24, color: "#888", marginBottom: 16 } }, siteName), h("div", {
|
|
1830
|
+
style: {
|
|
1831
|
+
fontSize: 56,
|
|
1832
|
+
fontWeight: 700,
|
|
1833
|
+
lineHeight: 1.2,
|
|
1834
|
+
marginBottom: 24
|
|
1835
|
+
}
|
|
1836
|
+
}, title), description ? h("div", { style: { fontSize: 24, color: "#999", lineHeight: 1.4 } }, description) : null), {
|
|
1837
|
+
width: 1200,
|
|
1838
|
+
height: 630,
|
|
1839
|
+
fonts: [
|
|
1840
|
+
{ name: "Inter", data: fontData, weight: 400, style: "normal" }
|
|
1841
|
+
]
|
|
1842
|
+
});
|
|
1843
|
+
const pngBuffer = await sharp(Buffer.from(svg)).png().toBuffer();
|
|
1844
|
+
await fs6.writeFile(path9.join(ogDir, `${slugKey}.png`), pngBuffer);
|
|
1845
|
+
} catch {}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
async function optimizeImages(pages, packageRoot, outputDir) {
|
|
1849
|
+
const contentDir = path9.resolve(packageRoot, ".content");
|
|
1850
|
+
const seen = new Set;
|
|
1851
|
+
let optimized = 0;
|
|
1852
|
+
for (const page of pages) {
|
|
1853
|
+
for (const imgUrl of page.images) {
|
|
1854
|
+
if (!isLocalImage(imgUrl) || seen.has(imgUrl))
|
|
1855
|
+
continue;
|
|
1856
|
+
seen.add(imgUrl);
|
|
1857
|
+
const relativePath = imgUrl.replace(/^\/_content\//, "");
|
|
1858
|
+
const srcPath = path9.resolve(contentDir, relativePath);
|
|
1859
|
+
if (!srcPath.startsWith(contentDir + path9.sep) && srcPath !== contentDir)
|
|
1860
|
+
continue;
|
|
1861
|
+
if (isSvg(imgUrl)) {
|
|
1862
|
+
const destPath2 = path9.join(outputDir, "_content", relativePath);
|
|
1863
|
+
try {
|
|
1864
|
+
await fs6.mkdir(path9.dirname(destPath2), { recursive: true });
|
|
1865
|
+
await fs6.copyFile(srcPath, destPath2);
|
|
1866
|
+
optimized++;
|
|
1867
|
+
} catch {}
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
const webpRelative = relativePath.replace(/\.[^.]+$/, ".webp");
|
|
1871
|
+
const destPath = path9.join(outputDir, "_content", webpRelative);
|
|
1872
|
+
try {
|
|
1873
|
+
await fs6.mkdir(path9.dirname(destPath), { recursive: true });
|
|
1874
|
+
const source = await fs6.readFile(srcPath);
|
|
1875
|
+
const optimizedBuf = await sharp(source).resize({ width: DEFAULT_WIDTH, withoutEnlargement: true }).webp({ quality: DEFAULT_QUALITY }).toBuffer();
|
|
1876
|
+
await fs6.writeFile(destPath, optimizedBuf);
|
|
1877
|
+
const origDest = path9.join(outputDir, "_content", relativePath);
|
|
1878
|
+
await fs6.mkdir(path9.dirname(origDest), { recursive: true });
|
|
1879
|
+
await fs6.copyFile(srcPath, origDest);
|
|
1880
|
+
optimized++;
|
|
1881
|
+
} catch {}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
if (optimized > 0) {
|
|
1885
|
+
console.log(chalk2.gray(` Optimized ${optimized} images`));
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
async function copyPublicAssets(projectRoot, outputDir) {
|
|
1889
|
+
const publicDir = path9.resolve(projectRoot, "public");
|
|
1890
|
+
if (!existsSync(publicDir))
|
|
1891
|
+
return;
|
|
1892
|
+
async function copyTree(src, dest) {
|
|
1893
|
+
const entries = await fs6.readdir(src, { withFileTypes: true });
|
|
1894
|
+
await fs6.mkdir(dest, { recursive: true });
|
|
1895
|
+
for (const entry of entries) {
|
|
1896
|
+
const srcPath = path9.join(src, entry.name);
|
|
1897
|
+
const destPath = path9.join(dest, entry.name);
|
|
1898
|
+
if (entry.isDirectory()) {
|
|
1899
|
+
await copyTree(srcPath, destPath);
|
|
1900
|
+
} else {
|
|
1901
|
+
await fs6.copyFile(srcPath, destPath);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
await copyTree(publicDir, outputDir);
|
|
1906
|
+
}
|
|
1907
|
+
function readViteManifest(outputDir) {
|
|
1908
|
+
const candidates = [
|
|
1909
|
+
path9.join(outputDir, ".vite", "manifest.json"),
|
|
1910
|
+
path9.join(outputDir, "assets", ".vite", "manifest.json"),
|
|
1911
|
+
path9.join(outputDir, ".vite/manifest.json")
|
|
1912
|
+
];
|
|
1913
|
+
for (const candidate of candidates) {
|
|
1914
|
+
if (existsSync(candidate)) {
|
|
1915
|
+
try {
|
|
1916
|
+
const raw = readFileSync(candidate, "utf-8");
|
|
1917
|
+
return JSON.parse(raw);
|
|
1918
|
+
} catch {
|
|
1919
|
+
continue;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1925
|
+
async function generateSpaIndex(config2, tree, outputDir) {
|
|
1926
|
+
const manifest = readViteManifest(outputDir);
|
|
1927
|
+
let entryJs = "";
|
|
1928
|
+
const cssFiles = [];
|
|
1929
|
+
const preloadFiles = [];
|
|
1930
|
+
if (manifest) {
|
|
1931
|
+
for (const [, entry] of Object.entries(manifest)) {
|
|
1932
|
+
if (entry.isEntry) {
|
|
1933
|
+
entryJs = `/${entry.file}`;
|
|
1934
|
+
if (entry.css) {
|
|
1935
|
+
cssFiles.push(...entry.css.map((f) => `/${f}`));
|
|
1936
|
+
}
|
|
1937
|
+
if (entry.imports) {
|
|
1938
|
+
for (const imp of entry.imports) {
|
|
1939
|
+
const impEntry = manifest[imp];
|
|
1940
|
+
if (impEntry) {
|
|
1941
|
+
preloadFiles.push(`/${impEntry.file}`);
|
|
1942
|
+
if (impEntry.css) {
|
|
1943
|
+
cssFiles.push(...impEntry.css.map((f) => `/${f}`));
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
break;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
if (!entryJs) {
|
|
1953
|
+
throw new Error("Could not determine Vite client entry from manifest — static index generation aborted");
|
|
1954
|
+
}
|
|
1955
|
+
const latestCtx = { dir: null, urlPrefix: "" };
|
|
1956
|
+
const pageData = {
|
|
1957
|
+
config: config2,
|
|
1958
|
+
tree,
|
|
1959
|
+
version: latestCtx
|
|
1960
|
+
};
|
|
1961
|
+
const safeJson = JSON.stringify(pageData).replace(/</g, "\\u003c");
|
|
1962
|
+
const cssLinks = [...new Set(cssFiles)].map((f) => ` <link rel="stylesheet" href="${f}">`).join(`
|
|
1963
|
+
`);
|
|
1964
|
+
const preloadLinks = [...new Set(preloadFiles)].map((f) => ` <link rel="modulepreload" href="${f}">`).join(`
|
|
1965
|
+
`);
|
|
1966
|
+
const html = `<!DOCTYPE html>
|
|
1967
|
+
<html lang="en">
|
|
1968
|
+
<head>
|
|
1969
|
+
<meta charset="UTF-8">
|
|
1970
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1971
|
+
<link rel="icon" href="/favicon.ico">
|
|
1972
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
1973
|
+
${cssLinks}
|
|
1974
|
+
${preloadLinks}
|
|
1975
|
+
<script type="module" src="${entryJs}"></script>
|
|
1976
|
+
<script>
|
|
1977
|
+
window.__STATIC_MODE__ = true;
|
|
1978
|
+
window.__PAGE_DATA__ = ${safeJson};
|
|
1979
|
+
</script>
|
|
1980
|
+
</head>
|
|
1981
|
+
<body>
|
|
1982
|
+
<div id="root"></div>
|
|
1983
|
+
</body>
|
|
1984
|
+
</html>`;
|
|
1985
|
+
await fs6.writeFile(path9.join(outputDir, "index.html"), html);
|
|
1986
|
+
}
|
|
1987
|
+
async function generateMarkdownFiles(pages, apiSpecs, outputDir) {
|
|
1988
|
+
for (const page of pages) {
|
|
1989
|
+
const mdPath = page.url === "/" ? "/index.md" : `${page.url}.md`;
|
|
1990
|
+
const outPath = path9.join(outputDir, mdPath);
|
|
1991
|
+
await fs6.mkdir(path9.dirname(outPath), { recursive: true });
|
|
1992
|
+
await fs6.writeFile(outPath, page.rawContent);
|
|
1993
|
+
}
|
|
1994
|
+
if (!apiSpecs.length)
|
|
1995
|
+
return;
|
|
1996
|
+
const { flattenSchema: flattenSchema2, generateExampleJson: generateExampleJson2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
1997
|
+
const { generateCurl: generateCurl2 } = await Promise.resolve().then(() => exports_snippet_generators);
|
|
1998
|
+
for (const spec of apiSpecs) {
|
|
1999
|
+
const specSlug = getSpecSlug(spec);
|
|
2000
|
+
const paths = spec.document.paths ?? {};
|
|
2001
|
+
for (const [pathStr, pathItem] of Object.entries(paths)) {
|
|
2002
|
+
if (!pathItem)
|
|
2003
|
+
continue;
|
|
2004
|
+
for (const method of ["get", "post", "put", "delete", "patch"]) {
|
|
2005
|
+
const op = pathItem[method];
|
|
2006
|
+
if (!op)
|
|
2007
|
+
continue;
|
|
2008
|
+
const opId = op.operationId ?? `${method}_${pathStr.replace(/[/{}\-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "")}`;
|
|
2009
|
+
const mdPath = `/apis/${specSlug}/${encodeURIComponent(opId)}.md`;
|
|
2010
|
+
const outPath = path9.join(outputDir, mdPath);
|
|
2011
|
+
await fs6.mkdir(path9.dirname(outPath), { recursive: true });
|
|
2012
|
+
const md = buildApiMd(method.toUpperCase(), pathStr, op, spec.server.url, spec.auth, flattenSchema2, generateExampleJson2, generateCurl2);
|
|
2013
|
+
await fs6.writeFile(outPath, md);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
function buildApiMd(method, apiPath, operation, serverUrl, auth, flattenSchema2, generateExampleJson2, generateCurl2) {
|
|
2019
|
+
const lines = [];
|
|
2020
|
+
const params = operation.parameters ?? [];
|
|
2021
|
+
lines.push(`# ${operation.summary ?? `${method} ${apiPath}`}`);
|
|
2022
|
+
lines.push("");
|
|
2023
|
+
if (operation.description) {
|
|
2024
|
+
lines.push(operation.description);
|
|
2025
|
+
lines.push("");
|
|
2026
|
+
}
|
|
2027
|
+
lines.push(`\`${method}\` \`${apiPath}\``);
|
|
2028
|
+
lines.push("");
|
|
2029
|
+
const headerParams = params.filter((p) => p.in === "header");
|
|
2030
|
+
const pathParams = params.filter((p) => p.in === "path");
|
|
2031
|
+
const queryParams = params.filter((p) => p.in === "query");
|
|
2032
|
+
if (auth || headerParams.length > 0) {
|
|
2033
|
+
lines.push("## Authorization");
|
|
2034
|
+
lines.push("");
|
|
2035
|
+
lines.push("| Header | Type | Required | Description |");
|
|
2036
|
+
lines.push("| --- | --- | --- | --- |");
|
|
2037
|
+
if (auth)
|
|
2038
|
+
lines.push(`| \`${auth.header}\` | string | Yes | ${auth.placeholder ?? "API key"} |`);
|
|
2039
|
+
for (const p of headerParams) {
|
|
2040
|
+
const schema = p.schema ?? {};
|
|
2041
|
+
lines.push(`| \`${p.name}\` | ${schema.type ?? "string"} | ${p.required ? "Yes" : "No"} | ${p.description ?? ""} |`);
|
|
2042
|
+
}
|
|
2043
|
+
lines.push("");
|
|
2044
|
+
}
|
|
2045
|
+
if (pathParams.length > 0) {
|
|
2046
|
+
lines.push("## Path Parameters");
|
|
2047
|
+
lines.push("");
|
|
2048
|
+
lines.push("| Parameter | Type | Required | Description |");
|
|
2049
|
+
lines.push("| --- | --- | --- | --- |");
|
|
2050
|
+
for (const p of pathParams) {
|
|
2051
|
+
const schema = p.schema ?? {};
|
|
2052
|
+
lines.push(`| \`${p.name}\` | ${schema.type ?? "string"} | ${p.required ? "Yes" : "No"} | ${p.description ?? ""} |`);
|
|
2053
|
+
}
|
|
2054
|
+
lines.push("");
|
|
2055
|
+
}
|
|
2056
|
+
if (queryParams.length > 0) {
|
|
2057
|
+
lines.push("## Query Parameters");
|
|
2058
|
+
lines.push("");
|
|
2059
|
+
lines.push("| Parameter | Type | Required | Description |");
|
|
2060
|
+
lines.push("| --- | --- | --- | --- |");
|
|
2061
|
+
for (const p of queryParams) {
|
|
2062
|
+
const schema = p.schema ?? {};
|
|
2063
|
+
lines.push(`| \`${p.name}\` | ${schema.type ?? "string"} | ${p.required ? "Yes" : "No"} | ${p.description ?? ""} |`);
|
|
2064
|
+
}
|
|
2065
|
+
lines.push("");
|
|
2066
|
+
}
|
|
2067
|
+
const requestBody = operation.requestBody;
|
|
2068
|
+
if (requestBody?.content) {
|
|
2069
|
+
const contentType = Object.keys(requestBody.content)[0];
|
|
2070
|
+
const schema = contentType ? requestBody.content[contentType]?.schema : undefined;
|
|
2071
|
+
if (schema) {
|
|
2072
|
+
lines.push("## Request Body");
|
|
2073
|
+
lines.push("");
|
|
2074
|
+
lines.push(`Content-Type: \`${contentType}\``);
|
|
2075
|
+
lines.push("");
|
|
2076
|
+
const example = generateExampleJson2(schema);
|
|
2077
|
+
lines.push("```json");
|
|
2078
|
+
lines.push(JSON.stringify(example, null, 2));
|
|
2079
|
+
lines.push("```");
|
|
2080
|
+
lines.push("");
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
const responses = operation.responses;
|
|
2084
|
+
if (responses) {
|
|
2085
|
+
lines.push("## Responses");
|
|
2086
|
+
lines.push("");
|
|
2087
|
+
for (const [status, resp] of Object.entries(responses)) {
|
|
2088
|
+
lines.push(`### ${status}${resp.description ? ` — ${resp.description}` : ""}`);
|
|
2089
|
+
lines.push("");
|
|
2090
|
+
const content2 = resp.content ?? {};
|
|
2091
|
+
const contentType = Object.keys(content2)[0];
|
|
2092
|
+
const schema = contentType ? content2[contentType]?.schema : undefined;
|
|
2093
|
+
if (schema) {
|
|
2094
|
+
const example = generateExampleJson2(schema);
|
|
2095
|
+
lines.push("```json");
|
|
2096
|
+
lines.push(JSON.stringify(example, null, 2));
|
|
2097
|
+
lines.push("```");
|
|
2098
|
+
lines.push("");
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
const headers = {};
|
|
2103
|
+
if (auth)
|
|
2104
|
+
headers[auth.header] = auth.placeholder ?? "YOUR_API_KEY";
|
|
2105
|
+
lines.push("## cURL");
|
|
2106
|
+
lines.push("");
|
|
2107
|
+
lines.push("```bash");
|
|
2108
|
+
lines.push(generateCurl2({ method, url: serverUrl + apiPath, headers }));
|
|
2109
|
+
lines.push("```");
|
|
2110
|
+
return lines.join(`
|
|
2111
|
+
`);
|
|
2112
|
+
}
|
|
2113
|
+
async function generateStaticSite(options) {
|
|
2114
|
+
const { projectRoot, config: config2, outputDir, packageRoot } = options;
|
|
2115
|
+
console.log(chalk2.cyan(`
|
|
2116
|
+
Generating static site...`));
|
|
2117
|
+
console.log(chalk2.gray(" Scanning content..."));
|
|
2118
|
+
const pages = await scanAllContent(projectRoot, config2, packageRoot);
|
|
2119
|
+
console.log(chalk2.gray(` Found ${pages.length} pages`));
|
|
2120
|
+
const contentMirror = path9.resolve(packageRoot, ".content");
|
|
2121
|
+
const folderMeta = await scanFolderMeta(contentMirror, config2);
|
|
2122
|
+
const tree = buildPageTree(pages, config2, folderMeta);
|
|
2123
|
+
const navMap = computeNavigation(tree);
|
|
2124
|
+
console.log(chalk2.gray(" Generating page data files..."));
|
|
2125
|
+
await generatePageDataFiles(pages, navMap, outputDir);
|
|
2126
|
+
console.log(chalk2.gray(" Generating search index..."));
|
|
2127
|
+
await generateSearchIndex(pages, config2, outputDir, projectRoot);
|
|
2128
|
+
console.log(chalk2.gray(" Generating API specs..."));
|
|
2129
|
+
await generateApiSpecs(config2, outputDir, projectRoot);
|
|
2130
|
+
const latestApiConfigs = config2.api ?? [];
|
|
2131
|
+
const apiSpecsForMd = latestApiConfigs.length ? await loadSpecs(latestApiConfigs, projectRoot) : [];
|
|
2132
|
+
console.log(chalk2.gray(" Generating markdown files..."));
|
|
2133
|
+
await generateMarkdownFiles(pages, apiSpecsForMd, outputDir);
|
|
2134
|
+
console.log(chalk2.gray(" Generating sitemap.xml..."));
|
|
2135
|
+
await generateSitemap(pages, config2, outputDir, projectRoot);
|
|
2136
|
+
console.log(chalk2.gray(" Generating robots.txt..."));
|
|
2137
|
+
await generateRobotsTxt(config2, outputDir);
|
|
2138
|
+
console.log(chalk2.gray(" Generating llms.txt..."));
|
|
2139
|
+
await generateLlmsTxt(pages, config2, outputDir);
|
|
2140
|
+
console.log(chalk2.gray(" Generating OG images..."));
|
|
2141
|
+
await generateOgImages(pages, config2, outputDir, packageRoot);
|
|
2142
|
+
console.log(chalk2.gray(" Optimizing images..."));
|
|
2143
|
+
await optimizeImages(pages, packageRoot, outputDir);
|
|
2144
|
+
console.log(chalk2.gray(" Copying public assets..."));
|
|
2145
|
+
await copyPublicAssets(projectRoot, outputDir);
|
|
2146
|
+
console.log(chalk2.gray(" Generating SPA index.html..."));
|
|
2147
|
+
await generateSpaIndex(config2, tree, outputDir);
|
|
2148
|
+
console.log(chalk2.green(` Static site generated: ${pages.length} pages`));
|
|
2149
|
+
}
|
|
2150
|
+
var init_static_generate = __esm(() => {
|
|
2151
|
+
init_types();
|
|
2152
|
+
init_config2();
|
|
2153
|
+
init_openapi();
|
|
2154
|
+
init_api_routes();
|
|
2155
|
+
init_image_utils();
|
|
735
2156
|
});
|
|
2157
|
+
|
|
2158
|
+
// src/lib/mdx-error.ts
|
|
2159
|
+
function cleanMessage(raw) {
|
|
2160
|
+
const lines = raw.replace(ANSI_PATTERN, "").split(`
|
|
2161
|
+
`).map((line) => line.replace(/^\[(?:plugin|vite)[^\]]*\]\s*/, "").replace(/^\S*\.mdx?(?::[\d:-]+)*:?\s*/, "").trim()).filter((line) => line && !/^Build failed with \d+ errors?:?$/.test(line));
|
|
2162
|
+
return lines[0] ?? raw.trim();
|
|
2163
|
+
}
|
|
2164
|
+
function parseMdxError(err) {
|
|
2165
|
+
if (!err || typeof err !== "object")
|
|
2166
|
+
return null;
|
|
2167
|
+
const e = err;
|
|
2168
|
+
const rawMessage = e.reason || e.message || "";
|
|
2169
|
+
const missing = rawMessage.match(/Expected component `([^`]+)` to be defined/);
|
|
2170
|
+
if (missing) {
|
|
2171
|
+
return { message: `Unknown component <${missing[1]}> — it is not registered in Chronicle's MDX components.` };
|
|
2172
|
+
}
|
|
2173
|
+
const file = [e.file, e.loc?.file, e.id, ...rawMessage.match(/((?:\/|\.\.?\/)[^\s:]+\.mdx?)/) ?? []].find(isContentFile);
|
|
2174
|
+
if (!file)
|
|
2175
|
+
return null;
|
|
2176
|
+
const place = e.place?.start ?? e.place;
|
|
2177
|
+
let line = e.line ?? place?.line ?? e.loc?.line;
|
|
2178
|
+
let column = e.column ?? place?.column ?? e.loc?.column;
|
|
2179
|
+
if (line == null) {
|
|
2180
|
+
const pos = rawMessage.match(/(\d+):(\d+)(?:-\d+:\d+)?/);
|
|
2181
|
+
if (pos) {
|
|
2182
|
+
line = Number(pos[1]);
|
|
2183
|
+
column = Number(pos[2]);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
return { message: cleanMessage(rawMessage), file: file.split("?")[0], line, column };
|
|
2187
|
+
}
|
|
2188
|
+
function buildCodeFrame(source, line, column, context = 2) {
|
|
2189
|
+
const all = source.split(/\r?\n/);
|
|
2190
|
+
const start = Math.max(1, line - context);
|
|
2191
|
+
const end = Math.min(all.length, line + context);
|
|
2192
|
+
const lines = [];
|
|
2193
|
+
for (let n = start;n <= end; n++) {
|
|
2194
|
+
lines.push({ number: n, text: all[n - 1] ?? "", target: n === line });
|
|
2195
|
+
}
|
|
2196
|
+
return { lines, caretColumn: column };
|
|
2197
|
+
}
|
|
2198
|
+
var ANSI_PATTERN, isContentFile = (p) => !!p && /\.mdx?$/.test(p.split("?")[0]);
|
|
2199
|
+
var init_mdx_error = __esm(() => {
|
|
2200
|
+
ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
2201
|
+
});
|
|
2202
|
+
|
|
2203
|
+
// src/cli/utils/mdx-error-report.ts
|
|
2204
|
+
var exports_mdx_error_report = {};
|
|
2205
|
+
__export(exports_mdx_error_report, {
|
|
2206
|
+
printMdxBuildError: () => printMdxBuildError
|
|
2207
|
+
});
|
|
2208
|
+
import fs7 from "node:fs/promises";
|
|
2209
|
+
import path10 from "node:path";
|
|
2210
|
+
import chalk3 from "chalk";
|
|
2211
|
+
async function printMdxBuildError(err, packageRoot) {
|
|
2212
|
+
const info = parseMdxError(err);
|
|
2213
|
+
if (!info)
|
|
2214
|
+
return false;
|
|
2215
|
+
const contentMirror = path10.resolve(packageRoot, ".content");
|
|
2216
|
+
const displayPath = info.file?.startsWith(contentMirror) ? path10.relative(contentMirror, info.file) : info.file && !path10.relative(process.cwd(), info.file).startsWith("..") ? path10.relative(process.cwd(), info.file) : info.file;
|
|
2217
|
+
console.error();
|
|
2218
|
+
console.error(chalk3.bgRed.white(" MDX Error "), chalk3.red(info.message));
|
|
2219
|
+
if (displayPath) {
|
|
2220
|
+
const position = info.line ? `:${info.line}${info.column ? `:${info.column}` : ""}` : "";
|
|
2221
|
+
console.error(chalk3.dim(` ${displayPath}${position}`));
|
|
2222
|
+
}
|
|
2223
|
+
if (info.file && info.line) {
|
|
2224
|
+
try {
|
|
2225
|
+
const source = await fs7.readFile(info.file, "utf-8");
|
|
2226
|
+
const frame = buildCodeFrame(source, info.line, info.column);
|
|
2227
|
+
const gutter = String(frame.lines[frame.lines.length - 1]?.number ?? 0).length;
|
|
2228
|
+
console.error();
|
|
2229
|
+
for (const { number, text, target } of frame.lines) {
|
|
2230
|
+
const ln = `${target ? ">" : " "} ${String(number).padStart(gutter)} | `;
|
|
2231
|
+
console.error(target ? chalk3.red(ln) + text : chalk3.dim(ln + text));
|
|
2232
|
+
if (target && frame.caretColumn) {
|
|
2233
|
+
console.error(chalk3.red(` ${" ".repeat(gutter)} | ${" ".repeat(frame.caretColumn - 1)}^`));
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
} catch {}
|
|
2237
|
+
}
|
|
2238
|
+
console.error();
|
|
2239
|
+
return true;
|
|
2240
|
+
}
|
|
2241
|
+
var init_mdx_error_report = __esm(() => {
|
|
2242
|
+
init_mdx_error();
|
|
2243
|
+
});
|
|
2244
|
+
|
|
2245
|
+
// src/cli/index.ts
|
|
2246
|
+
import { Command as Command6 } from "commander";
|
|
2247
|
+
|
|
2248
|
+
// src/cli/commands/build.ts
|
|
2249
|
+
import path11 from "node:path";
|
|
2250
|
+
import chalk4 from "chalk";
|
|
2251
|
+
import { Command } from "commander";
|
|
2252
|
+
|
|
736
2253
|
// src/cli/utils/config.ts
|
|
2254
|
+
init_types();
|
|
2255
|
+
import fs from "node:fs/promises";
|
|
2256
|
+
import path from "node:path";
|
|
2257
|
+
import chalk from "chalk";
|
|
2258
|
+
import { parse } from "yaml";
|
|
737
2259
|
function resolveConfigPath(configPath) {
|
|
738
2260
|
if (configPath)
|
|
739
2261
|
return path.resolve(configPath);
|
|
@@ -782,46 +2304,9 @@ import { fileURLToPath } from "url";
|
|
|
782
2304
|
var PACKAGE_ROOT = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
783
2305
|
|
|
784
2306
|
// src/cli/utils/scaffold.ts
|
|
2307
|
+
init_config2();
|
|
785
2308
|
import fs2 from "node:fs/promises";
|
|
786
2309
|
import path3 from "node:path";
|
|
787
|
-
|
|
788
|
-
// src/lib/config.ts
|
|
789
|
-
import { parse as parse2 } from "yaml";
|
|
790
|
-
var defaultConfig = chronicleConfigSchema.parse({
|
|
791
|
-
site: { title: "Documentation" },
|
|
792
|
-
content: [{ dir: "docs", label: "Docs" }],
|
|
793
|
-
theme: { name: "default" },
|
|
794
|
-
search: { enabled: true, placeholder: "Search..." }
|
|
795
|
-
});
|
|
796
|
-
function getLatestContentRoots(config2) {
|
|
797
|
-
return config2.content.map((c) => ({
|
|
798
|
-
versionDir: null,
|
|
799
|
-
versionLabel: config2.latest?.label ?? null,
|
|
800
|
-
contentDir: c.dir,
|
|
801
|
-
contentLabel: c.label,
|
|
802
|
-
contentDescription: c.description,
|
|
803
|
-
contentIcon: c.icon,
|
|
804
|
-
fsPath: `content/${c.dir}`,
|
|
805
|
-
urlPrefix: `/${c.dir}`
|
|
806
|
-
}));
|
|
807
|
-
}
|
|
808
|
-
function getVersionContentRoots(config2, versionDir) {
|
|
809
|
-
const version = config2.versions?.find((v) => v.dir === versionDir);
|
|
810
|
-
if (!version)
|
|
811
|
-
return [];
|
|
812
|
-
return version.content.map((c) => ({
|
|
813
|
-
versionDir: version.dir,
|
|
814
|
-
versionLabel: version.label,
|
|
815
|
-
contentDir: c.dir,
|
|
816
|
-
contentLabel: c.label,
|
|
817
|
-
contentDescription: c.description,
|
|
818
|
-
contentIcon: c.icon,
|
|
819
|
-
fsPath: `versions/${version.dir}/${c.dir}`,
|
|
820
|
-
urlPrefix: `/${version.dir}/${c.dir}`
|
|
821
|
-
}));
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// src/cli/utils/scaffold.ts
|
|
825
2310
|
async function buildContentMirror(mirrorRoot, projectRoot, config2) {
|
|
826
2311
|
await removeMirror(mirrorRoot);
|
|
827
2312
|
await fs2.mkdir(mirrorRoot, { recursive: true });
|
|
@@ -881,28 +2366,54 @@ async function removeMirror(mirrorRoot) {
|
|
|
881
2366
|
}
|
|
882
2367
|
|
|
883
2368
|
// src/cli/commands/build.ts
|
|
884
|
-
var buildCommand = new Command("build").description("Build for production").option("--config <path>", "Path to chronicle.yaml").option("--preset <preset>", "Deploy preset (vercel, cloudflare, node-server)").action(async (options) => {
|
|
2369
|
+
var buildCommand = new Command("build").description("Build for production").option("--config <path>", "Path to chronicle.yaml").option("--preset <preset>", "Deploy preset (vercel, cloudflare, node-server, static)").action(async (options) => {
|
|
885
2370
|
const { config: config2, projectRoot, configPath, preset } = await loadCLIConfig(options.config, {
|
|
886
2371
|
preset: options.preset
|
|
887
2372
|
});
|
|
888
2373
|
await linkContent(projectRoot, config2);
|
|
889
|
-
console.log(
|
|
890
|
-
const { createBuilder } = await import("vite");
|
|
891
|
-
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
2374
|
+
console.log(chalk4.cyan("Building for production..."));
|
|
2375
|
+
const { createBuilder, build } = await import("vite");
|
|
2376
|
+
const { createViteConfig: createViteConfig2, isStaticPreset: isStaticPreset2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
892
2377
|
const viteConfig = await createViteConfig2({
|
|
893
2378
|
packageRoot: PACKAGE_ROOT,
|
|
894
2379
|
projectRoot,
|
|
895
2380
|
configPath,
|
|
896
2381
|
preset
|
|
897
2382
|
});
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
2383
|
+
try {
|
|
2384
|
+
if (isStaticPreset2(preset)) {
|
|
2385
|
+
await build(viteConfig);
|
|
2386
|
+
} else {
|
|
2387
|
+
const builder = await createBuilder({ ...viteConfig, builder: {} });
|
|
2388
|
+
await builder.buildApp();
|
|
2389
|
+
}
|
|
2390
|
+
if (isStaticPreset2(preset)) {
|
|
2391
|
+
const { generateStaticSite: generateStaticSite2 } = await Promise.resolve().then(() => (init_static_generate(), exports_static_generate));
|
|
2392
|
+
const outputDir = path11.resolve(projectRoot, ".output/public");
|
|
2393
|
+
await generateStaticSite2({
|
|
2394
|
+
projectRoot,
|
|
2395
|
+
config: config2,
|
|
2396
|
+
outputDir,
|
|
2397
|
+
packageRoot: PACKAGE_ROOT
|
|
2398
|
+
});
|
|
2399
|
+
console.log(chalk4.green("Static build complete"));
|
|
2400
|
+
console.log(chalk4.cyan(`Output: ${outputDir}`));
|
|
2401
|
+
} else {
|
|
2402
|
+
console.log(chalk4.green("Build complete"));
|
|
2403
|
+
console.log(chalk4.cyan("Run `chronicle start` to start the server"));
|
|
2404
|
+
}
|
|
2405
|
+
} catch (err) {
|
|
2406
|
+
const { printMdxBuildError: printMdxBuildError2 } = await Promise.resolve().then(() => (init_mdx_error_report(), exports_mdx_error_report));
|
|
2407
|
+
if (await printMdxBuildError2(err, PACKAGE_ROOT)) {
|
|
2408
|
+
console.error(chalk4.red("Build failed"));
|
|
2409
|
+
process.exit(1);
|
|
2410
|
+
}
|
|
2411
|
+
throw err;
|
|
2412
|
+
}
|
|
902
2413
|
});
|
|
903
2414
|
|
|
904
2415
|
// src/cli/commands/dev.ts
|
|
905
|
-
import
|
|
2416
|
+
import chalk5 from "chalk";
|
|
906
2417
|
import { Command as Command2 } from "commander";
|
|
907
2418
|
var devCommand = new Command2("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("--config <path>", "Path to chronicle.yaml").option("--host <host>", "Host address", "localhost").option("--preset <preset>", "Deploy preset (bun, node-server, etc.)").action(async (options) => {
|
|
908
2419
|
const { config: config2, projectRoot, configPath } = await loadCLIConfig(options.config);
|
|
@@ -911,7 +2422,7 @@ var devCommand = new Command2("dev").description("Start development server").opt
|
|
|
911
2422
|
if (process.platform === "win32" && !process.env.NITRO_DEV_RUNNER) {
|
|
912
2423
|
process.env.NITRO_DEV_RUNNER = "node-process";
|
|
913
2424
|
}
|
|
914
|
-
console.log(
|
|
2425
|
+
console.log(chalk5.cyan("Starting dev server..."));
|
|
915
2426
|
const { createServer } = await import("vite");
|
|
916
2427
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
917
2428
|
const viteConfig = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot, configPath, preset: options.preset });
|
|
@@ -936,9 +2447,9 @@ var devCommand = new Command2("dev").description("Start development server").opt
|
|
|
936
2447
|
});
|
|
937
2448
|
|
|
938
2449
|
// src/cli/commands/init.ts
|
|
939
|
-
import
|
|
940
|
-
import
|
|
941
|
-
import
|
|
2450
|
+
import fs8 from "node:fs";
|
|
2451
|
+
import path12 from "node:path";
|
|
2452
|
+
import chalk6 from "chalk";
|
|
942
2453
|
import { Command as Command3 } from "commander";
|
|
943
2454
|
import { stringify } from "yaml";
|
|
944
2455
|
var defaultInitConfig = {
|
|
@@ -964,38 +2475,38 @@ var GITIGNORE_ENTRIES = ["node_modules", "dist", ".output"];
|
|
|
964
2475
|
function runInit(projectDir) {
|
|
965
2476
|
const events = [];
|
|
966
2477
|
const defaultDir = defaultInitConfig.content[0].dir;
|
|
967
|
-
const contentDir =
|
|
968
|
-
if (!
|
|
969
|
-
|
|
2478
|
+
const contentDir = path12.join(projectDir, "content", defaultDir);
|
|
2479
|
+
if (!fs8.existsSync(contentDir)) {
|
|
2480
|
+
fs8.mkdirSync(contentDir, { recursive: true });
|
|
970
2481
|
events.push({ type: "created", path: contentDir });
|
|
971
2482
|
}
|
|
972
|
-
const configPath =
|
|
973
|
-
if (!
|
|
974
|
-
|
|
2483
|
+
const configPath = path12.join(projectDir, "chronicle.yaml");
|
|
2484
|
+
if (!fs8.existsSync(configPath)) {
|
|
2485
|
+
fs8.writeFileSync(configPath, stringify(defaultInitConfig));
|
|
975
2486
|
events.push({ type: "created", path: configPath });
|
|
976
2487
|
} else {
|
|
977
2488
|
events.push({ type: "skipped", path: configPath, detail: "already exists" });
|
|
978
2489
|
}
|
|
979
|
-
const contentFiles =
|
|
2490
|
+
const contentFiles = fs8.readdirSync(contentDir);
|
|
980
2491
|
if (contentFiles.length === 0) {
|
|
981
|
-
const indexPath =
|
|
982
|
-
|
|
2492
|
+
const indexPath = path12.join(contentDir, "index.mdx");
|
|
2493
|
+
fs8.writeFileSync(indexPath, sampleMdx);
|
|
983
2494
|
events.push({ type: "created", path: indexPath });
|
|
984
2495
|
}
|
|
985
|
-
const gitignorePath =
|
|
986
|
-
if (
|
|
987
|
-
const existing =
|
|
2496
|
+
const gitignorePath = path12.join(projectDir, ".gitignore");
|
|
2497
|
+
if (fs8.existsSync(gitignorePath)) {
|
|
2498
|
+
const existing = fs8.readFileSync(gitignorePath, "utf-8");
|
|
988
2499
|
const existingLines = new Set(existing.split(/\r?\n/).map((l) => l.trim()).filter(Boolean));
|
|
989
2500
|
const missing = GITIGNORE_ENTRIES.filter((e) => !existingLines.has(e));
|
|
990
2501
|
if (missing.length > 0) {
|
|
991
|
-
|
|
2502
|
+
fs8.appendFileSync(gitignorePath, `
|
|
992
2503
|
${missing.join(`
|
|
993
2504
|
`)}
|
|
994
2505
|
`);
|
|
995
2506
|
events.push({ type: "updated", path: gitignorePath, detail: missing.join(", ") });
|
|
996
2507
|
}
|
|
997
2508
|
} else {
|
|
998
|
-
|
|
2509
|
+
fs8.writeFileSync(gitignorePath, `${GITIGNORE_ENTRIES.join(`
|
|
999
2510
|
`)}
|
|
1000
2511
|
`);
|
|
1001
2512
|
events.push({ type: "created", path: gitignorePath });
|
|
@@ -1004,25 +2515,25 @@ ${missing.join(`
|
|
|
1004
2515
|
}
|
|
1005
2516
|
function formatEvent(e) {
|
|
1006
2517
|
if (e.type === "skipped") {
|
|
1007
|
-
return `${
|
|
2518
|
+
return `${chalk6.yellow("⚠")} ${e.path}${e.detail ? ` ${e.detail}` : ""}`;
|
|
1008
2519
|
}
|
|
1009
2520
|
if (e.type === "updated") {
|
|
1010
|
-
return `${
|
|
2521
|
+
return `${chalk6.green("✓")} Updated ${e.path}${e.detail ? ` (+${e.detail})` : ""}`;
|
|
1011
2522
|
}
|
|
1012
|
-
return `${
|
|
2523
|
+
return `${chalk6.green("✓")} Created ${e.path}`;
|
|
1013
2524
|
}
|
|
1014
2525
|
var initCommand = new Command3("init").description("Initialize a new Chronicle project").action(() => {
|
|
1015
2526
|
const events = runInit(process.cwd());
|
|
1016
2527
|
for (const e of events)
|
|
1017
2528
|
console.log(formatEvent(e));
|
|
1018
|
-
console.log(
|
|
2529
|
+
console.log(chalk6.green(`
|
|
1019
2530
|
✓ Chronicle initialized!`));
|
|
1020
2531
|
console.log(`
|
|
1021
|
-
Run`,
|
|
2532
|
+
Run`, chalk6.cyan("chronicle dev"), "to start development server");
|
|
1022
2533
|
});
|
|
1023
2534
|
|
|
1024
2535
|
// src/cli/commands/serve.ts
|
|
1025
|
-
import
|
|
2536
|
+
import chalk7 from "chalk";
|
|
1026
2537
|
import { Command as Command4 } from "commander";
|
|
1027
2538
|
var serveCommand = new Command4("serve").description("Build and start production server").option("-p, --port <port>", "Port number", "3000").option("--config <path>", "Path to chronicle.yaml").option("--host <host>", "Host address", "localhost").option("--preset <preset>", "Deploy preset (vercel, cloudflare, node-server)").action(async (options) => {
|
|
1028
2539
|
const { config: config2, projectRoot, configPath, preset } = await loadCLIConfig(options.config, {
|
|
@@ -1038,9 +2549,9 @@ var serveCommand = new Command4("serve").description("Build and start production
|
|
|
1038
2549
|
configPath,
|
|
1039
2550
|
preset
|
|
1040
2551
|
});
|
|
1041
|
-
console.log(
|
|
2552
|
+
console.log(chalk7.cyan("Building for production..."));
|
|
1042
2553
|
await build(viteConfig);
|
|
1043
|
-
console.log(
|
|
2554
|
+
console.log(chalk7.cyan("Starting production server..."));
|
|
1044
2555
|
const server = await preview({
|
|
1045
2556
|
...viteConfig,
|
|
1046
2557
|
preview: { port, host: options.host }
|
|
@@ -1049,13 +2560,13 @@ var serveCommand = new Command4("serve").description("Build and start production
|
|
|
1049
2560
|
});
|
|
1050
2561
|
|
|
1051
2562
|
// src/cli/commands/start.ts
|
|
1052
|
-
import
|
|
2563
|
+
import chalk8 from "chalk";
|
|
1053
2564
|
import { Command as Command5 } from "commander";
|
|
1054
2565
|
var startCommand = new Command5("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").option("--config <path>", "Path to chronicle.yaml").option("--host <host>", "Host address", "localhost").action(async (options) => {
|
|
1055
2566
|
const { config: config2, projectRoot, configPath } = await loadCLIConfig(options.config);
|
|
1056
2567
|
const port = parseInt(options.port, 10);
|
|
1057
2568
|
await linkContent(projectRoot, config2);
|
|
1058
|
-
console.log(
|
|
2569
|
+
console.log(chalk8.cyan("Starting production server..."));
|
|
1059
2570
|
const { preview } = await import("vite");
|
|
1060
2571
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
1061
2572
|
const viteConfig = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot, configPath });
|