@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.
Files changed (37) hide show
  1. package/dist/cli/index.js +1763 -252
  2. package/package.json +8 -2
  3. package/src/cli/commands/build.ts +36 -7
  4. package/src/cli/commands/static-generate.ts +1071 -0
  5. package/src/cli/utils/mdx-error-report.ts +48 -0
  6. package/src/components/api/playground-dialog.tsx +65 -14
  7. package/src/components/mdx/code.tsx +8 -3
  8. package/src/components/mdx/image.tsx +15 -1
  9. package/src/components/mdx/index.tsx +10 -3
  10. package/src/components/ui/search.tsx +226 -64
  11. package/src/lib/data-urls.ts +23 -0
  12. package/src/lib/head.tsx +2 -1
  13. package/src/lib/mdx-component-names.ts +15 -0
  14. package/src/lib/mdx-error.test.ts +78 -0
  15. package/src/lib/mdx-error.ts +91 -0
  16. package/src/lib/openapi.ts +1 -1
  17. package/src/lib/page-context.tsx +29 -14
  18. package/src/lib/preload.ts +4 -2
  19. package/src/lib/remark-validate-mdx.test.ts +95 -0
  20. package/src/lib/remark-validate-mdx.ts +85 -0
  21. package/src/lib/route-resolver.ts +7 -0
  22. package/src/lib/static-mode.ts +3 -0
  23. package/src/pages/DocsPage.tsx +3 -2
  24. package/src/pages/NotFound.module.css +10 -0
  25. package/src/pages/RenderError.tsx +18 -0
  26. package/src/server/App.tsx +16 -12
  27. package/src/server/api/apis-proxy.ts +32 -7
  28. package/src/server/dev-error-page.ts +133 -0
  29. package/src/server/entry-server.tsx +6 -0
  30. package/src/server/entry-static.tsx +151 -0
  31. package/src/server/error.ts +13 -1
  32. package/src/server/plugins/telemetry.ts +3 -2
  33. package/src/server/vite-config.ts +22 -9
  34. package/src/themes/default/Page.module.css +2 -0
  35. package/src/themes/paper/Layout.module.css +0 -4
  36. package/src/themes/paper/Layout.tsx +0 -2
  37. 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
- ...preset && { preset },
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
- experimental: {
535
- database: true
536
- },
537
- database: {
538
- default: getDatabaseConnector(preset)
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/cli/index.ts
553
- import { Command as Command6 } from "commander";
554
-
555
- // src/cli/commands/build.ts
556
- import chalk2 from "chalk";
557
- import { Command } from "commander";
558
-
559
- // src/cli/utils/config.ts
560
- import fs from "node:fs/promises";
561
- import path from "node:path";
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/types/config.ts
566
- import { uniqBy } from "lodash-es";
567
- import { z } from "zod";
568
- var logoSchema = z.object({
569
- light: z.string().optional(),
570
- dark: z.string().optional()
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
- var apiSchema = z.object({
602
- name: z.string(),
603
- spec: z.string(),
604
- basePath: z.string(),
605
- icon: z.string().optional(),
606
- server: apiServerSchema,
607
- auth: apiAuthSchema.optional()
608
- });
609
- var googleAnalyticsSchema = z.object({
610
- measurementId: z.string()
611
- });
612
- var analyticsSchema = z.object({
613
- enabled: z.boolean().optional(),
614
- googleAnalytics: googleAnalyticsSchema.optional()
615
- });
616
- var telemetrySchema = z.object({
617
- enabled: z.boolean().optional(),
618
- serviceName: z.string().optional(),
619
- port: z.number().int().min(1).max(65535).default(9090)
620
- });
621
- var siteSchema = z.object({
622
- title: z.string(),
623
- description: z.string().optional()
624
- });
625
- var DIR_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
626
- var dirNameSchema = z.string().min(1).refine((s) => DIR_NAME_PATTERN.test(s) && s !== "." && s !== "..", {
627
- message: 'dir must start with a letter or digit and contain only letters, digits, ".", "_", or "-"'
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
- var contentEntrySchema = z.object({
630
- dir: dirNameSchema,
631
- label: z.string().min(1),
632
- description: z.string().optional(),
633
- icon: z.string().optional(),
634
- index_page: z.string().optional()
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
- var badgeVariantSchema = z.enum([
637
- "accent",
638
- "warning",
639
- "danger",
640
- "success",
641
- "neutral",
642
- "gradient"
643
- ]);
644
- var badgeSchema = z.object({
645
- label: z.string().min(1),
646
- variant: badgeVariantSchema.default("accent")
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
- var latestSchema = z.object({
649
- label: z.string().min(1),
650
- landing: z.boolean().optional()
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
- var versionSchema = z.object({
653
- dir: dirNameSchema,
654
- label: z.string().min(1),
655
- badge: badgeSchema.optional(),
656
- landing: z.boolean().optional(),
657
- content: z.array(contentEntrySchema).min(1),
658
- api: z.array(apiSchema).optional()
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
- var allUnique = (items, key) => uniqBy(items, key).length === items.length;
661
- var RESERVED_ROUTE_SEGMENTS = [
662
- "api",
663
- "apis",
664
- "og",
665
- "llms.txt",
666
- "robots.txt",
667
- "sitemap.xml"
668
- ];
669
- var redirectSchema = z.object({
670
- from: z.string(),
671
- to: z.string(),
672
- permanent: z.boolean().optional()
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
- var chronicleConfigSchema = z.object({
675
- site: siteSchema,
676
- url: z.string().optional(),
677
- content: z.array(contentEntrySchema).min(1),
678
- latest: latestSchema.optional(),
679
- versions: z.array(versionSchema).optional(),
680
- preset: z.string().optional(),
681
- logo: logoSchema.optional(),
682
- theme: themeSchema.optional(),
683
- navigation: navigationSchema.optional(),
684
- search: searchSchema.optional(),
685
- api: z.array(apiSchema).optional(),
686
- redirects: z.array(redirectSchema).optional(),
687
- analytics: analyticsSchema.optional(),
688
- telemetry: telemetrySchema.optional()
689
- }).strict().refine((cfg) => allUnique(cfg.content, (c) => c.dir), {
690
- message: "content[].dir must be unique",
691
- path: ["content"]
692
- }).refine((cfg) => !cfg.versions || allUnique(cfg.versions, (v) => v.dir), {
693
- message: "versions[].dir must be unique",
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
- cfg.versions?.forEach((v, vi) => {
718
- if (reserved.has(v.dir)) {
719
- ctx.addIssue({
720
- code: "custom",
721
- message,
722
- path: ["versions", vi, "dir"]
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
- v.content.forEach((c, ci) => {
726
- if (reserved.has(c.dir)) {
727
- ctx.addIssue({
728
- code: "custom",
729
- message,
730
- path: ["versions", vi, "content", ci, "dir"]
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(chalk2.cyan("Building for production..."));
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
- const builder = await createBuilder({ ...viteConfig, builder: {} });
899
- await builder.buildApp();
900
- console.log(chalk2.green("Build complete"));
901
- console.log(chalk2.cyan("Run `chronicle start` to start the server"));
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 chalk3 from "chalk";
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(chalk3.cyan("Starting dev server..."));
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 fs4 from "node:fs";
940
- import path7 from "node:path";
941
- import chalk4 from "chalk";
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 = path7.join(projectDir, "content", defaultDir);
968
- if (!fs4.existsSync(contentDir)) {
969
- fs4.mkdirSync(contentDir, { recursive: true });
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 = path7.join(projectDir, "chronicle.yaml");
973
- if (!fs4.existsSync(configPath)) {
974
- fs4.writeFileSync(configPath, stringify(defaultInitConfig));
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 = fs4.readdirSync(contentDir);
2490
+ const contentFiles = fs8.readdirSync(contentDir);
980
2491
  if (contentFiles.length === 0) {
981
- const indexPath = path7.join(contentDir, "index.mdx");
982
- fs4.writeFileSync(indexPath, sampleMdx);
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 = path7.join(projectDir, ".gitignore");
986
- if (fs4.existsSync(gitignorePath)) {
987
- const existing = fs4.readFileSync(gitignorePath, "utf-8");
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
- fs4.appendFileSync(gitignorePath, `
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
- fs4.writeFileSync(gitignorePath, `${GITIGNORE_ENTRIES.join(`
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 `${chalk4.yellow("⚠")} ${e.path}${e.detail ? ` ${e.detail}` : ""}`;
2518
+ return `${chalk6.yellow("⚠")} ${e.path}${e.detail ? ` ${e.detail}` : ""}`;
1008
2519
  }
1009
2520
  if (e.type === "updated") {
1010
- return `${chalk4.green("✓")} Updated ${e.path}${e.detail ? ` (+${e.detail})` : ""}`;
2521
+ return `${chalk6.green("✓")} Updated ${e.path}${e.detail ? ` (+${e.detail})` : ""}`;
1011
2522
  }
1012
- return `${chalk4.green("✓")} Created ${e.path}`;
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(chalk4.green(`
2529
+ console.log(chalk6.green(`
1019
2530
  ✓ Chronicle initialized!`));
1020
2531
  console.log(`
1021
- Run`, chalk4.cyan("chronicle dev"), "to start development server");
2532
+ Run`, chalk6.cyan("chronicle dev"), "to start development server");
1022
2533
  });
1023
2534
 
1024
2535
  // src/cli/commands/serve.ts
1025
- import chalk5 from "chalk";
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(chalk5.cyan("Building for production..."));
2552
+ console.log(chalk7.cyan("Building for production..."));
1042
2553
  await build(viteConfig);
1043
- console.log(chalk5.cyan("Starting production server..."));
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 chalk6 from "chalk";
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(chalk6.cyan("Starting production server..."));
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 });