blume 0.1.0 → 0.1.1

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 (104) hide show
  1. package/dist/cli/index.js +1162 -455
  2. package/dist/cli/index.js.map +32 -28
  3. package/dist/types/core/bridge.d.ts +24 -0
  4. package/dist/types/core/config.d.ts +35 -0
  5. package/dist/types/core/data.d.ts +129 -0
  6. package/dist/types/core/define-components.d.ts +27 -0
  7. package/dist/types/core/define-meta.d.ts +16 -0
  8. package/dist/types/core/deployment-env.d.ts +9 -0
  9. package/dist/types/core/diagnostics.d.ts +17 -0
  10. package/dist/types/core/i18n-ui.d.ts +500 -0
  11. package/dist/types/core/load-module.d.ts +7 -0
  12. package/dist/types/core/package-root.d.ts +17 -0
  13. package/dist/types/core/project.d.ts +9 -0
  14. package/dist/types/core/schema.d.ts +3451 -0
  15. package/dist/types/core/sources/types.d.ts +107 -0
  16. package/dist/types/core/types.d.ts +245 -0
  17. package/dist/types/core/ui-packs/ar.d.ts +3 -0
  18. package/dist/types/core/ui-packs/bg.d.ts +3 -0
  19. package/dist/types/core/ui-packs/bn.d.ts +3 -0
  20. package/dist/types/core/ui-packs/ca.d.ts +3 -0
  21. package/dist/types/core/ui-packs/cs.d.ts +3 -0
  22. package/dist/types/core/ui-packs/da.d.ts +3 -0
  23. package/dist/types/core/ui-packs/de.d.ts +3 -0
  24. package/dist/types/core/ui-packs/el.d.ts +3 -0
  25. package/dist/types/core/ui-packs/es.d.ts +3 -0
  26. package/dist/types/core/ui-packs/fa.d.ts +3 -0
  27. package/dist/types/core/ui-packs/fi.d.ts +3 -0
  28. package/dist/types/core/ui-packs/fr.d.ts +3 -0
  29. package/dist/types/core/ui-packs/he.d.ts +3 -0
  30. package/dist/types/core/ui-packs/hi.d.ts +3 -0
  31. package/dist/types/core/ui-packs/hr.d.ts +3 -0
  32. package/dist/types/core/ui-packs/hu.d.ts +3 -0
  33. package/dist/types/core/ui-packs/id.d.ts +3 -0
  34. package/dist/types/core/ui-packs/index.d.ts +13 -0
  35. package/dist/types/core/ui-packs/it.d.ts +3 -0
  36. package/dist/types/core/ui-packs/ja.d.ts +3 -0
  37. package/dist/types/core/ui-packs/ko.d.ts +3 -0
  38. package/dist/types/core/ui-packs/nl.d.ts +3 -0
  39. package/dist/types/core/ui-packs/no.d.ts +3 -0
  40. package/dist/types/core/ui-packs/pl.d.ts +3 -0
  41. package/dist/types/core/ui-packs/pt-br.d.ts +3 -0
  42. package/dist/types/core/ui-packs/pt.d.ts +3 -0
  43. package/dist/types/core/ui-packs/ro.d.ts +3 -0
  44. package/dist/types/core/ui-packs/ru.d.ts +3 -0
  45. package/dist/types/core/ui-packs/sk.d.ts +3 -0
  46. package/dist/types/core/ui-packs/sr.d.ts +3 -0
  47. package/dist/types/core/ui-packs/sv.d.ts +3 -0
  48. package/dist/types/core/ui-packs/th.d.ts +3 -0
  49. package/dist/types/core/ui-packs/tr.d.ts +3 -0
  50. package/dist/types/core/ui-packs/uk.d.ts +3 -0
  51. package/dist/types/core/ui-packs/vi.d.ts +3 -0
  52. package/dist/types/core/ui-packs/zh-tw.d.ts +3 -0
  53. package/dist/types/core/ui-packs/zh.d.ts +3 -0
  54. package/dist/types/core/version.d.ts +8 -0
  55. package/dist/types/index.d.ts +10 -0
  56. package/dist/types/migrate/mintlify/config.d.ts +2 -0
  57. package/dist/types/migrate/mintlify/i18n.d.ts +7 -0
  58. package/dist/types/theme/fonts.d.ts +163 -0
  59. package/docs/advanced/custom-pages.mdx +91 -10
  60. package/docs/configuration/index.mdx +19 -0
  61. package/docs/content/components.mdx +87 -1
  62. package/package.json +13 -3
  63. package/src/ai/llms.ts +1 -2
  64. package/src/astro/examples.ts +104 -0
  65. package/src/astro/generate.ts +197 -69
  66. package/src/astro/islands.ts +1 -1
  67. package/src/astro/pages.ts +59 -0
  68. package/src/astro/templates.ts +136 -9
  69. package/src/cli/commands/validate.ts +1 -0
  70. package/src/components/content/CodeBlock.astro +28 -0
  71. package/src/components/content/Component.astro +65 -0
  72. package/src/components/content/Diff.astro +44 -0
  73. package/src/components/content/Tab.astro +8 -2
  74. package/src/components/content/diff.ts +95 -0
  75. package/src/components/layout/Favicon.astro +11 -4
  76. package/src/components/layout/PageLayout.astro +173 -0
  77. package/src/components/layout/ReferenceLayout.astro +6 -1
  78. package/src/components/layout/RootLayout.astro +9 -5
  79. package/src/components/layout/head-scripts.ts +19 -0
  80. package/src/core/data.ts +128 -0
  81. package/src/core/frontmatter.ts +43 -0
  82. package/src/core/links.ts +26 -10
  83. package/src/core/sources/filesystem.ts +1 -1
  84. package/src/core/sources/mdx-remote.ts +1 -2
  85. package/src/core/sources/mintlify.ts +1 -1
  86. package/src/core/sources/normalize.ts +19 -3
  87. package/src/core/sources/notion.ts +1 -1
  88. package/src/core/sources/sanity.ts +1 -1
  89. package/src/core/tsconfig-aliases.ts +201 -0
  90. package/src/index.ts +12 -0
  91. package/src/markdown/index.ts +69 -0
  92. package/src/migrate/fumadocs/config.ts +53 -4
  93. package/src/migrate/fumadocs/content.ts +1 -1
  94. package/src/migrate/fumadocs/groups.ts +230 -0
  95. package/src/migrate/fumadocs/index.ts +104 -19
  96. package/src/migrate/fumadocs/meta.ts +140 -10
  97. package/src/migrate/mintlify/snippets.ts +2 -1
  98. package/src/migrate/mintlify/transform.ts +1 -2
  99. package/src/migrate/nextra/index.ts +1 -1
  100. package/src/migrate/shared.ts +98 -1
  101. package/src/migrate/starlight/index.ts +1 -1
  102. package/src/registry/eject.ts +57 -10
  103. package/src/search/documents.ts +1 -2
  104. package/src/theme/entry.ts +29 -0
package/dist/cli/index.js CHANGED
@@ -279,14 +279,30 @@ Next steps:
279
279
  });
280
280
 
281
281
  // src/cli/commands/build.ts
282
- import { existsSync as existsSync9 } from "node:fs";
282
+ import { existsSync as existsSync11 } from "node:fs";
283
283
  import { writeFile as writeFile6 } from "node:fs/promises";
284
284
  import { build } from "astro";
285
285
  import { defineCommand as defineCommand2 } from "citty";
286
- import { join as join21 } from "pathe";
286
+ import { join as join23 } from "pathe";
287
287
 
288
- // src/ai/llms.ts
289
- import matter from "gray-matter";
288
+ // src/core/frontmatter.ts
289
+ import baseMatter from "gray-matter";
290
+ import { dump, load } from "js-yaml";
291
+ var yamlEngine = {
292
+ parse: (input) => load(input) ?? {},
293
+ stringify: (data) => dump(data)
294
+ };
295
+ var withYamlEngine = (options) => ({
296
+ ...options,
297
+ engines: {
298
+ yaml: yamlEngine,
299
+ ...options?.engines
300
+ }
301
+ });
302
+ var matter = Object.assign((input, options) => baseMatter(input, withYamlEngine(options)), baseMatter, {
303
+ stringify: (file, data, options) => baseMatter.stringify(file, data, withYamlEngine(options))
304
+ });
305
+ var frontmatter_default = matter;
290
306
 
291
307
  // src/core/sources/read.ts
292
308
  import { readFile as readFile2 } from "node:fs/promises";
@@ -335,7 +351,7 @@ var buildFull = async (project) => {
335
351
  const pages = orderedPages(project);
336
352
  const sections = await Promise.all(pages.map(async (page) => {
337
353
  const raw = await readEntryText(project, page);
338
- const body = matter(raw).content.trim();
354
+ const body = frontmatter_default(raw).content.trim();
339
355
  const url = pageUrl(page.route, config.deployment.site);
340
356
  return [`# ${page.title}`, `Source: ${url}`, "", body].join(`
341
357
  `);
@@ -473,9 +489,6 @@ var buildSearchIndex = async (outDir) => {
473
489
  return result.page_count;
474
490
  };
475
491
 
476
- // src/search/documents.ts
477
- import matter2 from "gray-matter";
478
-
479
492
  // src/core/ui-packs/ar.ts
480
493
  var ar = {
481
494
  actions: {
@@ -2365,7 +2378,7 @@ var buildSearchDocuments = async (project, options) => {
2365
2378
  return await Promise.all(indexable.map(async (route) => {
2366
2379
  const page = pageById.get(route.id);
2367
2380
  const raw = page ? await readEntryText(project, page) : "";
2368
- const body = raw ? toPlainText(matter2(raw).content) : "";
2381
+ const body = raw ? toPlainText(frontmatter_default(raw).content) : "";
2369
2382
  const tags = page?.meta?.search?.tags;
2370
2383
  const crumb = crumbs.get(route.path);
2371
2384
  return {
@@ -2501,19 +2514,20 @@ var syncSearchProvider = async (project, reporter) => {
2501
2514
  };
2502
2515
 
2503
2516
  // src/astro/generate.ts
2504
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
2517
+ import { existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "node:fs";
2505
2518
  import {
2519
+ lstat,
2506
2520
  mkdir as mkdir2,
2507
- readFile as readFile6,
2521
+ readFile as readFile7,
2508
2522
  rename,
2509
2523
  rm,
2510
2524
  symlink,
2511
2525
  writeFile as writeFile2
2512
2526
  } from "node:fs/promises";
2513
- import { createRequire as createRequire3 } from "node:module";
2514
- import { pathToFileURL } from "node:url";
2515
- import { dirname as dirname5, join as join9, normalize, relative as relative4 } from "pathe";
2516
- import { glob as glob3 } from "tinyglobby";
2527
+ import { createRequire as createRequire4 } from "node:module";
2528
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
2529
+ import { dirname as dirname6, join as join11, normalize, relative as relative5 } from "pathe";
2530
+ import { glob as glob4 } from "tinyglobby";
2517
2531
 
2518
2532
  // src/ai/ask.ts
2519
2533
  var ASK_PRESETS = {
@@ -2754,6 +2768,143 @@ var resolveUIStrings = (locale, options) => {
2754
2768
  return dict;
2755
2769
  };
2756
2770
 
2771
+ // src/core/tsconfig-aliases.ts
2772
+ import { existsSync as existsSync3, readFileSync as readFileSync2, statSync } from "node:fs";
2773
+ import { createRequire as createRequire2 } from "node:module";
2774
+ import { pathToFileURL } from "node:url";
2775
+ import { dirname as dirname4, isAbsolute, join as join6, resolve as resolve2 } from "pathe";
2776
+ var stripJsonComments = (text) => {
2777
+ let out = "";
2778
+ let inString = false;
2779
+ for (let index = 0;index < text.length; index += 1) {
2780
+ const char = text[index];
2781
+ if (inString) {
2782
+ out += char;
2783
+ if (char === "\\") {
2784
+ out += text[index + 1] ?? "";
2785
+ index += 1;
2786
+ } else if (char === '"') {
2787
+ inString = false;
2788
+ }
2789
+ continue;
2790
+ }
2791
+ if (char === '"') {
2792
+ inString = true;
2793
+ out += char;
2794
+ continue;
2795
+ }
2796
+ if (char === "/" && text[index + 1] === "/") {
2797
+ const newline = text.indexOf(`
2798
+ `, index + 2);
2799
+ index = newline === -1 ? text.length : newline - 1;
2800
+ continue;
2801
+ }
2802
+ if (char === "/" && text[index + 1] === "*") {
2803
+ const end = text.indexOf("*/", index + 2);
2804
+ index = end === -1 ? text.length : end + 1;
2805
+ continue;
2806
+ }
2807
+ out += char;
2808
+ }
2809
+ return out;
2810
+ };
2811
+ var TRAILING_COMMA = /,(?<rest>\s*[}\]])/gu;
2812
+ var parseJsonc = (text) => {
2813
+ try {
2814
+ const cleaned = stripJsonComments(text).replaceAll(TRAILING_COMMA, "$<rest>");
2815
+ const value = JSON.parse(cleaned);
2816
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2817
+ } catch {
2818
+ return null;
2819
+ }
2820
+ };
2821
+ var isFile = (path) => {
2822
+ try {
2823
+ return statSync(path).isFile();
2824
+ } catch {
2825
+ return false;
2826
+ }
2827
+ };
2828
+ var resolveExtends = (spec, fromDir) => {
2829
+ if (spec.startsWith(".") || isAbsolute(spec)) {
2830
+ const candidates = spec.endsWith(".json") ? [resolve2(fromDir, spec)] : [
2831
+ resolve2(fromDir, `${spec}.json`),
2832
+ resolve2(fromDir, spec, "tsconfig.json"),
2833
+ resolve2(fromDir, spec)
2834
+ ];
2835
+ return candidates.find(isFile) ?? null;
2836
+ }
2837
+ try {
2838
+ const require_ = createRequire2(pathToFileURL(join6(fromDir, "_.js")).href);
2839
+ for (const sub of [`${spec}/tsconfig.json`, spec]) {
2840
+ try {
2841
+ return require_.resolve(sub);
2842
+ } catch {}
2843
+ }
2844
+ } catch {}
2845
+ return null;
2846
+ };
2847
+ var loadPaths = (file, seen) => {
2848
+ if (seen.has(file) || !existsSync3(file)) {
2849
+ return null;
2850
+ }
2851
+ seen.add(file);
2852
+ const json = parseJsonc(readFileSync2(file, "utf-8"));
2853
+ if (!json) {
2854
+ return null;
2855
+ }
2856
+ const options = json.compilerOptions ?? {};
2857
+ if (options.paths && typeof options.paths === "object") {
2858
+ const baseUrl = typeof options.baseUrl === "string" ? options.baseUrl : ".";
2859
+ return {
2860
+ baseDir: resolve2(dirname4(file), baseUrl),
2861
+ paths: options.paths
2862
+ };
2863
+ }
2864
+ const bases = Array.isArray(json.extends) ? json.extends : [json.extends].filter(Boolean);
2865
+ for (const base of bases) {
2866
+ if (typeof base !== "string") {
2867
+ continue;
2868
+ }
2869
+ const resolved = resolveExtends(base, dirname4(file));
2870
+ const found = resolved ? loadPaths(resolved, seen) : null;
2871
+ if (found) {
2872
+ return found;
2873
+ }
2874
+ }
2875
+ return null;
2876
+ };
2877
+ var toAlias = (key, value, baseDir) => {
2878
+ const first = Array.isArray(value) ? value[0] : value;
2879
+ if (typeof first !== "string") {
2880
+ return null;
2881
+ }
2882
+ const find = key.endsWith("/*") ? key.slice(0, -2) : key;
2883
+ const target = first.endsWith("/*") ? first.slice(0, -2) : first;
2884
+ if (find === "" || find === "*") {
2885
+ return null;
2886
+ }
2887
+ return { find, replacement: resolve2(baseDir, target) };
2888
+ };
2889
+ var resolveTsconfigAliases = (root) => {
2890
+ const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join6(root, name)).find((file) => existsSync3(file));
2891
+ if (!entry) {
2892
+ return {};
2893
+ }
2894
+ const loaded = loadPaths(entry, new Set);
2895
+ if (!loaded) {
2896
+ return {};
2897
+ }
2898
+ const aliases = {};
2899
+ for (const [key, value] of Object.entries(loaded.paths)) {
2900
+ const alias = toAlias(key, value, loaded.baseDir);
2901
+ if (alias) {
2902
+ aliases[alias.find] = alias.replacement;
2903
+ }
2904
+ }
2905
+ return aliases;
2906
+ };
2907
+
2757
2908
  // src/deploy/rss.ts
2758
2909
  var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
2759
2910
  var pageDate = (page) => {
@@ -2835,11 +2986,11 @@ ${items}
2835
2986
 
2836
2987
  // src/openapi/scalar.ts
2837
2988
  import { readFile as readFile4 } from "node:fs/promises";
2838
- import { isAbsolute, join as join7 } from "pathe";
2989
+ import { isAbsolute as isAbsolute2, join as join8 } from "pathe";
2839
2990
 
2840
2991
  // src/astro/templates.ts
2841
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
2842
- import { dirname as dirname4, join as join6 } from "pathe";
2992
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
2993
+ import { dirname as dirname5, join as join7 } from "pathe";
2843
2994
 
2844
2995
  // src/theme/fonts.ts
2845
2996
  var FALLBACKS = {
@@ -2997,24 +3148,24 @@ var WORKSPACE_MARKERS = [
2997
3148
  "yarn.lock"
2998
3149
  ];
2999
3150
  var hasWorkspacesField = (pkgPath) => {
3000
- if (!existsSync3(pkgPath)) {
3151
+ if (!existsSync4(pkgPath)) {
3001
3152
  return false;
3002
3153
  }
3003
3154
  try {
3004
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
3155
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
3005
3156
  return pkg.workspaces !== undefined;
3006
3157
  } catch {
3007
3158
  return false;
3008
3159
  }
3009
3160
  };
3010
- var hasWorkspaceMarker = (dir) => hasWorkspacesField(join6(dir, "package.json")) || WORKSPACE_MARKERS.some((marker) => existsSync3(join6(dir, marker)));
3161
+ var hasWorkspaceMarker = (dir) => hasWorkspacesField(join7(dir, "package.json")) || WORKSPACE_MARKERS.some((marker) => existsSync4(join7(dir, marker)));
3011
3162
  var findWorkspaceRoot = (start) => {
3012
3163
  let dir = start;
3013
3164
  for (;; ) {
3014
3165
  if (hasWorkspaceMarker(dir)) {
3015
3166
  return dir;
3016
3167
  }
3017
- const parent = dirname4(dir);
3168
+ const parent = dirname5(dir);
3018
3169
  if (parent === dir) {
3019
3170
  return start;
3020
3171
  }
@@ -3061,10 +3212,19 @@ var runtimeDependencies = (options) => {
3061
3212
  }
3062
3213
  return deps;
3063
3214
  };
3215
+ var renderUserAliases = (aliases) => Object.entries(aliases ?? {}).toSorted(([a], [b]) => b.length - a.length).map(([find, replacement]) => `
3216
+ ${JSON.stringify(find)}: ${JSON.stringify(replacement)},`).join("");
3064
3217
  var astroConfigTemplate = (options) => {
3065
3218
  const { context, config, needsReact, pages, dataPath, themePath } = options;
3066
- const { contentRoutes, needsSvelte, needsVue, searchClientPath } = options;
3219
+ const {
3220
+ contentRoutes,
3221
+ examplesPath,
3222
+ needsSvelte,
3223
+ needsVue,
3224
+ searchClientPath
3225
+ } = options;
3067
3226
  const { deployment } = config;
3227
+ const userAliasLines = renderUserAliases(options.aliases);
3068
3228
  const server = deployment.output === "server";
3069
3229
  const fsAllow = [...new Set([findWorkspaceRoot(context.root), context.root])];
3070
3230
  const adapterImport = server && deployment.adapter ? `import adapter from "${ADAPTER_IMPORTS[deployment.adapter]}";
@@ -3167,8 +3327,9 @@ export default defineConfig({
3167
3327
  resolve: {
3168
3328
  alias: {
3169
3329
  "blume:data": ${JSON.stringify(dataPath)},
3330
+ "blume:examples": ${JSON.stringify(examplesPath)},
3170
3331
  "blume:search-client": ${JSON.stringify(searchClientPath)},
3171
- "blume:theme": ${JSON.stringify(themePath)},
3332
+ "blume:theme": ${JSON.stringify(themePath)},${userAliasLines}
3172
3333
  },
3173
3334
  },
3174
3335
  server: {
@@ -3180,7 +3341,7 @@ export default defineConfig({
3180
3341
  });
3181
3342
  `;
3182
3343
  };
3183
- var stagedContentDir = (outDir) => join6(outDir, "content");
3344
+ var stagedContentDir = (outDir) => join7(outDir, "content");
3184
3345
  var contentConfigTemplate = (options) => {
3185
3346
  const { context, config } = options;
3186
3347
  const stagedBase = options.stagedBase ?? stagedContentDir(context.outDir);
@@ -3426,24 +3587,44 @@ export function GET({ props }) {
3426
3587
  });
3427
3588
  }
3428
3589
  `;
3429
- var ogEndpointTemplate = () => `// Generated by Blume. Do not edit.
3590
+ var ogEndpointTemplate = (customRoutes = []) => `// Generated by Blume. Do not edit.
3430
3591
  import { renderOgImage } from "blume/og";
3431
3592
  import data from "../../generated/data.json";
3432
3593
 
3433
3594
  export const prerender = true;
3434
3595
 
3596
+ // Custom (non-content) pages opted into a generated card, baked in at build.
3597
+ const customRoutes = ${JSON.stringify(customRoutes)};
3598
+
3435
3599
  export function getStaticPaths() {
3436
- return data.routes.map((route) => ({
3437
- params: { slug: route.path === "/" ? "index" : route.path.slice(1) },
3438
- props: { title: route.title },
3439
- }));
3600
+ const seen = new Set();
3601
+ const paths = [];
3602
+ const add = (slug, title, eyebrow) => {
3603
+ if (seen.has(slug)) {
3604
+ return;
3605
+ }
3606
+ seen.add(slug);
3607
+ paths.push({ params: { slug }, props: { eyebrow, title } });
3608
+ };
3609
+ // A custom page wins over a content route sharing its path, so add it first.
3610
+ for (const route of customRoutes) {
3611
+ add(route.slug, route.title, route.eyebrow);
3612
+ }
3613
+ for (const route of data.routes) {
3614
+ add(
3615
+ route.path === "/" ? "index" : route.path.slice(1),
3616
+ route.title,
3617
+ data.config.title
3618
+ );
3619
+ }
3620
+ return paths;
3440
3621
  }
3441
3622
 
3442
3623
  export async function GET({ props }) {
3443
3624
  const png = await renderOgImage({
3444
- title: props.title,
3445
- eyebrow: data.config.title,
3446
3625
  accent: data.config.theme.accent,
3626
+ eyebrow: props.eyebrow,
3627
+ title: props.title,
3447
3628
  });
3448
3629
  return new Response(png, {
3449
3630
  headers: {
@@ -3470,6 +3651,7 @@ const configuration = ${JSON.stringify(options.configuration, null, 2)};
3470
3651
  fontCssVars={data.fontCssVars}
3471
3652
  logo={data.config.logo}
3472
3653
  favicon={data.config.favicon}
3654
+ appleIcon={data.config.appleIcon}
3473
3655
  navigation={data.navigation}
3474
3656
  pageTitle={${JSON.stringify(options.title)}}
3475
3657
  route={${JSON.stringify(options.route)}}
@@ -3501,12 +3683,15 @@ import Badge from "blume/components/content/Badge.astro";
3501
3683
  import Callout from "blume/components/content/Callout.astro";
3502
3684
  import Card from "blume/components/content/Card.astro";
3503
3685
  import CardGroup from "blume/components/content/CardGroup.astro";
3686
+ import CodeBlock from "blume/components/content/CodeBlock.astro";
3504
3687
  import CodeGroup from "blume/components/content/CodeGroup.astro";
3505
3688
  import ColorRoot from "blume/components/content/Color.astro";
3506
3689
  import ColorItem from "blume/components/content/ColorItem.astro";
3507
3690
  import ColorRow from "blume/components/content/ColorRow.astro";
3508
3691
  import Column from "blume/components/content/Column.astro";
3509
3692
  import Columns from "blume/components/content/Columns.astro";
3693
+ import Component from "blume/components/content/Component.astro";
3694
+ import Diff from "blume/components/content/Diff.astro";
3510
3695
  import Expandable from "blume/components/content/Expandable.astro";
3511
3696
  import FileTree from "blume/components/content/FileTree.astro";
3512
3697
  import Frame from "blume/components/content/Frame.astro";
@@ -3546,10 +3731,13 @@ const components = {
3546
3731
  Callout,
3547
3732
  Card,
3548
3733
  CardGroup,
3734
+ CodeBlock,
3549
3735
  CodeGroup,
3550
3736
  Color,
3551
3737
  Column,
3552
3738
  Columns,
3739
+ Component,
3740
+ Diff,
3553
3741
  Expandable,
3554
3742
  FileTree,
3555
3743
  Frame,
@@ -3670,6 +3858,7 @@ const localeSwitch = i18n
3670
3858
  logo={data.config.logo}
3671
3859
  mcp={data.config.mcp}
3672
3860
  favicon={data.config.favicon}
3861
+ appleIcon={data.config.appleIcon}
3673
3862
  banner={data.config.banner}
3674
3863
  analytics={data.config.analytics}
3675
3864
  imageZoom={data.config.imageZoom}
@@ -3796,6 +3985,7 @@ const canonical = base ? base + "/changelog" : null;
3796
3985
  logo={data.config.logo}
3797
3986
  mcp={data.config.mcp}
3798
3987
  favicon={data.config.favicon}
3988
+ appleIcon={data.config.appleIcon}
3799
3989
  banner={data.config.banner}
3800
3990
  analytics={data.config.analytics}
3801
3991
  imageZoom={data.config.imageZoom}
@@ -3875,8 +4065,43 @@ ${entries}
3875
4065
  };
3876
4066
  `;
3877
4067
  };
4068
+ var exampleDirective = (spec) => {
4069
+ if (spec.framework === "astro" || !spec.client) {
4070
+ return "";
4071
+ }
4072
+ return spec.client === "only" ? `client:only="${spec.framework}" ` : `client:${spec.client} `;
4073
+ };
4074
+ var exampleSlug = (path) => path.replaceAll("/", "__").replaceAll(/[^a-zA-Z0-9_]+/gu, "-");
4075
+ var exampleWrapperTemplate = (spec) => `---
4076
+ // Generated by Blume. Do not edit.
4077
+ import Example from ${JSON.stringify(spec.file)};
4078
+ ---
4079
+ <Example ${exampleDirective(spec)}{...Astro.props}><slot /></Example>
4080
+ `;
4081
+ var exampleMapTemplate = (specs) => {
4082
+ if (specs.length === 0) {
4083
+ return `// Generated by Blume. Do not edit.
4084
+ export const examples = {};
4085
+ `;
4086
+ }
4087
+ const imports = specs.map((spec, index) => `import E${index} from "./examples/${exampleSlug(spec.path)}.astro";`).join(`
4088
+ `);
4089
+ const entries = specs.map((spec, index) => ` ${JSON.stringify(spec.path)}: { Component: E${index}, code: ${JSON.stringify(spec.source)}, lang: ${JSON.stringify(spec.lang)} },`).join(`
4090
+ `);
4091
+ return `// Generated by Blume. Do not edit.
4092
+ ${imports}
4093
+ export const examples = {
4094
+ ${entries}
4095
+ };
4096
+ `;
4097
+ };
3878
4098
  var envTemplate = () => `/// <reference path="../.astro/types.d.ts" />
3879
4099
  /// <reference types="astro/client" />
4100
+
4101
+ declare module "blume:data" {
4102
+ const data: import("blume").BlumeData;
4103
+ export default data;
4104
+ }
3880
4105
  `;
3881
4106
  var runtimePackageTemplate = (dependencies = []) => `${JSON.stringify({
3882
4107
  dependencies: Object.fromEntries([...dependencies].toSorted().map((name) => [name, "*"])),
@@ -4057,7 +4282,7 @@ var specConfiguration = async (spec, root) => {
4057
4282
  if (URL_SPEC.test(spec)) {
4058
4283
  return { config: { url: spec } };
4059
4284
  }
4060
- const absolute = isAbsolute(spec) ? spec : join7(root, spec);
4285
+ const absolute = isAbsolute2(spec) ? spec : join8(root, spec);
4061
4286
  try {
4062
4287
  return { config: { content: await readFile4(absolute, "utf-8") } };
4063
4288
  } catch {
@@ -4419,6 +4644,19 @@ ${options.sources.map((source) => `@source "${source}";`).join(`
4419
4644
  text-decoration: var(--shiki-dark-text-decoration, inherit);
4420
4645
  }
4421
4646
 
4647
+ /* The Diff component (@pierre/diffs) renders into a declarative shadow root and
4648
+ themes itself with light-dark(). Drive that from Blume's data-theme instead
4649
+ of the OS, scoped to the diff host: the custom property pierces the shadow
4650
+ boundary, where the appended host color-scheme rule consumes it. */
4651
+ blume-diff {
4652
+ --blume-diff-color-scheme: light;
4653
+ display: block;
4654
+ }
4655
+
4656
+ :root[data-theme="dark"] blume-diff {
4657
+ --blume-diff-color-scheme: dark;
4658
+ }
4659
+
4422
4660
  .prose :where(pre[data-language]) {
4423
4661
  padding-top: 3.75rem;
4424
4662
  }
@@ -4492,6 +4730,22 @@ blume-tabs pre,
4492
4730
  background: var(--blume-code-background);
4493
4731
  }
4494
4732
 
4733
+ /* The Component source pane fills its tab's fixed height (set inline to match the
4734
+ preview tab) and scrolls the inner code element, leaving the pre static so the
4735
+ copy button — absolute, pinned to the pre — doesn't drift while it scrolls. */
4736
+ pre.blume-source {
4737
+ display: flex;
4738
+ flex-direction: column;
4739
+ height: 100%;
4740
+ overflow: hidden;
4741
+ }
4742
+
4743
+ pre.blume-source > code {
4744
+ flex: 1;
4745
+ min-height: 0;
4746
+ overflow: auto;
4747
+ }
4748
+
4495
4749
  /* Code blocks inside tabs sit flush; the tab container owns the frame. These
4496
4750
  rules are unlayered like the base \`pre\` styles, so they win over Tailwind's
4497
4751
  layered utilities (which a class on the panel cannot). The second selector
@@ -4701,9 +4955,9 @@ ${options.userTheme}
4701
4955
  `;
4702
4956
 
4703
4957
  // src/theme/twoslash.ts
4704
- import { readFileSync as readFileSync3 } from "node:fs";
4705
- import { createRequire as createRequire2 } from "node:module";
4706
- var require2 = createRequire2(import.meta.url);
4958
+ import { readFileSync as readFileSync4 } from "node:fs";
4959
+ import { createRequire as createRequire3 } from "node:module";
4960
+ var require2 = createRequire3(import.meta.url);
4707
4961
  var OVERRIDES = `
4708
4962
  /* Twoslash: theme the rich renderer with Blume tokens. */
4709
4963
  :root {
@@ -4763,13 +5017,18 @@ var OVERRIDES = `
4763
5017
  `;
4764
5018
  var twoslashCss = () => {
4765
5019
  const file = require2.resolve("@shikijs/twoslash/style-rich.css");
4766
- return `${readFileSync3(file, "utf-8")}
5020
+ return `${readFileSync4(file, "utf-8")}
4767
5021
  ${OVERRIDES}`;
4768
5022
  };
4769
5023
 
5024
+ // src/astro/examples.ts
5025
+ import { readFile as readFile6 } from "node:fs/promises";
5026
+ import { join as join10, relative as relative3 } from "pathe";
5027
+ import { glob as glob2 } from "tinyglobby";
5028
+
4770
5029
  // src/astro/islands.ts
4771
5030
  import { readFile as readFile5 } from "node:fs/promises";
4772
- import { basename, join as join8 } from "pathe";
5031
+ import { basename, join as join9 } from "pathe";
4773
5032
  import { glob } from "tinyglobby";
4774
5033
  var DEFAULT_CLIENT = "visible";
4775
5034
  var VALID_MODES = new Set([
@@ -4798,7 +5057,7 @@ var readClientMode = (source, file, warnings) => {
4798
5057
  return mode;
4799
5058
  };
4800
5059
  var discoverIslands = async (root) => {
4801
- const dir = join8(root, "islands");
5060
+ const dir = join9(root, "islands");
4802
5061
  const matches = await glob(["**/*.{jsx,svelte,tsx,vue}"], {
4803
5062
  absolute: true,
4804
5063
  cwd: dir,
@@ -4837,50 +5096,133 @@ var discoverIslands = async (root) => {
4837
5096
  return { islands, warnings };
4838
5097
  };
4839
5098
 
5099
+ // src/astro/examples.ts
5100
+ var FRAMEWORK_BY_EXT2 = {
5101
+ astro: "astro",
5102
+ jsx: "react",
5103
+ svelte: "svelte",
5104
+ tsx: "react",
5105
+ vue: "vue"
5106
+ };
5107
+ var EXAMPLE_FILE = /\.(?<ext>astro|jsx|svelte|tsx|vue)$/u;
5108
+ var discoverExamples = async (root) => {
5109
+ const dir = join10(root, "examples");
5110
+ const matches = await glob2(["**/*.{astro,jsx,svelte,tsx,vue}"], {
5111
+ absolute: true,
5112
+ cwd: dir,
5113
+ onlyFiles: true
5114
+ });
5115
+ const files = matches.toSorted();
5116
+ const sources = await Promise.all(files.map((file) => readFile6(file, "utf-8")));
5117
+ const examples = [];
5118
+ const warnings = [];
5119
+ const seen = new Map;
5120
+ for (const [index, file] of files.entries()) {
5121
+ const ext = file.match(EXAMPLE_FILE)?.groups?.ext;
5122
+ const framework = ext ? FRAMEWORK_BY_EXT2[ext] : undefined;
5123
+ if (!(ext && framework)) {
5124
+ continue;
5125
+ }
5126
+ const path = relative3(dir, file).slice(0, -(ext.length + 1));
5127
+ const existing = seen.get(path);
5128
+ if (existing) {
5129
+ warnings.push(`Two examples both resolve to "${path}" ("${existing}" and "${file}"); ignoring the second. Give them distinct paths.`);
5130
+ continue;
5131
+ }
5132
+ seen.set(path, file);
5133
+ const source = sources[index] ?? "";
5134
+ examples.push({
5135
+ client: framework === "astro" ? undefined : readClientMode(source, file, warnings),
5136
+ file,
5137
+ framework,
5138
+ lang: ext,
5139
+ path,
5140
+ source
5141
+ });
5142
+ }
5143
+ return { examples, warnings };
5144
+ };
5145
+
4840
5146
  // src/astro/pages.ts
4841
- import { extname, relative as relative3 } from "pathe";
4842
- import { glob as glob2 } from "tinyglobby";
5147
+ import { extname, relative as relative4 } from "pathe";
5148
+ import { glob as glob3 } from "tinyglobby";
4843
5149
  var discoverPages = async (pagesRoot) => {
4844
- const files = await glob2(["**/*.astro"], {
5150
+ const files = await glob3(["**/*.astro"], {
4845
5151
  absolute: true,
4846
5152
  cwd: pagesRoot,
4847
5153
  onlyFiles: true
4848
5154
  });
4849
5155
  files.sort();
4850
5156
  return files.map((file) => {
4851
- const rel = relative3(pagesRoot, file);
5157
+ const rel = relative4(pagesRoot, file);
4852
5158
  const withoutExt = rel.slice(0, rel.length - extname(rel).length);
4853
5159
  const parts = withoutExt.split("/").filter((part) => part !== "index");
4854
5160
  const pattern = parts.length === 0 ? "/" : `/${parts.join("/")}`;
4855
5161
  return { entrypoint: file, pattern };
4856
5162
  });
4857
5163
  };
5164
+ var PRIVATE_SEGMENT = /^[._]/u;
5165
+ var humanizeSegment = (segment) => segment.split(/[-_]/u).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
5166
+ var customOgRoutes = (pages, siteTitle, siteDescription) => {
5167
+ const seen = new Set;
5168
+ const routes = [];
5169
+ for (const { pattern } of pages) {
5170
+ const segments = pattern.split("/").filter(Boolean);
5171
+ if (segments.some((part) => PRIVATE_SEGMENT.test(part) || part.includes("["))) {
5172
+ continue;
5173
+ }
5174
+ const slug = segments.length === 0 ? "index" : segments.join("/");
5175
+ if (seen.has(slug)) {
5176
+ continue;
5177
+ }
5178
+ seen.add(slug);
5179
+ const last = segments.at(-1);
5180
+ routes.push(last ? { eyebrow: siteTitle, slug, title: humanizeSegment(last) } : { eyebrow: siteDescription, slug, title: siteTitle });
5181
+ }
5182
+ return routes;
5183
+ };
4858
5184
 
4859
5185
  // src/astro/generate.ts
4860
- var BLUME_SRC = join9(packageRoot(), "src");
4861
- var BLUME_NODE_MODULES = join9(packageRoot(), "node_modules");
5186
+ var BLUME_SRC = join11(packageRoot(), "src");
4862
5187
  var canResolveFrom = (fromDir, spec) => {
4863
5188
  try {
4864
- createRequire3(pathToFileURL(join9(fromDir, "_.js")).href).resolve(spec);
5189
+ createRequire4(pathToFileURL2(join11(fromDir, "_.js")).href).resolve(spec);
4865
5190
  return true;
4866
5191
  } catch {
4867
5192
  return false;
4868
5193
  }
4869
5194
  };
4870
- var canResolveAstro = (fromDir) => canResolveFrom(fromDir, "astro/package.json");
4871
- var ensureDepsLink = async (outDir) => {
4872
- if (canResolveAstro(outDir)) {
4873
- return;
5195
+ var resolvedAstroPath = (fromDir) => {
5196
+ try {
5197
+ const pkg = createRequire4(pathToFileURL2(join11(fromDir, "_.js")).href).resolve("astro/package.json");
5198
+ return realpathSync(pkg);
5199
+ } catch {
5200
+ return null;
4874
5201
  }
4875
- if (!existsSync4(join9(BLUME_NODE_MODULES, "astro"))) {
5202
+ };
5203
+ var blumeDepsDir = (pkgDir = packageRoot()) => {
5204
+ const candidates = [join11(pkgDir, "node_modules"), dirname6(pkgDir)];
5205
+ return candidates.find((dir) => existsSync5(join11(dir, "astro"))) ?? null;
5206
+ };
5207
+ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
5208
+ const depsDir = blumeDepsDir(pkgDir);
5209
+ if (!depsDir || !existsSync5(join11(depsDir, "@astrojs", "mdx"))) {
4876
5210
  return;
4877
5211
  }
4878
- const link = join9(outDir, "node_modules");
4879
- if (existsSync4(link)) {
5212
+ const blumeAstro = resolvedAstroPath(depsDir);
5213
+ if (blumeAstro && resolvedAstroPath(outDir) === blumeAstro) {
4880
5214
  return;
4881
5215
  }
5216
+ const link = join11(outDir, "node_modules");
5217
+ const existing = await lstat(link).catch(() => null);
5218
+ if (existing) {
5219
+ if (!existing.isSymbolicLink()) {
5220
+ return;
5221
+ }
5222
+ await rm(link, { force: true });
5223
+ }
4882
5224
  await mkdir2(outDir, { recursive: true });
4883
- await symlink(BLUME_NODE_MODULES, link, "junction");
5225
+ await symlink(depsDir, link, "junction");
4884
5226
  };
4885
5227
  var ISLAND_FRAMEWORK_DEPS = {
4886
5228
  svelte: "@astrojs/svelte",
@@ -4901,13 +5243,13 @@ var readOptional = async (path) => {
4901
5243
  return "";
4902
5244
  }
4903
5245
  try {
4904
- return await readFile6(path, "utf-8");
5246
+ return await readFile7(path, "utf-8");
4905
5247
  } catch {
4906
5248
  return "";
4907
5249
  }
4908
5250
  };
4909
5251
  var detectNeedsReact = async (root) => {
4910
- const matches = await glob3(["**/*.{tsx,jsx}"], {
5252
+ const matches = await glob4(["**/*.{tsx,jsx}"], {
4911
5253
  cwd: root,
4912
5254
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
4913
5255
  onlyFiles: true
@@ -4917,14 +5259,14 @@ var detectNeedsReact = async (root) => {
4917
5259
  var writeIfChanged = async (path, content) => {
4918
5260
  let existing = null;
4919
5261
  try {
4920
- existing = await readFile6(path, "utf-8");
5262
+ existing = await readFile7(path, "utf-8");
4921
5263
  } catch {
4922
5264
  existing = null;
4923
5265
  }
4924
5266
  if (existing === content) {
4925
5267
  return false;
4926
5268
  }
4927
- await mkdir2(dirname5(path), { recursive: true });
5269
+ await mkdir2(dirname6(path), { recursive: true });
4928
5270
  const tmp = `${path}.${process.pid}.tmp`;
4929
5271
  await writeFile2(tmp, content, "utf-8");
4930
5272
  try {
@@ -4936,7 +5278,7 @@ var writeIfChanged = async (path, content) => {
4936
5278
  return true;
4937
5279
  };
4938
5280
  var pruneOrphans = async (srcDir, written) => {
4939
- const existing = await glob3("**/*", {
5281
+ const existing = await glob4("**/*", {
4940
5282
  absolute: true,
4941
5283
  cwd: srcDir,
4942
5284
  onlyFiles: true
@@ -4956,11 +5298,11 @@ var writeStagedContent = async (out, staged) => {
4956
5298
  const contentDir = stagedContentDir(out);
4957
5299
  const written = new Set;
4958
5300
  await Promise.all([...staged].map(async ([entryId, text]) => {
4959
- const path = join9(contentDir, entryId);
5301
+ const path = join11(contentDir, entryId);
4960
5302
  written.add(normalize(path));
4961
5303
  await writeIfChanged(path, text);
4962
5304
  }));
4963
- if (existsSync4(contentDir)) {
5305
+ if (existsSync5(contentDir)) {
4964
5306
  await pruneOrphans(contentDir, written);
4965
5307
  }
4966
5308
  };
@@ -4977,11 +5319,11 @@ var resolveLogo = (project) => {
4977
5319
  if (light && light === dark && light.toLowerCase().endsWith(".svg")) {
4978
5320
  const rel = light.replace(/^\//u, "");
4979
5321
  const file = [
4980
- join9(project.context.root, "public", rel),
4981
- join9(project.context.root, rel)
4982
- ].find((path) => existsSync4(path));
5322
+ join11(project.context.root, "public", rel),
5323
+ join11(project.context.root, rel)
5324
+ ].find((path) => existsSync5(path));
4983
5325
  if (file) {
4984
- return { alt, href, svg: readFileSync4(file, "utf-8") };
5326
+ return { alt, href, svg: readFileSync5(file, "utf-8") };
4985
5327
  }
4986
5328
  }
4987
5329
  return { alt, dark, href, light };
@@ -4996,6 +5338,8 @@ var FAVICON_CANDIDATES = [
4996
5338
  ];
4997
5339
  var FAVICON_TYPES = {
4998
5340
  ico: "image/x-icon",
5341
+ jpeg: "image/jpeg",
5342
+ jpg: "image/jpeg",
4999
5343
  png: "image/png",
5000
5344
  svg: "image/svg+xml"
5001
5345
  };
@@ -5003,27 +5347,35 @@ var faviconType = (name) => {
5003
5347
  const ext = name.split(".").pop()?.toLowerCase();
5004
5348
  return ext ? FAVICON_TYPES[ext] : undefined;
5005
5349
  };
5006
- var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync4(file).toString("base64")}`;
5350
+ var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync5(file).toString("base64")}`;
5007
5351
  var defaultFavicon = () => ({
5008
- href: inlineDataUri(join9(BLUME_SRC, "assets", "icon.png"), "image/png"),
5352
+ href: inlineDataUri(join11(BLUME_SRC, "assets", "icon.png"), "image/png"),
5009
5353
  type: "image/png"
5010
5354
  });
5011
- var resolveFavicon = (project) => {
5355
+ var APPLE_ICON_CANDIDATES = [
5356
+ "apple-icon.png",
5357
+ "apple-icon.jpg",
5358
+ "apple-icon.jpeg",
5359
+ "apple-touch-icon.png"
5360
+ ];
5361
+ var resolveIconFile = (project, candidates) => {
5012
5362
  const { root } = project.context;
5013
- for (const name of FAVICON_CANDIDATES) {
5014
- if (existsSync4(join9(root, "public", name))) {
5363
+ for (const name of candidates) {
5364
+ if (existsSync5(join11(root, "public", name))) {
5015
5365
  return { href: `/${name}`, type: faviconType(name) };
5016
5366
  }
5017
5367
  }
5018
- for (const name of FAVICON_CANDIDATES) {
5019
- const file = join9(root, name);
5020
- if (existsSync4(file)) {
5368
+ for (const name of candidates) {
5369
+ const file = join11(root, name);
5370
+ if (existsSync5(file)) {
5021
5371
  const type = faviconType(name);
5022
5372
  return { href: inlineDataUri(file, type ?? "image/x-icon"), type };
5023
5373
  }
5024
5374
  }
5025
- return defaultFavicon();
5375
+ return null;
5026
5376
  };
5377
+ var resolveFavicon = (project) => resolveIconFile(project, FAVICON_CANDIDATES) ?? defaultFavicon();
5378
+ var resolveAppleIcon = (project) => resolveIconFile(project, APPLE_ICON_CANDIDATES);
5027
5379
  var resolveBanner = (config) => {
5028
5380
  const { banner } = config;
5029
5381
  if (!banner) {
@@ -5048,7 +5400,7 @@ var buildRuntimeData = (project) => {
5048
5400
  if (!(editBase && sourcePath)) {
5049
5401
  return null;
5050
5402
  }
5051
- const rel = relative4(context.root, sourcePath).split("\\").join("/");
5403
+ const rel = relative5(context.root, sourcePath).split("\\").join("/");
5052
5404
  return `${editBase}/${github?.dir ? `${github.dir}/${rel}` : rel}`;
5053
5405
  };
5054
5406
  const { i18n } = config;
@@ -5081,6 +5433,7 @@ var buildRuntimeData = (project) => {
5081
5433
  const data = {
5082
5434
  config: {
5083
5435
  analytics: config.analytics ?? null,
5436
+ appleIcon: resolveAppleIcon(project),
5084
5437
  banner: resolveBanner(config),
5085
5438
  codeWrap: config.markdown.code.wrap,
5086
5439
  description: config.description,
@@ -5099,7 +5452,7 @@ var buildRuntimeData = (project) => {
5099
5452
  imageZoom: config.markdown.imageZoom,
5100
5453
  logo: resolveLogo(project),
5101
5454
  mcp: config.mcp.enabled ? { name: config.mcp.name ?? config.title, route: config.mcp.route } : null,
5102
- og: { enabled: config.seo.og.enabled },
5455
+ og: { enabled: config.seo.og.enabled ?? false },
5103
5456
  repoUrl,
5104
5457
  search: {
5105
5458
  enabled: config.search.provider !== "none",
@@ -5141,7 +5494,7 @@ var buildRuntimeData = (project) => {
5141
5494
  var planMcp = (project, srcDir) => {
5142
5495
  const { config } = project;
5143
5496
  const { route } = config.mcp;
5144
- const dir = join9(srcDir, "blume-mcp");
5497
+ const dir = join11(srcDir, "blume-mcp");
5145
5498
  const base = {
5146
5499
  dir,
5147
5500
  discoveryPages: [],
@@ -5165,11 +5518,11 @@ var planMcp = (project, srcDir) => {
5165
5518
  ...base,
5166
5519
  discoveryPages: [
5167
5520
  {
5168
- entrypoint: join9(dir, "discovery.ts"),
5521
+ entrypoint: join11(dir, "discovery.ts"),
5169
5522
  pattern: "/.well-known/mcp.json"
5170
5523
  },
5171
5524
  {
5172
- entrypoint: join9(dir, "server-card.ts"),
5525
+ entrypoint: join11(dir, "server-card.ts"),
5173
5526
  pattern: "/.well-known/mcp/server-card.json"
5174
5527
  }
5175
5528
  ],
@@ -5188,20 +5541,21 @@ var writeMcpFiles = async (project, plan, write) => {
5188
5541
  version: data.version
5189
5542
  };
5190
5543
  await Promise.all([
5191
- write(join9(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
5544
+ write(join11(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
5192
5545
  `),
5193
- write(join9(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
5194
- write(join9(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
5195
- write(join9(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
5546
+ write(join11(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
5547
+ write(join11(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
5548
+ write(join11(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
5196
5549
  ]);
5197
5550
  };
5198
5551
  var generateRuntime = async (project) => {
5199
5552
  const { context, config } = project;
5200
5553
  const out = context.outDir;
5201
- const srcDir = join9(out, "src");
5202
- const dataPath = join9(srcDir, "generated", "data.json");
5203
- const themePath = join9(srcDir, "generated", "app.css");
5204
- const searchClientPath = join9(srcDir, "generated", "search-client.ts");
5554
+ const srcDir = join11(out, "src");
5555
+ const dataPath = join11(srcDir, "generated", "data.json");
5556
+ const themePath = join11(srcDir, "generated", "app.css");
5557
+ const searchClientPath = join11(srcDir, "generated", "search-client.ts");
5558
+ const examplesPath = join11(srcDir, "generated", "examples.ts");
5205
5559
  const written = new Set;
5206
5560
  const write = (path, content) => {
5207
5561
  written.add(normalize(path));
@@ -5211,26 +5565,33 @@ var generateRuntime = async (project) => {
5211
5565
  const askEnabled = config.ai.ask?.enabled ?? false;
5212
5566
  const exportPdf = config.export.pdf;
5213
5567
  const exportEpub = config.export.epub;
5214
- const [pages, detectedReact, userTheme, islandDiscovery] = await Promise.all([
5568
+ const [pages, detectedReact, userTheme, islandDiscovery, exampleDiscovery] = await Promise.all([
5215
5569
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
5216
5570
  detectNeedsReact(context.root),
5217
5571
  readOptional(context.themeFile),
5218
- discoverIslands(context.root)
5572
+ discoverIslands(context.root),
5573
+ discoverExamples(context.root)
5219
5574
  ]);
5220
- const islandFrameworks = new Set(islandDiscovery.islands.map((island) => island.framework));
5221
- const needsReact = detectedReact || askEnabled || islandFrameworks.has("react");
5222
- const needsVue = islandFrameworks.has("vue");
5223
- const needsSvelte = islandFrameworks.has("svelte");
5575
+ const frameworks = new Set([
5576
+ ...islandDiscovery.islands.map((island) => island.framework),
5577
+ ...exampleDiscovery.examples.map((example) => example.framework)
5578
+ ]);
5579
+ const needsReact = detectedReact || askEnabled || frameworks.has("react");
5580
+ const needsVue = frameworks.has("vue");
5581
+ const needsSvelte = frameworks.has("svelte");
5582
+ const ogRoutes = customOgRoutes(pages, config.title, config.description);
5224
5583
  const mcp = planMcp(project, srcDir);
5225
5584
  pages.push(...mcp.discoveryPages);
5226
5585
  const staged = collectStaged(project);
5227
5586
  const hasStaged = staged.size > 0;
5228
5587
  const structural = await Promise.all([
5229
- write(join9(out, "astro.config.mjs"), astroConfigTemplate({
5588
+ write(join11(out, "astro.config.mjs"), astroConfigTemplate({
5589
+ aliases: resolveTsconfigAliases(context.root),
5230
5590
  config,
5231
5591
  contentRoutes: project.manifest.routes.map((route) => route.path),
5232
5592
  context,
5233
5593
  dataPath,
5594
+ examplesPath,
5234
5595
  needsReact,
5235
5596
  needsSvelte,
5236
5597
  needsVue,
@@ -5238,18 +5599,19 @@ var generateRuntime = async (project) => {
5238
5599
  searchClientPath,
5239
5600
  themePath
5240
5601
  })),
5241
- write(join9(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
5242
- write(join9(out, "tsconfig.json"), runtimeTsconfigTemplate()),
5243
- write(join9(srcDir, "env.d.ts"), envTemplate()),
5244
- write(join9(srcDir, "content.config.ts"), contentConfigTemplate({ config, context, staged: hasStaged })),
5245
- write(join9(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
5602
+ write(join11(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
5603
+ write(join11(out, "tsconfig.json"), runtimeTsconfigTemplate()),
5604
+ write(join11(srcDir, "env.d.ts"), envTemplate()),
5605
+ write(join11(srcDir, "content.config.ts"), contentConfigTemplate({ config, context, staged: hasStaged })),
5606
+ write(join11(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
5246
5607
  askEnabled,
5247
5608
  exportEpub,
5248
5609
  exportPdf,
5249
5610
  mathEnabled: config.markdown.math
5250
5611
  })),
5251
- write(join9(srcDir, "generated", "components.ts"), userComponentsTemplate(context.componentsFile)),
5252
- write(join9(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
5612
+ write(join11(srcDir, "generated", "components.ts"), userComponentsTemplate(context.componentsFile)),
5613
+ write(join11(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
5614
+ write(join11(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples)),
5253
5615
  write(themePath, tailwindEntryTemplate({
5254
5616
  configTokens: `${buildThemeCss(config.theme)}${buildFontsCss(config.theme.fonts)}`,
5255
5617
  sources: [
@@ -5260,52 +5622,57 @@ var generateRuntime = async (project) => {
5260
5622
  userTheme
5261
5623
  }))
5262
5624
  ]);
5263
- await Promise.all(islandDiscovery.islands.map((island) => write(join9(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island))));
5625
+ await Promise.all(islandDiscovery.islands.map((island) => write(join11(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island))));
5626
+ await Promise.all(exampleDiscovery.examples.map((example) => write(join11(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example))));
5264
5627
  if (askEnabled) {
5265
- await write(join9(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(config.ai.ask)));
5628
+ await write(join11(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(config.ai.ask)));
5266
5629
  }
5267
5630
  await writeMcpFiles(project, mcp, write);
5268
5631
  if (config.seo.og.enabled) {
5269
- await write(join9(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate());
5632
+ await write(join11(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes));
5270
5633
  }
5271
5634
  const hasChangelog = project.graph.pages.some((page) => page.contentType === "changelog" && !(page.meta.draft || page.meta.sidebar.hidden));
5272
5635
  const changelogRouteTaken = project.graph.pages.some((page) => page.route === "/changelog");
5273
5636
  if (hasChangelog && !changelogRouteTaken) {
5274
- await write(join9(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({ askEnabled, exportEpub, exportPdf }));
5637
+ await write(join11(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({ askEnabled, exportEpub, exportPdf }));
5275
5638
  }
5276
5639
  await write(searchClientPath, searchClientTemplate(config));
5277
5640
  if (servesStaticIndex(config.search.provider)) {
5278
5641
  const documents = await buildSearchDocuments(project);
5279
- await write(join9(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
5642
+ await write(join11(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
5280
5643
  `);
5281
- await write(join9(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
5644
+ await write(join11(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
5282
5645
  }
5283
5646
  if (config.search.provider === "mixedbread") {
5284
- await write(join9(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
5647
+ await write(join11(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
5285
5648
  }
5286
5649
  const rawMarkdown = await buildRawMarkdown(project);
5287
5650
  await Promise.all([
5288
- write(join9(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
5651
+ write(join11(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
5289
5652
  `),
5290
- write(join9(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate()),
5291
- write(join9(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate())
5653
+ write(join11(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate()),
5654
+ write(join11(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate())
5292
5655
  ]);
5293
5656
  const feeds = buildRssFeeds(project);
5294
5657
  if (feeds.length > 0) {
5295
5658
  const feedXml = Object.fromEntries(feeds.map((feed) => [feed.type, renderRssFeed(feed)]));
5296
5659
  await Promise.all([
5297
- write(join9(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
5660
+ write(join11(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
5298
5661
  `),
5299
- write(join9(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
5662
+ write(join11(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
5300
5663
  ]);
5301
5664
  }
5302
- const warnings = [...mcp.warnings, ...islandDiscovery.warnings];
5665
+ const warnings = [
5666
+ ...mcp.warnings,
5667
+ ...islandDiscovery.warnings,
5668
+ ...exampleDiscovery.warnings
5669
+ ];
5303
5670
  for (const dep of searchProviderMeta(config.search.provider).runtimeDeps) {
5304
5671
  if (!canResolveFrom(context.root, dep)) {
5305
5672
  warnings.push(`Search provider "${config.search.provider}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`);
5306
5673
  }
5307
5674
  }
5308
- warnings.push(...islandFrameworkWarnings(islandFrameworks, context.root));
5675
+ warnings.push(...islandFrameworkWarnings(frameworks, context.root));
5309
5676
  if (hasReferences(config)) {
5310
5677
  const references = await buildReferenceFiles({
5311
5678
  config,
@@ -5313,10 +5680,10 @@ var generateRuntime = async (project) => {
5313
5680
  root: context.root
5314
5681
  });
5315
5682
  warnings.push(...references.warnings);
5316
- await Promise.all(references.files.map((file) => write(join9(srcDir, "pages", file.pagePath), file.content)));
5683
+ await Promise.all(references.files.map((file) => write(join11(srcDir, "pages", file.pagePath), file.content)));
5317
5684
  }
5318
- await write(join9(srcDir, "generated", "data.json"), buildRuntimeData(project));
5319
- await write(join9(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
5685
+ await write(join11(srcDir, "generated", "data.json"), buildRuntimeData(project));
5686
+ await write(join11(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
5320
5687
  `);
5321
5688
  await writeStagedContent(out, staged);
5322
5689
  await pruneOrphans(srcDir, written);
@@ -5324,13 +5691,13 @@ var generateRuntime = async (project) => {
5324
5691
  };
5325
5692
 
5326
5693
  // src/core/bridge.ts
5327
- import { existsSync as existsSync5 } from "node:fs";
5328
- import { readFile as readFile8 } from "node:fs/promises";
5329
- import { join as join10 } from "pathe";
5694
+ import { existsSync as existsSync6 } from "node:fs";
5695
+ import { readFile as readFile9 } from "node:fs/promises";
5696
+ import { join as join12 } from "pathe";
5330
5697
 
5331
5698
  // src/migrate/mintlify/config.ts
5332
- import { readFile as readFile7 } from "node:fs/promises";
5333
- import { dirname as dirname6, relative as relative5, resolve as resolve2 } from "pathe";
5699
+ import { readFile as readFile8 } from "node:fs/promises";
5700
+ import { dirname as dirname7, relative as relative6, resolve as resolve3 } from "pathe";
5334
5701
  var MINTLIFY_DEFAULT_IGNORES = [
5335
5702
  "**/_*",
5336
5703
  "**/.*",
@@ -5366,12 +5733,12 @@ var asDirectoryMode = (value) => value === "accordion" || value === "card" || va
5366
5733
  var withoutUndefined = (value) => Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
5367
5734
  var hasOwn = (object, key) => Object.hasOwn(object, key);
5368
5735
  var isInsideRoot = (root, candidate) => {
5369
- const rel = relative5(root, candidate);
5736
+ const rel = relative6(root, candidate);
5370
5737
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
5371
5738
  };
5372
5739
  var readJsonFile = async (file) => {
5373
5740
  try {
5374
- return JSON.parse(await readFile7(file, "utf-8"));
5741
+ return JSON.parse(await readFile8(file, "utf-8"));
5375
5742
  } catch (error) {
5376
5743
  throw new BlumeError({
5377
5744
  code: "BLUME_MINTLIFY_CONFIG_INVALID",
@@ -5391,7 +5758,7 @@ var resolveRefs = async (value, options) => {
5391
5758
  }
5392
5759
  const ref = asString(object.$ref);
5393
5760
  if (ref) {
5394
- const refFile = resolve2(dirname6(options.file), ref);
5761
+ const refFile = resolve3(dirname7(options.file), ref);
5395
5762
  if (!isInsideRoot(options.root, refFile)) {
5396
5763
  throw new BlumeError({
5397
5764
  code: "BLUME_MINTLIFY_REF_OUTSIDE_ROOT",
@@ -5791,7 +6158,7 @@ var mintlifyNavbar = (value) => {
5791
6158
  };
5792
6159
  var mintignorePatterns = async (root) => {
5793
6160
  try {
5794
- const raw = await readFile7(resolve2(root, ".mintignore"), "utf-8");
6161
+ const raw = await readFile8(resolve3(root, ".mintignore"), "utf-8");
5795
6162
  return raw.split(`
5796
6163
  `).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).filter((line) => !line.startsWith("!")).map((line) => line.endsWith("/") ? `${line}**` : line);
5797
6164
  } catch {
@@ -6015,8 +6382,8 @@ var mintlifyIcons = (value) => {
6015
6382
  });
6016
6383
  };
6017
6384
  var loadMintlifyConfig = async (root, file) => {
6018
- const projectRoot = resolve2(root);
6019
- const configFile = resolve2(file);
6385
+ const projectRoot = resolve3(root);
6386
+ const configFile = resolve3(file);
6020
6387
  const spec = asObject(await resolveRefs(await readJsonFile(configFile), {
6021
6388
  file: configFile,
6022
6389
  root: projectRoot,
@@ -6117,12 +6484,12 @@ var mintlifyI18n = (spec) => {
6117
6484
  // src/core/bridge.ts
6118
6485
  var MINTLIFY_CONFIG_FILES = ["docs.json", "mint.json"];
6119
6486
  var detectMintlifyBridge = async (root) => {
6120
- const configFile = MINTLIFY_CONFIG_FILES.map((name) => join10(root, name)).find((candidate) => existsSync5(candidate));
6487
+ const configFile = MINTLIFY_CONFIG_FILES.map((name) => join12(root, name)).find((candidate) => existsSync6(candidate));
6121
6488
  if (!configFile) {
6122
6489
  return null;
6123
6490
  }
6124
6491
  const config = await loadMintlifyConfig(root, configFile);
6125
- const spec = JSON.parse(await readFile8(configFile, "utf-8"));
6492
+ const spec = JSON.parse(await readFile9(configFile, "utf-8"));
6126
6493
  const i18n = mintlifyI18n(spec);
6127
6494
  if (i18n) {
6128
6495
  config.i18n = i18n;
@@ -6206,8 +6573,8 @@ var createModuleLoader = () => {
6206
6573
  };
6207
6574
 
6208
6575
  // src/core/project.ts
6209
- import { existsSync as existsSync6 } from "node:fs";
6210
- import { isAbsolute as isAbsolute2, join as join11, resolve as resolve3 } from "pathe";
6576
+ import { existsSync as existsSync7 } from "node:fs";
6577
+ import { isAbsolute as isAbsolute3, join as join13, resolve as resolve4 } from "pathe";
6211
6578
  var CONFIG_FILENAMES = [
6212
6579
  "blume.config.ts",
6213
6580
  "blume.config.mjs",
@@ -6217,8 +6584,8 @@ var THEME_FILENAMES = ["theme.css"];
6217
6584
  var COMPONENTS_FILENAMES = ["components.tsx", "components.ts"];
6218
6585
  var firstExisting = (root, names) => {
6219
6586
  for (const name of names) {
6220
- const candidate = join11(root, name);
6221
- if (existsSync6(candidate)) {
6587
+ const candidate = join13(root, name);
6588
+ if (existsSync7(candidate)) {
6222
6589
  return candidate;
6223
6590
  }
6224
6591
  }
@@ -6226,15 +6593,15 @@ var firstExisting = (root, names) => {
6226
6593
  };
6227
6594
  var findConfigFile = (root) => firstExisting(root, CONFIG_FILENAMES);
6228
6595
  var resolveProjectContext = (root, config) => {
6229
- const absoluteRoot = resolve3(root);
6230
- const contentRoot = isAbsolute2(config.content.root) ? config.content.root : join11(absoluteRoot, config.content.root);
6231
- const pagesPath = join11(absoluteRoot, config.content.pages);
6232
- const pagesRoot = existsSync6(pagesPath) ? pagesPath : null;
6596
+ const absoluteRoot = resolve4(root);
6597
+ const contentRoot = isAbsolute3(config.content.root) ? config.content.root : join13(absoluteRoot, config.content.root);
6598
+ const pagesPath = join13(absoluteRoot, config.content.pages);
6599
+ const pagesRoot = existsSync7(pagesPath) ? pagesPath : null;
6233
6600
  return {
6234
6601
  componentsFile: firstExisting(absoluteRoot, COMPONENTS_FILENAMES),
6235
6602
  configFile: findConfigFile(absoluteRoot),
6236
6603
  contentRoot,
6237
- outDir: join11(absoluteRoot, ".blume"),
6604
+ outDir: join13(absoluteRoot, ".blume"),
6238
6605
  pagesRoot,
6239
6606
  root: absoluteRoot,
6240
6607
  themeFile: firstExisting(absoluteRoot, THEME_FILENAMES)
@@ -7183,7 +7550,7 @@ var buildContentGraph = (pages, options) => {
7183
7550
 
7184
7551
  // src/core/last-modified.ts
7185
7552
  import { execFileSync } from "node:child_process";
7186
- import { relative as relative6 } from "pathe";
7553
+ import { relative as relative7 } from "pathe";
7187
7554
  var resolveLastModifiedConfig = (value) => {
7188
7555
  if (value === false) {
7189
7556
  return { enabled: false, source: "git" };
@@ -7223,7 +7590,7 @@ var gitLastModifiedTimes = (root, contentRoot, sourcePaths) => {
7223
7590
  const byRepoPath = parseGitLog(output);
7224
7591
  const result = new Map;
7225
7592
  for (const sourcePath of sourcePaths) {
7226
- const iso = byRepoPath.get(relative6(gitRoot, sourcePath));
7593
+ const iso = byRepoPath.get(relative7(gitRoot, sourcePath));
7227
7594
  if (iso) {
7228
7595
  result.set(sourcePath, iso);
7229
7596
  }
@@ -7235,8 +7602,8 @@ var gitLastModifiedTimes = (root, contentRoot, sourcePaths) => {
7235
7602
  };
7236
7603
 
7237
7604
  // src/core/meta.ts
7238
- import { basename as basename2, dirname as dirname7, relative as relative7 } from "pathe";
7239
- import { glob as glob4 } from "tinyglobby";
7605
+ import { basename as basename2, dirname as dirname8, relative as relative8 } from "pathe";
7606
+ import { glob as glob5 } from "tinyglobby";
7240
7607
  var META_FILES = [
7241
7608
  "**/meta.ts",
7242
7609
  "**/meta.js",
@@ -7247,16 +7614,16 @@ var META_FILES = [
7247
7614
  ];
7248
7615
  var resolveMeta = async (mod) => typeof mod === "function" ? await mod() : mod;
7249
7616
  var discoverFolderMeta = async (contentRoot) => {
7250
- const files = await glob4(META_FILES, {
7617
+ const files = await glob5(META_FILES, {
7251
7618
  absolute: true,
7252
7619
  cwd: contentRoot,
7253
7620
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
7254
7621
  onlyFiles: true
7255
7622
  });
7256
- const load = createModuleLoader();
7623
+ const load2 = createModuleLoader();
7257
7624
  const loaded = await Promise.all(files.map(async (file) => {
7258
7625
  try {
7259
- return { file, ok: true, value: await resolveMeta(await load(file)) };
7626
+ return { file, ok: true, value: await resolveMeta(await load2(file)) };
7260
7627
  } catch (error) {
7261
7628
  return { error, file, ok: false };
7262
7629
  }
@@ -7265,7 +7632,7 @@ var discoverFolderMeta = async (contentRoot) => {
7265
7632
  const shared = new Map;
7266
7633
  const diagnostics = [];
7267
7634
  for (const entry of loaded) {
7268
- const dir = relative7(contentRoot, dirname7(entry.file));
7635
+ const dir = relative8(contentRoot, dirname8(entry.file));
7269
7636
  if (!entry.ok) {
7270
7637
  diagnostics.push({
7271
7638
  code: "BLUME_META_LOAD_FAILED",
@@ -7290,6 +7657,7 @@ var discoverFolderMeta = async (contentRoot) => {
7290
7657
  };
7291
7658
 
7292
7659
  // src/core/sources/normalize.ts
7660
+ import GithubSlugger from "github-slugger";
7293
7661
  import { extname as extname3 } from "pathe";
7294
7662
  var NUMERIC_PREFIX2 = /^\d+[-_.]/u;
7295
7663
  var GROUP_FOLDER2 = /^\((?<label>.+)\)$/u;
@@ -7322,6 +7690,7 @@ var CODE_FENCE2 = /^```/u;
7322
7690
  var ATX_HEADING = /^(?<hashes>#{1,6})\s+(?<text>.+?)\s*#*$/u;
7323
7691
  var extractHeadings = (body) => {
7324
7692
  const headings = [];
7693
+ const slugger = new GithubSlugger;
7325
7694
  let inFence = false;
7326
7695
  for (const line of body.split(`
7327
7696
  `)) {
@@ -7336,7 +7705,7 @@ var extractHeadings = (body) => {
7336
7705
  if (match?.groups) {
7337
7706
  const depth = match.groups.hashes?.length ?? 1;
7338
7707
  const text = (match.groups.text ?? "").trim();
7339
- headings.push({ depth, slug: slugify2(text), text });
7708
+ headings.push({ depth, slug: slugger.slug(text), text });
7340
7709
  }
7341
7710
  }
7342
7711
  return headings;
@@ -7433,18 +7802,17 @@ var normalizeEntry = (entry, ctx) => {
7433
7802
  };
7434
7803
 
7435
7804
  // src/core/sources/resolve.ts
7436
- import { join as join20 } from "pathe";
7805
+ import { join as join22 } from "pathe";
7437
7806
 
7438
7807
  // src/core/sources/filesystem.ts
7439
- import { existsSync as existsSync7, watch as fsWatch } from "node:fs";
7440
- import { readFile as readFile9 } from "node:fs/promises";
7441
- import matter3 from "gray-matter";
7442
- import { extname as extname4, isAbsolute as isAbsolute3, join as join12, relative as relative8, resolve as resolve4 } from "pathe";
7443
- import { glob as glob5 } from "tinyglobby";
7808
+ import { existsSync as existsSync8, watch as fsWatch } from "node:fs";
7809
+ import { readFile as readFile10 } from "node:fs/promises";
7810
+ import { extname as extname4, isAbsolute as isAbsolute4, join as join14, relative as relative9, resolve as resolve5 } from "pathe";
7811
+ import { glob as glob6 } from "tinyglobby";
7444
7812
  var filesystemSource = (options) => {
7445
- const contentRoot = isAbsolute3(options.root) ? options.root : join12(resolve4(options.projectRoot), options.root);
7446
- const load = async () => {
7447
- const files = await glob5(options.include, {
7813
+ const contentRoot = isAbsolute4(options.root) ? options.root : join14(resolve5(options.projectRoot), options.root);
7814
+ const load2 = async () => {
7815
+ const files = await glob6(options.include, {
7448
7816
  absolute: true,
7449
7817
  cwd: contentRoot,
7450
7818
  ignore: options.exclude,
@@ -7452,21 +7820,21 @@ var filesystemSource = (options) => {
7452
7820
  });
7453
7821
  files.sort();
7454
7822
  const entries = await Promise.all(files.map(async (file) => {
7455
- const source = await readFile9(file, "utf-8");
7823
+ const source = await readFile10(file, "utf-8");
7456
7824
  const ext = extname4(file).toLowerCase();
7457
7825
  const format = ext === ".mdx" ? "mdx" : "md";
7458
- const parsed = matter3(source);
7826
+ const parsed = frontmatter_default(source);
7459
7827
  return {
7460
7828
  body: { format, text: parsed.content },
7461
7829
  data: parsed.data,
7462
- ref: relative8(contentRoot, file),
7830
+ ref: relative9(contentRoot, file),
7463
7831
  sourcePath: file
7464
7832
  };
7465
7833
  }));
7466
7834
  return { diagnostics: [], entries };
7467
7835
  };
7468
7836
  const validate = () => {
7469
- if (!existsSync7(contentRoot)) {
7837
+ if (!existsSync8(contentRoot)) {
7470
7838
  throw new BlumeError({
7471
7839
  code: options.missingCode ?? "BLUME_CONTENT_ROOT_MISSING",
7472
7840
  file: contentRoot,
@@ -7477,7 +7845,7 @@ var filesystemSource = (options) => {
7477
7845
  }
7478
7846
  };
7479
7847
  const watch = (onChange) => {
7480
- if (!existsSync7(contentRoot)) {
7848
+ if (!existsSync8(contentRoot)) {
7481
7849
  return () => {};
7482
7850
  }
7483
7851
  const watcher = fsWatch(contentRoot, { recursive: true }, onChange);
@@ -7485,22 +7853,19 @@ var filesystemSource = (options) => {
7485
7853
  };
7486
7854
  return {
7487
7855
  contentRoot,
7488
- load,
7856
+ load: load2,
7489
7857
  name: options.name,
7490
7858
  prefix: options.prefix,
7491
- read: (ref) => readFile9(join12(contentRoot, ref), "utf-8"),
7859
+ read: (ref) => readFile10(join14(contentRoot, ref), "utf-8"),
7492
7860
  staged: false,
7493
7861
  validate,
7494
7862
  watch
7495
7863
  };
7496
7864
  };
7497
7865
 
7498
- // src/core/sources/mdx-remote.ts
7499
- import matter4 from "gray-matter";
7500
-
7501
7866
  // src/core/sources/cache.ts
7502
- import { mkdir as mkdir3, readFile as readFile10, writeFile as writeFile3 } from "node:fs/promises";
7503
- import { join as join13 } from "pathe";
7867
+ import { mkdir as mkdir3, readFile as readFile11, writeFile as writeFile3 } from "node:fs/promises";
7868
+ import { join as join15 } from "pathe";
7504
7869
  var hashText = (text) => {
7505
7870
  let hash = 5381;
7506
7871
  for (let i = 0;i < text.length; i += 1) {
@@ -7509,11 +7874,11 @@ var hashText = (text) => {
7509
7874
  return hash.toString(36);
7510
7875
  };
7511
7876
  var entriesDigest = (entries) => hashText(entries.map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`).join("|"));
7512
- var pollingWatch = (load, intervalSeconds) => (onChange) => {
7877
+ var pollingWatch = (load2, intervalSeconds) => (onChange) => {
7513
7878
  let last = "";
7514
7879
  const tick = async () => {
7515
7880
  try {
7516
- const { entries } = await load();
7881
+ const { entries } = await load2();
7517
7882
  const next = entriesDigest(entries);
7518
7883
  if (last && next !== last) {
7519
7884
  onChange();
@@ -7527,11 +7892,11 @@ var pollingWatch = (load, intervalSeconds) => (onChange) => {
7527
7892
  return () => clearInterval(timer);
7528
7893
  };
7529
7894
  var snapshotCache = (cacheDir) => {
7530
- const file = join13(cacheDir, "entries.json");
7895
+ const file = join15(cacheDir, "entries.json");
7531
7896
  return {
7532
7897
  read: async () => {
7533
7898
  try {
7534
- return JSON.parse(await readFile10(file, "utf-8"));
7899
+ return JSON.parse(await readFile11(file, "utf-8"));
7535
7900
  } catch {
7536
7901
  return [];
7537
7902
  }
@@ -7663,7 +8028,7 @@ var mdxRemoteSource = (options, ctx) => {
7663
8028
  throw new Error(`${item.fetchUrl} -> ${res.status}`);
7664
8029
  }
7665
8030
  const text = await res.text();
7666
- const parsed = matter4(text);
8031
+ const parsed = frontmatter_default(text);
7667
8032
  const format = item.ref.toLowerCase().endsWith(".mdx") ? "mdx" : "md";
7668
8033
  return {
7669
8034
  body: { format, text: parsed.content },
@@ -7674,7 +8039,7 @@ var mdxRemoteSource = (options, ctx) => {
7674
8039
  ref: item.ref
7675
8040
  };
7676
8041
  };
7677
- const load = async () => {
8042
+ const load2 = async () => {
7678
8043
  const result = await loadWithCache(options.name, cache, async () => {
7679
8044
  const refs = await enumerate();
7680
8045
  return await Promise.all(refs.map(fetchEntry));
@@ -7692,35 +8057,91 @@ var mdxRemoteSource = (options, ctx) => {
7692
8057
  return entry?.raw ?? entry?.body.text ?? "";
7693
8058
  };
7694
8059
  return {
7695
- load,
8060
+ load: load2,
7696
8061
  name: options.name,
7697
8062
  prefix: options.prefix,
7698
8063
  read,
7699
8064
  staged: true,
7700
- watch: options.pollInterval ? pollingWatch(load, options.pollInterval) : undefined
8065
+ watch: options.pollInterval ? pollingWatch(load2, options.pollInterval) : undefined
7701
8066
  };
7702
8067
  };
7703
8068
 
7704
8069
  // src/core/sources/mintlify.ts
7705
- import { existsSync as existsSync8, watch as fsWatch2 } from "node:fs";
7706
- import { readFile as readFile11 } from "node:fs/promises";
7707
- import matter7 from "gray-matter";
7708
- import { isAbsolute as isAbsolute4, join as join16, relative as relative11, resolve as resolve6 } from "pathe";
7709
- import { glob as glob6 } from "tinyglobby";
7710
-
7711
- // src/migrate/mintlify/transform.ts
7712
- import matter6 from "gray-matter";
8070
+ import { existsSync as existsSync10, watch as fsWatch2 } from "node:fs";
8071
+ import { readFile as readFile13 } from "node:fs/promises";
8072
+ import { isAbsolute as isAbsolute5, join as join18, relative as relative12, resolve as resolve7 } from "pathe";
8073
+ import { glob as glob7 } from "tinyglobby";
7713
8074
 
7714
8075
  // src/migrate/shared.ts
7715
- import { writeFile as writeFile4 } from "node:fs/promises";
7716
- import { join as join14 } from "pathe";
8076
+ import { existsSync as existsSync9 } from "node:fs";
8077
+ import { readFile as readFile12, writeFile as writeFile4 } from "node:fs/promises";
8078
+ import { join as join16 } from "pathe";
7717
8079
  var writeBlumeConfig = async (root, config) => {
7718
8080
  const body = `import { defineConfig } from "blume";
7719
8081
 
7720
8082
  export default defineConfig(${JSON.stringify(config, null, 2)});
7721
8083
  `;
7722
- await writeFile4(join14(root, "blume.config.ts"), body, "utf-8");
8084
+ await writeFile4(join16(root, "blume.config.ts"), body, "utf-8");
7723
8085
  };
8086
+ var BLUME_SCRIPTS = {
8087
+ build: "blume build",
8088
+ dev: "blume dev",
8089
+ start: "blume preview"
8090
+ };
8091
+ var rewriteFrameworkScripts = async (root, cli, remove) => {
8092
+ const pkgPath = join16(root, "package.json");
8093
+ if (!existsSync9(pkgPath)) {
8094
+ return false;
8095
+ }
8096
+ let pkg;
8097
+ try {
8098
+ pkg = JSON.parse(await readFile12(pkgPath, "utf-8"));
8099
+ } catch {
8100
+ return false;
8101
+ }
8102
+ const { scripts } = pkg;
8103
+ if (!scripts || typeof scripts !== "object") {
8104
+ return false;
8105
+ }
8106
+ const next = {};
8107
+ let changed = false;
8108
+ for (const [name, command] of Object.entries(scripts)) {
8109
+ const blume = BLUME_SCRIPTS[name];
8110
+ if (typeof command === "string" && remove?.test(command)) {
8111
+ changed = true;
8112
+ } else if (typeof command === "string" && blume && cli.test(command) && command !== blume) {
8113
+ next[name] = blume;
8114
+ changed = true;
8115
+ } else {
8116
+ next[name] = command;
8117
+ }
8118
+ }
8119
+ if (changed) {
8120
+ pkg.scripts = next;
8121
+ await writeFile4(pkgPath, `${JSON.stringify(pkg, null, 2)}
8122
+ `, "utf-8");
8123
+ }
8124
+ return changed;
8125
+ };
8126
+ var gitignoreKey = (line) => line.trim().replace(/\/+$/u, "");
8127
+ var ensureGitignore = async (root, entries) => {
8128
+ const path = join16(root, ".gitignore");
8129
+ const existing = existsSync9(path) ? await readFile12(path, "utf-8") : "";
8130
+ const present = new Set(existing.split(`
8131
+ `).map(gitignoreKey).filter(Boolean));
8132
+ const added = entries.filter((entry) => !present.has(gitignoreKey(entry)));
8133
+ if (added.length === 0) {
8134
+ return [];
8135
+ }
8136
+ const gap = existing.length > 0 && !existing.endsWith(`
8137
+ `) ? `
8138
+ ` : "";
8139
+ await writeFile4(path, `${existing}${gap}${added.join(`
8140
+ `)}
8141
+ `, "utf-8");
8142
+ return added;
8143
+ };
8144
+ var leftoverFiles = (root, candidates) => candidates.filter((candidate) => existsSync9(join16(root, candidate)));
7724
8145
  var attribute = (attrs, name) => {
7725
8146
  const match = attrs.match(new RegExp(`\\b${name}=(?:"(?<dq>[^"]*)"|'(?<sq>[^']*)')`, "u"));
7726
8147
  return match?.groups?.dq ?? match?.groups?.sq;
@@ -8117,7 +8538,7 @@ var isLiteralObject = (value) => typeof value === "object" && value !== null &&
8117
8538
  var asLiteralArray = (value) => Array.isArray(value) ? value : undefined;
8118
8539
 
8119
8540
  // src/migrate/mintlify/content.ts
8120
- import { dirname as dirname8, join as join15, relative as relative9 } from "pathe";
8541
+ import { dirname as dirname9, join as join17, relative as relative10 } from "pathe";
8121
8542
  var CALLOUT_DIRECTIVES = {
8122
8543
  Check: "success",
8123
8544
  Danger: "danger",
@@ -8153,9 +8574,9 @@ var rewriteSnippetImports = (source, options) => {
8153
8574
  if (/\.mdx?$/u.test(importSource)) {
8154
8575
  return "";
8155
8576
  }
8156
- const target = join15(options.root, importSource.replace(/^\/+/u, ""));
8577
+ const target = join17(options.root, importSource.replace(/^\/+/u, ""));
8157
8578
  components.push(importSource.replace(/^\/+/u, ""));
8158
- let rel = relative9(dirname8(options.filePath), target);
8579
+ let rel = relative10(dirname9(options.filePath), target);
8159
8580
  if (!rel.startsWith(".")) {
8160
8581
  rel = `./${rel}`;
8161
8582
  }
@@ -8345,8 +8766,7 @@ var rewriteMintlifySvgIconProps = (source) => {
8345
8766
 
8346
8767
  // src/migrate/mintlify/snippets.ts
8347
8768
  import { readFile as readFileFromDisk } from "node:fs/promises";
8348
- import matter5 from "gray-matter";
8349
- import { dirname as dirname9, relative as relative10, resolve as resolve5 } from "pathe";
8769
+ import { dirname as dirname10, relative as relative11, resolve as resolve6 } from "pathe";
8350
8770
  var MARKDOWN_SNIPPET_IMPORT = /^import\s+(?<name>[$A-Z_a-z][$\w]*)\s+from\s+["'](?<source>[^"']+\.mdx?)["'];?\s*$/gmu;
8351
8771
  var NAMED_SNIPPET_IMPORT = /^import\s+\{(?<names>[^}]+)\}\s+from\s+["'](?<source>[^"']+\.mdx?)["'];?\s*$/gmu;
8352
8772
  var EXPORTED_STRING_CONST = /^export\s+const\s+(?<name>[$A-Z_a-z][$\w]*)\s*=\s*(?:"(?<double>(?:\\.|[^"\\])*)"|'(?<single>(?:\\.|[^'\\])*)'|`(?<template>(?:\\.|[^`\\])*)`)\s*;?\s*$/gmu;
@@ -8356,14 +8776,14 @@ var GLOBAL_VARIABLE = /\{\{\s*(?<name>[A-Za-z0-9-]+)\s*\}\}/gu;
8356
8776
  var FRONTMATTER_BLOCK = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/u;
8357
8777
  var USER_EXPORT = /^(?:export\s+)?(?:const|let|var)\s+user\s*=|^import\s+\{\s*user\s*\}/mu;
8358
8778
  var isInsideRoot2 = (root, candidate) => {
8359
- const rel = relative10(root, candidate);
8779
+ const rel = relative11(root, candidate);
8360
8780
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
8361
8781
  };
8362
8782
  var escapeRegExp = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
8363
8783
  var snippetSelfClosingTagPattern = (name) => new RegExp(`<${escapeRegExp(name)}(?<attrs>[^>]*?)\\s*/>`, "gu");
8364
8784
  var snippetPairedTagPattern = (name) => new RegExp(`<${escapeRegExp(name)}(?<attrs>[^>]*?)>[\\s\\S]*?</${escapeRegExp(name)}>`, "gu");
8365
8785
  var rootRelativePath = (root, file) => {
8366
- const rel = relative10(root, file);
8786
+ const rel = relative11(root, file);
8367
8787
  return rel ? `/${rel}` : "/";
8368
8788
  };
8369
8789
  var snippetCycleMessage = (root, file, trail) => {
@@ -8372,7 +8792,7 @@ var snippetCycleMessage = (root, file, trail) => {
8372
8792
  return `Circular Mintlify snippet import detected: ${cycle}`;
8373
8793
  };
8374
8794
  var resolveSnippetPath = (options) => {
8375
- const target = options.source.startsWith("/") ? resolve5(options.root, options.source.slice(1)) : resolve5(dirname9(options.filePath), options.source);
8795
+ const target = options.source.startsWith("/") ? resolve6(options.root, options.source.slice(1)) : resolve6(dirname10(options.filePath), options.source);
8376
8796
  return isInsideRoot2(options.root, target) ? target : null;
8377
8797
  };
8378
8798
  var collectImports = (source) => [...source.matchAll(MARKDOWN_SNIPPET_IMPORT)].flatMap((match) => {
@@ -8435,10 +8855,10 @@ var rewriteMintlifyMarkdownSnippets = async (source, options) => {
8435
8855
  throw new Error(snippetCycleMessage(options.root, file, options.trail ?? []));
8436
8856
  }
8437
8857
  seen.add(file);
8438
- const readFile11 = options.readFile ?? readFileFromDisk;
8858
+ const readFile13 = options.readFile ?? readFileFromDisk;
8439
8859
  try {
8440
- const raw = await readFile11(file);
8441
- const content = matter5(raw).content.trim();
8860
+ const raw = await readFile13(file);
8861
+ const content = frontmatter_default(raw).content.trim();
8442
8862
  const transformed = await rewriteMintlifyMarkdownSnippets(content, {
8443
8863
  ...options,
8444
8864
  filePath: file,
@@ -8478,7 +8898,7 @@ var rewriteMintlifyMarkdownSnippets = async (source, options) => {
8478
8898
  return await inlineAt(0, source);
8479
8899
  };
8480
8900
  var rewriteMintlifySnippetVariables = async (source, options) => {
8481
- const readFile11 = options.readFile ?? readFileFromDisk;
8901
+ const readFile13 = options.readFile ?? readFileFromDisk;
8482
8902
  const inlineImport = async (current, variableImport) => {
8483
8903
  const file = resolveSnippetPath({
8484
8904
  filePath: options.filePath,
@@ -8488,7 +8908,7 @@ var rewriteMintlifySnippetVariables = async (source, options) => {
8488
8908
  if (!file) {
8489
8909
  return current;
8490
8910
  }
8491
- const exports = collectStringExports(matter5(await readFile11(file)).content);
8911
+ const exports = collectStringExports(frontmatter_default(await readFile13(file)).content);
8492
8912
  let next = current;
8493
8913
  for (const name of variableImport.names) {
8494
8914
  const value = exports.get(name.imported);
@@ -8544,10 +8964,10 @@ var transformMintlifyContent = async (raw, options) => {
8544
8964
  text = rewriteMintlifyExampleBlocks(text);
8545
8965
  text = rewriteMintlifyCallouts(text);
8546
8966
  const unsupported = unsupportedMintlifyComponents(text);
8547
- const parsed = matter6(text);
8967
+ const parsed = frontmatter_default(text);
8548
8968
  const mapped = normalizeMintlifyPageMeta(parsed.data);
8549
8969
  const { data, removed } = stripUnknownPageMeta(mapped);
8550
- const content = Object.keys(data).length > 0 ? matter6.stringify(parsed.content, data) : parsed.content;
8970
+ const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
8551
8971
  return {
8552
8972
  components: snippetImports.components,
8553
8973
  content,
@@ -8566,15 +8986,15 @@ var MINTLIFY_SOURCE_IGNORES = [
8566
8986
  "snippets/**"
8567
8987
  ];
8568
8988
  var mintlifySource = (options) => {
8569
- const contentRoot = isAbsolute4(options.root) ? options.root : join16(resolve6(options.projectRoot), options.root);
8989
+ const contentRoot = isAbsolute5(options.root) ? options.root : join18(resolve7(options.projectRoot), options.root);
8570
8990
  const ignore = [...new Set([...options.exclude, ...MINTLIFY_SOURCE_IGNORES])];
8571
8991
  const transform = (raw, file) => transformMintlifyContent(raw, {
8572
8992
  filePath: file,
8573
- root: resolve6(options.projectRoot),
8993
+ root: resolve7(options.projectRoot),
8574
8994
  variables: options.variables
8575
8995
  });
8576
- const load = async () => {
8577
- const files = await glob6(options.include, {
8996
+ const load2 = async () => {
8997
+ const files = await glob7(options.include, {
8578
8998
  absolute: true,
8579
8999
  cwd: contentRoot,
8580
9000
  ignore,
@@ -8583,16 +9003,16 @@ var mintlifySource = (options) => {
8583
9003
  files.sort();
8584
9004
  const unsupported = new Set;
8585
9005
  const entries = await Promise.all(files.map(async (file) => {
8586
- const result = await transform(await readFile11(file, "utf-8"), file);
9006
+ const result = await transform(await readFile13(file, "utf-8"), file);
8587
9007
  for (const name of result.unsupported) {
8588
9008
  unsupported.add(name);
8589
9009
  }
8590
- const parsed = matter7(result.content);
9010
+ const parsed = frontmatter_default(result.content);
8591
9011
  return {
8592
9012
  body: { format: "mdx", text: parsed.content },
8593
9013
  data: parsed.data,
8594
9014
  raw: result.content,
8595
- ref: relative11(contentRoot, file),
9015
+ ref: relative12(contentRoot, file),
8596
9016
  sourcePath: file
8597
9017
  };
8598
9018
  }));
@@ -8606,7 +9026,7 @@ var mintlifySource = (options) => {
8606
9026
  return { diagnostics, entries };
8607
9027
  };
8608
9028
  const validate = () => {
8609
- if (!existsSync8(contentRoot)) {
9029
+ if (!existsSync10(contentRoot)) {
8610
9030
  throw new BlumeError({
8611
9031
  code: "BLUME_CONTENT_ROOT_MISSING",
8612
9032
  file: contentRoot,
@@ -8618,11 +9038,11 @@ var mintlifySource = (options) => {
8618
9038
  };
8619
9039
  const watch = (onChange) => {
8620
9040
  const disposers = [];
8621
- if (existsSync8(contentRoot)) {
9041
+ if (existsSync10(contentRoot)) {
8622
9042
  const watcher = fsWatch2(contentRoot, { recursive: true }, onChange);
8623
9043
  disposers.push(() => watcher.close());
8624
9044
  }
8625
- if (options.configFile && existsSync8(options.configFile)) {
9045
+ if (options.configFile && existsSync10(options.configFile)) {
8626
9046
  const watcher = fsWatch2(options.configFile, onChange);
8627
9047
  disposers.push(() => watcher.close());
8628
9048
  }
@@ -8633,13 +9053,13 @@ var mintlifySource = (options) => {
8633
9053
  };
8634
9054
  };
8635
9055
  const read = async (ref) => {
8636
- const file = join16(contentRoot, ref);
8637
- const result = await transform(await readFile11(file, "utf-8"), file);
9056
+ const file = join18(contentRoot, ref);
9057
+ const result = await transform(await readFile13(file, "utf-8"), file);
8638
9058
  return result.content;
8639
9059
  };
8640
9060
  return {
8641
9061
  contentRoot,
8642
- load,
9062
+ load: load2,
8643
9063
  name: options.name,
8644
9064
  prefix: options.prefix,
8645
9065
  read,
@@ -8650,12 +9070,11 @@ var mintlifySource = (options) => {
8650
9070
  };
8651
9071
 
8652
9072
  // src/core/sources/notion.ts
8653
- import matter8 from "gray-matter";
8654
- import { join as join18 } from "pathe";
9073
+ import { join as join20 } from "pathe";
8655
9074
 
8656
9075
  // src/core/sources/assets.ts
8657
9076
  import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
8658
- import { extname as extname5, join as join17 } from "pathe";
9077
+ import { extname as extname5, join as join19 } from "pathe";
8659
9078
  var MD_IMAGE = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
8660
9079
  var REMOTE = /^https?:\/\//u;
8661
9080
  var SAFE_EXT = /^\.[a-z0-9]+$/iu;
@@ -8684,7 +9103,7 @@ var materializeAssets = async (markdown, ctx) => {
8684
9103
  const bytes = new Uint8Array(await res.arrayBuffer());
8685
9104
  const file = `${hashText(url)}${extFor(url)}`;
8686
9105
  await mkdir4(ctx.assetsDir, { recursive: true });
8687
- await writeFile5(join17(ctx.assetsDir, file), bytes);
9106
+ await writeFile5(join19(ctx.assetsDir, file), bytes);
8688
9107
  rewrites.set(url, `${ctx.assetsBaseUrl}/${file}`);
8689
9108
  } catch (error) {
8690
9109
  diagnostics.push({
@@ -8778,8 +9197,8 @@ ${text}
8778
9197
  };
8779
9198
  var notionSource = (options, ctx) => {
8780
9199
  const props = options.properties ?? {};
8781
- const cache = snapshotCache(ctx?.cacheDir ?? join18(".blume", "cache", options.name));
8782
- const assetsDir = ctx?.assetsDir ?? join18(".blume", "public", "blume-assets", options.name);
9200
+ const cache = snapshotCache(ctx?.cacheDir ?? join20(".blume", "cache", options.name));
9201
+ const assetsDir = ctx?.assetsDir ?? join20(".blume", "public", "blume-assets", options.name);
8783
9202
  const assetsBaseUrl = ctx?.assetsBaseUrl ?? `/blume-assets/${options.name}`;
8784
9203
  let snapshot = new Map;
8785
9204
  const resolveClient = async () => {
@@ -8895,7 +9314,7 @@ ${rendered.join(`
8895
9314
  assetsDir,
8896
9315
  fetchImpl: options.fetchImpl
8897
9316
  });
8898
- const raw = matter8.stringify(assets.markdown, data);
9317
+ const raw = frontmatter_default.stringify(assets.markdown, data);
8899
9318
  return {
8900
9319
  diagnostics: assets.diagnostics,
8901
9320
  entry: {
@@ -8908,7 +9327,7 @@ ${rendered.join(`
8908
9327
  }
8909
9328
  };
8910
9329
  };
8911
- const load = async () => {
9330
+ const load2 = async () => {
8912
9331
  const assetDiagnostics = [];
8913
9332
  const result = await loadWithCache(options.name, cache, async () => {
8914
9333
  const client = await resolveClient();
@@ -8937,18 +9356,17 @@ ${rendered.join(`
8937
9356
  return all.find((e) => e.ref === ref)?.raw ?? "";
8938
9357
  };
8939
9358
  return {
8940
- load,
9359
+ load: load2,
8941
9360
  name: options.name,
8942
9361
  prefix: options.prefix,
8943
9362
  read,
8944
9363
  staged: true,
8945
- watch: options.pollInterval ? pollingWatch(load, options.pollInterval) : undefined
9364
+ watch: options.pollInterval ? pollingWatch(load2, options.pollInterval) : undefined
8946
9365
  };
8947
9366
  };
8948
9367
 
8949
9368
  // src/core/sources/sanity.ts
8950
- import matter9 from "gray-matter";
8951
- import { join as join19 } from "pathe";
9369
+ import { join as join21 } from "pathe";
8952
9370
 
8953
9371
  // src/core/sources/portable-text.ts
8954
9372
  var HEADING_STYLES = {
@@ -9087,7 +9505,7 @@ var resolveClient = async (options, preview) => {
9087
9505
  };
9088
9506
  var sanitySource = (options, ctx) => {
9089
9507
  const fields = options.fields ?? {};
9090
- const cache = snapshotCache(ctx?.cacheDir ?? join19(".blume", "cache", options.name));
9508
+ const cache = snapshotCache(ctx?.cacheDir ?? join21(".blume", "cache", options.name));
9091
9509
  let snapshot = new Map;
9092
9510
  const toEntry = (doc) => {
9093
9511
  const slugValue = asString2(getPath(doc, fields.slug ?? "slug.current")) ?? asString2(doc._id) ?? "untitled";
@@ -9109,7 +9527,7 @@ var sanitySource = (options, ctx) => {
9109
9527
  },
9110
9528
  serializers: options.serializers
9111
9529
  }) : "";
9112
- const raw = matter9.stringify(markdown, data);
9530
+ const raw = frontmatter_default.stringify(markdown, data);
9113
9531
  return {
9114
9532
  body: { format: "md", text: markdown },
9115
9533
  data,
@@ -9119,7 +9537,7 @@ var sanitySource = (options, ctx) => {
9119
9537
  ref: `${slug}.md`
9120
9538
  };
9121
9539
  };
9122
- const load = async () => {
9540
+ const load2 = async () => {
9123
9541
  const result = await loadWithCache(options.name, cache, async () => {
9124
9542
  const client = await resolveClient(options, ctx?.preview ?? false);
9125
9543
  const docs = await client.fetch(options.query);
@@ -9137,12 +9555,12 @@ var sanitySource = (options, ctx) => {
9137
9555
  return all.find((e) => e.ref === ref)?.raw ?? "";
9138
9556
  };
9139
9557
  return {
9140
- load,
9558
+ load: load2,
9141
9559
  name: options.name,
9142
9560
  prefix: options.prefix,
9143
9561
  read,
9144
9562
  staged: true,
9145
- watch: options.pollInterval ? pollingWatch(load, options.pollInterval) : undefined
9563
+ watch: options.pollInterval ? pollingWatch(load2, options.pollInterval) : undefined
9146
9564
  };
9147
9565
  };
9148
9566
 
@@ -9162,8 +9580,8 @@ var uniqueNamer = () => {
9162
9580
  };
9163
9581
  var sourceContext = (context, name, runtime) => ({
9164
9582
  assetsBaseUrl: `/blume-assets/${name}`,
9165
- assetsDir: join20(context.outDir, "public", "blume-assets", name),
9166
- cacheDir: join20(context.outDir, "cache", name),
9583
+ assetsDir: join22(context.outDir, "public", "blume-assets", name),
9584
+ cacheDir: join22(context.outDir, "cache", name),
9167
9585
  mode: runtime.mode,
9168
9586
  preview: runtime.preview,
9169
9587
  projectRoot: context.root,
@@ -9396,7 +9814,7 @@ var buildCommand = defineCommand2({
9396
9814
  logLevel: "info",
9397
9815
  root: project.context.outDir
9398
9816
  });
9399
- const distDir = join21(root, "dist");
9817
+ const distDir = join23(root, "dist");
9400
9818
  if (project.config.search.provider === "pagefind") {
9401
9819
  logger.start("Building search index");
9402
9820
  const indexed = await buildSearchIndex(distDir);
@@ -9410,19 +9828,19 @@ var buildCommand = defineCommand2({
9410
9828
  if (project.config.ai.llmsTxt) {
9411
9829
  const { index, full } = await buildLlmsFiles(project);
9412
9830
  await Promise.all([
9413
- writeFile6(join21(distDir, "llms.txt"), index, "utf-8"),
9414
- writeFile6(join21(distDir, "llms-full.txt"), full, "utf-8")
9831
+ writeFile6(join23(distDir, "llms.txt"), index, "utf-8"),
9832
+ writeFile6(join23(distDir, "llms-full.txt"), full, "utf-8")
9415
9833
  ]);
9416
9834
  logger.success("Generated llms.txt and llms-full.txt");
9417
9835
  }
9418
9836
  const sitemap = buildSitemap(project);
9419
- if (sitemap && !existsSync9(join21(distDir, "sitemap.xml"))) {
9420
- await writeFile6(join21(distDir, "sitemap.xml"), sitemap, "utf-8");
9837
+ if (sitemap && !existsSync11(join23(distDir, "sitemap.xml"))) {
9838
+ await writeFile6(join23(distDir, "sitemap.xml"), sitemap, "utf-8");
9421
9839
  logger.success("Generated sitemap.xml");
9422
9840
  }
9423
9841
  const robots = buildRobots(project);
9424
- if (robots && !existsSync9(join21(distDir, "robots.txt"))) {
9425
- await writeFile6(join21(distDir, "robots.txt"), robots, "utf-8");
9842
+ if (robots && !existsSync11(join23(distDir, "robots.txt"))) {
9843
+ await writeFile6(join23(distDir, "robots.txt"), robots, "utf-8");
9426
9844
  logger.success("Generated robots.txt");
9427
9845
  }
9428
9846
  const { config } = project;
@@ -9590,39 +10008,47 @@ var doctorCommand = defineCommand4({
9590
10008
  });
9591
10009
 
9592
10010
  // src/cli/commands/eject.ts
9593
- import { readFile as readFile13, writeFile as writeFile8 } from "node:fs/promises";
10011
+ import { readFile as readFile15, writeFile as writeFile8 } from "node:fs/promises";
9594
10012
  import { defineCommand as defineCommand5 } from "citty";
9595
- import { join as join23, relative as relative13 } from "pathe";
10013
+ import { join as join25, relative as relative14 } from "pathe";
9596
10014
 
9597
10015
  // src/registry/eject.ts
9598
- import { existsSync as existsSync10 } from "node:fs";
9599
- import { cp, mkdir as mkdir5, readFile as readFile12, rm as rm2, writeFile as writeFile7 } from "node:fs/promises";
9600
- import { join as join22, relative as relative12 } from "pathe";
10016
+ import { existsSync as existsSync12 } from "node:fs";
10017
+ import { cp, mkdir as mkdir5, readFile as readFile14, rm as rm2, writeFile as writeFile7 } from "node:fs/promises";
10018
+ import { join as join24, relative as relative13 } from "pathe";
9601
10019
  var POSIX = (path) => path.split("\\").join("/");
9602
10020
  var eject = async (root) => {
9603
10021
  const project = await scanProject(root, { mode: "build" });
9604
10022
  const { context, config } = project;
9605
- const srcDir = join22(root, "src");
9606
- const genDir = join22(srcDir, "generated");
10023
+ const srcDir = join24(root, "src");
10024
+ const genDir = join24(srcDir, "generated");
9607
10025
  const askEnabled = config.ai.ask?.enabled ?? false;
9608
10026
  const exportPdf = config.export.pdf;
9609
10027
  const exportEpub = config.export.epub;
9610
- const [pages, needsReactRaw, userTheme, rawMarkdown] = await Promise.all([
10028
+ const [pages, needsReactRaw, userTheme, rawMarkdown, islands, examples] = await Promise.all([
9611
10029
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
9612
10030
  detectNeedsReact(root),
9613
- context.themeFile ? readFile12(context.themeFile, "utf-8") : Promise.resolve(""),
9614
- buildRawMarkdown(project)
10031
+ context.themeFile ? readFile14(context.themeFile, "utf-8") : Promise.resolve(""),
10032
+ buildRawMarkdown(project),
10033
+ discoverIslands(root),
10034
+ discoverExamples(root)
10035
+ ]);
10036
+ const frameworks = new Set([
10037
+ ...islands.islands.map((island) => island.framework),
10038
+ ...examples.examples.map((example) => example.framework)
9615
10039
  ]);
9616
- const needsReact = needsReactRaw || askEnabled;
10040
+ const needsReact = needsReactRaw || askEnabled || frameworks.has("react");
10041
+ const needsVue = frameworks.has("vue");
10042
+ const needsSvelte = frameworks.has("svelte");
9617
10043
  const relContext = {
9618
10044
  ...context,
9619
- contentRoot: POSIX(relative12(root, context.contentRoot)),
10045
+ contentRoot: POSIX(relative13(root, context.contentRoot)),
9620
10046
  outDir: ".",
9621
10047
  root: "."
9622
10048
  };
9623
- const componentsImport = context.componentsFile ? `../../${POSIX(relative12(root, context.componentsFile))}` : null;
10049
+ const componentsImport = context.componentsFile ? `../../${POSIX(relative13(root, context.componentsFile))}` : null;
9624
10050
  const relPages = pages.map((page) => ({
9625
- entrypoint: POSIX(relative12(root, page.entrypoint)),
10051
+ entrypoint: POSIX(relative13(root, page.entrypoint)),
9626
10052
  pattern: page.pattern
9627
10053
  }));
9628
10054
  const staged = collectStaged(project);
@@ -9635,18 +10061,21 @@ var eject = async (root) => {
9635
10061
  contentRoutes: project.manifest.routes.map((route) => route.path),
9636
10062
  context: relContext,
9637
10063
  dataPath: "./src/generated/data.json",
10064
+ examplesPath: "./src/generated/examples.ts",
9638
10065
  needsReact,
10066
+ needsSvelte,
10067
+ needsVue,
9639
10068
  pages: relPages,
9640
10069
  searchClientPath: "./src/generated/search-client.ts",
9641
10070
  themePath: "./src/generated/app.css"
9642
10071
  }),
9643
- path: join22(root, "astro.config.mjs")
10072
+ path: join24(root, "astro.config.mjs")
9644
10073
  },
9645
10074
  {
9646
10075
  content: runtimeTsconfigTemplate(),
9647
- path: join22(root, "tsconfig.json")
10076
+ path: join24(root, "tsconfig.json")
9648
10077
  },
9649
- { content: envTemplate(), path: join22(srcDir, "env.d.ts") },
10078
+ { content: envTemplate(), path: join24(srcDir, "env.d.ts") },
9650
10079
  {
9651
10080
  content: contentConfigTemplate({
9652
10081
  config,
@@ -9654,7 +10083,7 @@ var eject = async (root) => {
9654
10083
  staged: hasStaged,
9655
10084
  stagedBase: stagedDir
9656
10085
  }),
9657
- path: join22(srcDir, "content.config.ts")
10086
+ path: join24(srcDir, "content.config.ts")
9658
10087
  },
9659
10088
  {
9660
10089
  content: catchAllPageTemplate({
@@ -9663,11 +10092,19 @@ var eject = async (root) => {
9663
10092
  exportPdf,
9664
10093
  mathEnabled: config.markdown.math
9665
10094
  }),
9666
- path: join22(srcDir, "pages", "[...slug].astro")
10095
+ path: join24(srcDir, "pages", "[...slug].astro")
9667
10096
  },
9668
10097
  {
9669
10098
  content: userComponentsTemplate(componentsImport),
9670
- path: join22(genDir, "components.ts")
10099
+ path: join24(genDir, "components.ts")
10100
+ },
10101
+ {
10102
+ content: islandMapTemplate(islands.islands),
10103
+ path: join24(genDir, "islands.ts")
10104
+ },
10105
+ {
10106
+ content: exampleMapTemplate(examples.examples),
10107
+ path: join24(genDir, "examples.ts")
9671
10108
  },
9672
10109
  {
9673
10110
  content: tailwindEntryTemplate({
@@ -9679,54 +10116,54 @@ var eject = async (root) => {
9679
10116
  twoslashCss: twoslashCss(),
9680
10117
  userTheme
9681
10118
  }),
9682
- path: join22(genDir, "app.css")
10119
+ path: join24(genDir, "app.css")
9683
10120
  },
9684
- { content: buildRuntimeData(project), path: join22(genDir, "data.json") },
10121
+ { content: buildRuntimeData(project), path: join24(genDir, "data.json") },
9685
10122
  {
9686
10123
  content: `${JSON.stringify(rawMarkdown)}
9687
10124
  `,
9688
- path: join22(genDir, "raw-markdown.json")
10125
+ path: join24(genDir, "raw-markdown.json")
9689
10126
  },
9690
10127
  {
9691
10128
  content: rawMarkdownEndpointTemplate(),
9692
- path: join22(srcDir, "pages", "[...slug].md.ts")
10129
+ path: join24(srcDir, "pages", "[...slug].md.ts")
9693
10130
  },
9694
10131
  {
9695
10132
  content: rawMarkdownEndpointTemplate(),
9696
- path: join22(srcDir, "pages", "[...slug].mdx.ts")
10133
+ path: join24(srcDir, "pages", "[...slug].mdx.ts")
9697
10134
  }
9698
10135
  ];
9699
10136
  if (askEnabled) {
9700
10137
  files.push({
9701
10138
  content: askEndpointTemplate(resolveAskBackend(config.ai.ask)),
9702
- path: join22(srcDir, "pages", "api", "ask.ts")
10139
+ path: join24(srcDir, "pages", "api", "ask.ts")
9703
10140
  });
9704
10141
  }
9705
10142
  if (config.seo.og.enabled) {
9706
10143
  files.push({
9707
- content: ogEndpointTemplate(),
9708
- path: join22(srcDir, "pages", "og", "[...slug].png.ts")
10144
+ content: ogEndpointTemplate(customOgRoutes(pages, config.title, config.description)),
10145
+ path: join24(srcDir, "pages", "og", "[...slug].png.ts")
9709
10146
  });
9710
10147
  }
9711
10148
  files.push({
9712
10149
  content: searchClientTemplate(config),
9713
- path: join22(genDir, "search-client.ts")
10150
+ path: join24(genDir, "search-client.ts")
9714
10151
  });
9715
10152
  if (servesStaticIndex(config.search.provider)) {
9716
10153
  const documents = await buildSearchDocuments(project);
9717
10154
  files.push({
9718
10155
  content: `${JSON.stringify(documents)}
9719
10156
  `,
9720
- path: join22(genDir, "search.json")
10157
+ path: join24(genDir, "search.json")
9721
10158
  }, {
9722
10159
  content: searchEndpointTemplate(),
9723
- path: join22(srcDir, "pages", "blume-search.json.ts")
10160
+ path: join24(srcDir, "pages", "blume-search.json.ts")
9724
10161
  });
9725
10162
  }
9726
10163
  if (config.search.provider === "mixedbread") {
9727
10164
  files.push({
9728
10165
  content: mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""),
9729
- path: join22(srcDir, "pages", "api", "search.ts")
10166
+ path: join24(srcDir, "pages", "api", "search.ts")
9730
10167
  });
9731
10168
  }
9732
10169
  const feeds = buildRssFeeds(project);
@@ -9735,10 +10172,10 @@ var eject = async (root) => {
9735
10172
  files.push({
9736
10173
  content: `${JSON.stringify(feedXml)}
9737
10174
  `,
9738
- path: join22(genDir, "rss.json")
10175
+ path: join24(genDir, "rss.json")
9739
10176
  }, {
9740
10177
  content: rssEndpointTemplate(),
9741
- path: join22(srcDir, "pages", "[section]", "rss.xml.ts")
10178
+ path: join24(srcDir, "pages", "[section]", "rss.xml.ts")
9742
10179
  });
9743
10180
  }
9744
10181
  if (hasReferences(config)) {
@@ -9750,20 +10187,27 @@ var eject = async (root) => {
9750
10187
  for (const file of references.files) {
9751
10188
  files.push({
9752
10189
  content: file.content,
9753
- path: join22(srcDir, "pages", file.pagePath)
10190
+ path: join24(srcDir, "pages", file.pagePath)
9754
10191
  });
9755
10192
  }
9756
10193
  }
10194
+ files.push(...islands.islands.map((island) => ({
10195
+ content: islandWrapperTemplate(island),
10196
+ path: join24(genDir, "islands", `${island.name}.astro`)
10197
+ })), ...examples.examples.map((example) => ({
10198
+ content: exampleWrapperTemplate(example),
10199
+ path: join24(genDir, "examples", `${exampleSlug(example.path)}.astro`)
10200
+ })));
9757
10201
  for (const [entryId, content] of staged) {
9758
- files.push({ content, path: join22(root, stagedDir, entryId) });
10202
+ files.push({ content, path: join24(root, stagedDir, entryId) });
9759
10203
  }
9760
10204
  await Promise.all(files.map(async (file) => {
9761
- await mkdir5(join22(file.path, ".."), { recursive: true });
10205
+ await mkdir5(join24(file.path, ".."), { recursive: true });
9762
10206
  await writeFile7(file.path, file.content, "utf-8");
9763
10207
  }));
9764
- const assetsSrc = join22(context.outDir, "public", "blume-assets");
9765
- if (existsSync10(assetsSrc)) {
9766
- await cp(assetsSrc, join22(root, "public", "blume-assets"), {
10208
+ const assetsSrc = join24(context.outDir, "public", "blume-assets");
10209
+ if (existsSync12(assetsSrc)) {
10210
+ await cp(assetsSrc, join24(root, "public", "blume-assets"), {
9767
10211
  recursive: true
9768
10212
  });
9769
10213
  }
@@ -9773,10 +10217,10 @@ var eject = async (root) => {
9773
10217
 
9774
10218
  // src/cli/commands/eject.ts
9775
10219
  var updatePackageScripts = async (root) => {
9776
- const pkgPath = join23(root, "package.json");
10220
+ const pkgPath = join25(root, "package.json");
9777
10221
  let pkg;
9778
10222
  try {
9779
- pkg = JSON.parse(await readFile13(pkgPath, "utf-8"));
10223
+ pkg = JSON.parse(await readFile15(pkgPath, "utf-8"));
9780
10224
  } catch {
9781
10225
  return;
9782
10226
  }
@@ -9809,7 +10253,7 @@ var ejectCommand = defineCommand5({
9809
10253
  await updatePackageScripts(root);
9810
10254
  logger.success(`Ejected ${files.length} file(s):`);
9811
10255
  for (const file of files) {
9812
- process.stdout.write(` ${relative13(root, file)}
10256
+ process.stdout.write(` ${relative14(root, file)}
9813
10257
  `);
9814
10258
  }
9815
10259
  logger.box(`Your project is now a standalone Astro app.
@@ -9822,10 +10266,10 @@ The blume package remains importable.`);
9822
10266
  });
9823
10267
 
9824
10268
  // src/cli/commands/init.ts
9825
- import { existsSync as existsSync11 } from "node:fs";
10269
+ import { existsSync as existsSync13 } from "node:fs";
9826
10270
  import { mkdir as mkdir6, writeFile as writeFile9 } from "node:fs/promises";
9827
10271
  import { defineCommand as defineCommand6 } from "citty";
9828
- import { basename as basename3, dirname as dirname10, join as join24 } from "pathe";
10272
+ import { basename as basename3, dirname as dirname11, join as join26 } from "pathe";
9829
10273
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
9830
10274
  var packageTemplate = (name, version) => `{
9831
10275
  "name": ${JSON.stringify(name)},
@@ -9860,11 +10304,11 @@ Welcome to **Blume** — markdown-first docs powered by Astro and Vite.
9860
10304
  Edit \`docs/index.mdx\` to get started, then run \`blume dev\`.
9861
10305
  `;
9862
10306
  var writeFileSafe = async (path, content) => {
9863
- if (existsSync11(path)) {
10307
+ if (existsSync13(path)) {
9864
10308
  logger.info(`Skipped existing ${path}`);
9865
10309
  return false;
9866
10310
  }
9867
- await mkdir6(dirname10(path), { recursive: true });
10311
+ await mkdir6(dirname11(path), { recursive: true });
9868
10312
  await writeFile9(path, content, "utf-8");
9869
10313
  logger.success(`Created ${path}`);
9870
10314
  return true;
@@ -9885,9 +10329,9 @@ var initCommand = defineCommand6({
9885
10329
  async run({ args }) {
9886
10330
  const root = process.cwd();
9887
10331
  const contentDir = args["content-dir"] ?? "docs";
9888
- const createdPackage = await writeFileSafe(join24(root, "package.json"), packageTemplate(toPackageName(basename3(root)), getBlumeVersion()));
9889
- await writeFileSafe(join24(root, "blume.config.ts"), CONFIG_TEMPLATE);
9890
- await writeFileSafe(join24(root, contentDir, "index.mdx"), INDEX_TEMPLATE);
10332
+ const createdPackage = await writeFileSafe(join26(root, "package.json"), packageTemplate(toPackageName(basename3(root)), getBlumeVersion()));
10333
+ await writeFileSafe(join26(root, "blume.config.ts"), CONFIG_TEMPLATE);
10334
+ await writeFileSafe(join26(root, contentDir, "index.mdx"), INDEX_TEMPLATE);
9891
10335
  const nextSteps = createdPackage ? `Next steps:
9892
10336
 
9893
10337
  npm install
@@ -9904,16 +10348,15 @@ var initCommand = defineCommand6({
9904
10348
  import { defineCommand as defineCommand7 } from "citty";
9905
10349
 
9906
10350
  // src/migrate/fumadocs/index.ts
9907
- import { existsSync as existsSync14 } from "node:fs";
9908
- import { mkdir as mkdir7, readFile as readFile15, rename as rename2, rm as rm3, writeFile as writeFile10 } from "node:fs/promises";
9909
- import matter11 from "gray-matter";
9910
- import { dirname as dirname12, join as join26, relative as relative14 } from "pathe";
9911
- import { glob as glob7 } from "tinyglobby";
10351
+ import { existsSync as existsSync17 } from "node:fs";
10352
+ import { mkdir as mkdir8, readFile as readFile17, rename as rename3, rm as rm3, writeFile as writeFile11 } from "node:fs/promises";
10353
+ import { dirname as dirname14, join as join29, relative as relative15 } from "pathe";
10354
+ import { glob as glob8 } from "tinyglobby";
9912
10355
 
9913
10356
  // src/migrate/fumadocs/config.ts
9914
- import { existsSync as existsSync12 } from "node:fs";
9915
- import { readFile as readFile14 } from "node:fs/promises";
9916
- import { join as join25 } from "pathe";
10357
+ import { existsSync as existsSync14 } from "node:fs";
10358
+ import { readFile as readFile16 } from "node:fs/promises";
10359
+ import { basename as basename4, dirname as dirname12, join as join27 } from "pathe";
9917
10360
  var SOURCE_FILES = [
9918
10361
  "lib/source.ts",
9919
10362
  "app/source.ts",
@@ -9930,25 +10373,62 @@ var prettifyTitle = (name) => {
9930
10373
  }
9931
10374
  return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
9932
10375
  };
10376
+ var GENERIC_NAMES = new Set([
10377
+ "api",
10378
+ "app",
10379
+ "client",
10380
+ "frontend",
10381
+ "server",
10382
+ "site",
10383
+ "web",
10384
+ "www"
10385
+ ]);
10386
+ var gitRepoRoot = (start) => {
10387
+ let dir = start;
10388
+ for (;; ) {
10389
+ if (existsSync14(join27(dir, ".git"))) {
10390
+ return dir;
10391
+ }
10392
+ const parent = dirname12(dir);
10393
+ if (parent === dir) {
10394
+ return null;
10395
+ }
10396
+ dir = parent;
10397
+ }
10398
+ };
10399
+ var bareName = (name) => name.includes("/") ? name.slice(name.lastIndexOf("/") + 1) : name;
9933
10400
  var readTitle = async (root) => {
9934
- const packageJson = join25(root, "package.json");
9935
- if (!existsSync12(packageJson)) {
10401
+ const packageJson = join27(root, "package.json");
10402
+ if (!existsSync14(packageJson)) {
9936
10403
  return "Documentation";
9937
10404
  }
9938
10405
  try {
9939
- const parsed = JSON.parse(await readFile14(packageJson, "utf-8"));
9940
- return typeof parsed.name === "string" && parsed.name.trim() ? prettifyTitle(parsed.name) : "Documentation";
10406
+ const parsed = JSON.parse(await readFile16(packageJson, "utf-8"));
10407
+ const { name } = parsed;
10408
+ if (typeof name !== "string" || !name.trim()) {
10409
+ return "Documentation";
10410
+ }
10411
+ if (GENERIC_NAMES.has(bareName(name).toLowerCase())) {
10412
+ const repoRoot = gitRepoRoot(root);
10413
+ if (repoRoot && repoRoot !== root) {
10414
+ const repoTitle = prettifyTitle(basename4(repoRoot));
10415
+ if (repoTitle !== "Documentation") {
10416
+ return repoTitle;
10417
+ }
10418
+ }
10419
+ }
10420
+ return prettifyTitle(name);
9941
10421
  } catch {
9942
10422
  return "Documentation";
9943
10423
  }
9944
10424
  };
9945
10425
  var scrapeBaseUrl = async (root) => {
9946
10426
  for (const candidate of SOURCE_FILES) {
9947
- const file = join25(root, candidate);
9948
- if (!existsSync12(file)) {
10427
+ const file = join27(root, candidate);
10428
+ if (!existsSync14(file)) {
9949
10429
  continue;
9950
10430
  }
9951
- const base = BASE_URL.exec(await readFile14(file, "utf-8"))?.groups?.base;
10431
+ const base = BASE_URL.exec(await readFile16(file, "utf-8"))?.groups?.base;
9952
10432
  if (base) {
9953
10433
  return base;
9954
10434
  }
@@ -9977,10 +10457,9 @@ var loadFumadocsConfig = async (root) => {
9977
10457
  };
9978
10458
 
9979
10459
  // src/migrate/fumadocs/content.ts
9980
- import { existsSync as existsSync13 } from "node:fs";
10460
+ import { existsSync as existsSync15 } from "node:fs";
9981
10461
  import { readFile as readFileFromDisk2 } from "node:fs/promises";
9982
- import matter10 from "gray-matter";
9983
- import { dirname as dirname11, resolve as resolve7 } from "pathe";
10462
+ import { dirname as dirname13, resolve as resolve8 } from "pathe";
9984
10463
  var FUMADOCS_IMPORT = /^import\s+[\s\S]*?\s+from\s+["']fumadocs-(?:ui|core|mdx)(?:\/[^"']*)?["'];?[ \t]*\n?/gmu;
9985
10464
  var stripFumadocsImports = (source) => {
9986
10465
  const stripped = source.replace(FUMADOCS_IMPORT, "");
@@ -10181,18 +10660,18 @@ var inlineFumadocsIncludes = async (source, options) => {
10181
10660
  if (!rawPath) {
10182
10661
  continue;
10183
10662
  }
10184
- const target = resolve7(dirname11(options.filePath), rawPath);
10663
+ const target = resolve8(dirname13(options.filePath), rawPath);
10185
10664
  if (seen.has(target)) {
10186
10665
  warnings.push(`Circular <include> "${rawPath}" — left as-is.`);
10187
10666
  continue;
10188
10667
  }
10189
- if (!existsSync13(target)) {
10668
+ if (!existsSync15(target)) {
10190
10669
  warnings.push(`<include> target "${rawPath}" not found — left as-is.`);
10191
10670
  continue;
10192
10671
  }
10193
10672
  seen.add(target);
10194
10673
  const raw = await read(target);
10195
- const nested = await inlineFumadocsIncludes(matter10(raw).content.trim(), {
10674
+ const nested = await inlineFumadocsIncludes(frontmatter_default(raw).content.trim(), {
10196
10675
  ...options,
10197
10676
  filePath: target,
10198
10677
  seen
@@ -10217,10 +10696,16 @@ var normalizeFumadocsPageMeta = (value) => {
10217
10696
  return stripUnknownPageMeta(data);
10218
10697
  };
10219
10698
 
10699
+ // src/migrate/fumadocs/groups.ts
10700
+ import { existsSync as existsSync16, statSync as statSync2 } from "node:fs";
10701
+ import { mkdir as mkdir7, rename as rename2, writeFile as writeFile10 } from "node:fs/promises";
10702
+ import { basename as basename5, join as join28 } from "pathe";
10703
+
10220
10704
  // src/migrate/fumadocs/meta.ts
10221
10705
  var SEPARATOR = /^---(?<label>.*)---$/u;
10222
10706
  var LINK2 = /^(?:\[[^\]]*\])?\[(?<text>[^\]]*)\]\((?<href>[^)]*)\)$/u;
10223
10707
  var REST = "...";
10708
+ var EXTRACT = /^\.\.\.(?<folder>.+)$/u;
10224
10709
  var filterPages = (raw) => {
10225
10710
  if (!Array.isArray(raw)) {
10226
10711
  return { pages: [], warnings: [] };
@@ -10235,6 +10720,15 @@ var filterPages = (raw) => {
10235
10720
  if (value === REST || value === "") {
10236
10721
  continue;
10237
10722
  }
10723
+ const extract = EXTRACT.exec(value);
10724
+ if (extract) {
10725
+ const folder = extract.groups?.folder?.trim() ?? "";
10726
+ if (folder) {
10727
+ pages.push(folder);
10728
+ warnings.push(`Sidebar extract "...${folder}" became a normal "${folder}" group (its pages are not flattened into the parent).`);
10729
+ }
10730
+ continue;
10731
+ }
10238
10732
  const separator = SEPARATOR.exec(value);
10239
10733
  if (separator) {
10240
10734
  const label = separator.groups?.label?.trim();
@@ -10250,7 +10744,11 @@ var filterPages = (raw) => {
10250
10744
  }
10251
10745
  return { pages, warnings };
10252
10746
  };
10253
- var translateFumadocsMeta = (value) => {
10747
+ var renderMetaModule = (meta) => `import { defineMeta } from "blume";
10748
+
10749
+ export default defineMeta(${JSON.stringify(meta, null, 2)});
10750
+ `;
10751
+ var translateFumadocsSelfMeta = (value) => {
10254
10752
  if (!value || typeof value !== "object" || Array.isArray(value)) {
10255
10753
  return { meta: {}, warnings: [] };
10256
10754
  }
@@ -10272,23 +10770,197 @@ var translateFumadocsMeta = (value) => {
10272
10770
  if (typeof source.description === "string") {
10273
10771
  warnings.push("Dropped folder `description` — Blume folders have no description field.");
10274
10772
  }
10275
- const { pages, warnings: pageWarnings } = filterPages(source.pages);
10276
- if (pages.length > 0) {
10277
- meta.pages = pages;
10773
+ return { meta, warnings };
10774
+ };
10775
+ var translateFumadocsMeta = (value) => {
10776
+ const { meta, warnings } = translateFumadocsSelfMeta(value);
10777
+ if (value && typeof value === "object" && !Array.isArray(value)) {
10778
+ const { pages, warnings: pageWarnings } = filterPages(value.pages);
10779
+ if (pages.length > 0) {
10780
+ meta.pages = pages;
10781
+ }
10782
+ warnings.push(...pageWarnings);
10278
10783
  }
10279
- warnings.push(...pageWarnings);
10280
10784
  return { meta, warnings };
10281
10785
  };
10786
+ var parseFumadocsPages = (raw) => {
10787
+ const lead = [];
10788
+ const sections = [];
10789
+ if (!Array.isArray(raw)) {
10790
+ return { hasSections: false, lead, sections };
10791
+ }
10792
+ let current = null;
10793
+ const push = (item) => {
10794
+ (current ? current.items : lead).push(item);
10795
+ };
10796
+ for (const entry of raw) {
10797
+ if (typeof entry !== "string") {
10798
+ continue;
10799
+ }
10800
+ const value = entry.trim();
10801
+ if (value === REST || value === "") {
10802
+ continue;
10803
+ }
10804
+ const separator = SEPARATOR.exec(value);
10805
+ if (separator) {
10806
+ current = {
10807
+ items: [],
10808
+ label: separator.groups?.label?.trim() || "Section"
10809
+ };
10810
+ sections.push(current);
10811
+ continue;
10812
+ }
10813
+ const extract = EXTRACT.exec(value);
10814
+ if (extract) {
10815
+ const name = extract.groups?.folder?.trim();
10816
+ if (name) {
10817
+ push({ kind: "extract", name });
10818
+ }
10819
+ continue;
10820
+ }
10821
+ const link = LINK2.exec(value);
10822
+ if (link) {
10823
+ push({
10824
+ href: link.groups?.href ?? "",
10825
+ kind: "link",
10826
+ text: link.groups?.text ?? ""
10827
+ });
10828
+ continue;
10829
+ }
10830
+ push({ kind: "ref", name: value });
10831
+ }
10832
+ return { hasSections: sections.length > 0, lead, sections };
10833
+ };
10834
+
10835
+ // src/migrate/fumadocs/groups.ts
10836
+ var PAGE_EXTS = [".mdx", ".md"];
10837
+ var WORD_SPLIT3 = /[-_]/u;
10838
+ var PATH_SEP = /[/\\]/u;
10839
+ var isDirectory = (path) => {
10840
+ try {
10841
+ return statSync2(path).isDirectory();
10842
+ } catch {
10843
+ return false;
10844
+ }
10845
+ };
10846
+ var resolveEntry = (docsDir, name) => {
10847
+ for (const ext of PAGE_EXTS) {
10848
+ const file = join28(docsDir, `${name}${ext}`);
10849
+ if (existsSync16(file)) {
10850
+ return { kind: "file", path: file };
10851
+ }
10852
+ }
10853
+ const folder = join28(docsDir, name);
10854
+ return isDirectory(folder) ? { kind: "folder", path: folder } : null;
10855
+ };
10856
+ var humanize2 = (name) => name.split(WORD_SPLIT3).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
10857
+ var linkWarning = (item) => `Dropped sidebar link "${item.text}" (${item.href}) — add it to navbar.links manually.`;
10858
+ var extractWarning = (name) => `Sidebar extract "...${name}" became a normal "${name}" group (its pages are not flattened into the parent).`;
10859
+ var addUngroupedItem = (item, order, warnings) => {
10860
+ if (item.kind === "link") {
10861
+ warnings.push(linkWarning(item));
10862
+ return;
10863
+ }
10864
+ if (item.kind === "extract") {
10865
+ warnings.push(extractWarning(item.name));
10866
+ }
10867
+ order.push(item.name);
10868
+ };
10869
+ var reshapeSingleFolderSection = (section, only, order, warnings) => {
10870
+ order.push(only.name);
10871
+ if (humanize2(only.name).toLowerCase() !== section.label.toLowerCase()) {
10872
+ warnings.push(`Section "${section.label}" wraps folder "${only.name}"; set that folder's meta title to "${section.label}" to make the heading match.`);
10873
+ }
10874
+ if (only.kind === "extract") {
10875
+ warnings.push(extractWarning(only.name));
10876
+ }
10877
+ };
10878
+ var moveItemIntoGroup = async (item, docsDir, groupDir, label, warnings) => {
10879
+ if (item.kind === "link") {
10880
+ warnings.push(linkWarning(item));
10881
+ return null;
10882
+ }
10883
+ const resolved = resolveEntry(docsDir, item.name);
10884
+ if (!resolved) {
10885
+ warnings.push(`Sidebar entry "${item.name}" in section "${label}" matched no page or folder; skipped.`);
10886
+ return null;
10887
+ }
10888
+ const dest = join28(groupDir, basename5(resolved.path));
10889
+ if (existsSync16(dest)) {
10890
+ warnings.push(`Skipped moving "${item.name}" into section "${label}" (target already exists).`);
10891
+ return null;
10892
+ }
10893
+ await mkdir7(groupDir, { recursive: true });
10894
+ await rename2(resolved.path, dest);
10895
+ if (item.kind === "extract") {
10896
+ warnings.push(extractWarning(item.name));
10897
+ }
10898
+ return item.name;
10899
+ };
10900
+ var reshapeSection = async (section, docsDir, order, warnings) => {
10901
+ const movable = section.items.filter((item) => item.kind !== "link");
10902
+ if (movable.length === 1) {
10903
+ const [only] = movable;
10904
+ if (resolveEntry(docsDir, only.name)?.kind === "folder") {
10905
+ reshapeSingleFolderSection(section, only, order, warnings);
10906
+ return;
10907
+ }
10908
+ }
10909
+ if (PATH_SEP.test(section.label)) {
10910
+ warnings.push(`Section "${section.label}" has a slash in its name and can't become a group folder; its pages stay ungrouped.`);
10911
+ for (const item of section.items) {
10912
+ addUngroupedItem(item, order, warnings);
10913
+ }
10914
+ return;
10915
+ }
10916
+ const groupDir = join28(docsDir, `(${section.label})`);
10917
+ const sectionKeys = [];
10918
+ for (const item of section.items) {
10919
+ const key = await moveItemIntoGroup(item, docsDir, groupDir, section.label, warnings);
10920
+ if (key) {
10921
+ sectionKeys.push(key);
10922
+ }
10923
+ }
10924
+ if (sectionKeys.length === 0) {
10925
+ return;
10926
+ }
10927
+ order.push(section.label);
10928
+ if (sectionKeys.length > 1) {
10929
+ await writeFile10(join28(groupDir, "meta.ts"), renderMetaModule({ pages: sectionKeys }), "utf-8");
10930
+ }
10931
+ };
10932
+ var reshapeFumadocsGroups = async (structure, docsDir) => {
10933
+ const order = [];
10934
+ const warnings = [];
10935
+ for (const item of structure.lead) {
10936
+ addUngroupedItem(item, order, warnings);
10937
+ }
10938
+ for (const section of structure.sections) {
10939
+ await reshapeSection(section, docsDir, order, warnings);
10940
+ }
10941
+ return { order, warnings };
10942
+ };
10282
10943
 
10283
10944
  // src/migrate/fumadocs/index.ts
10284
10945
  var SOURCE_DIR = "content/docs";
10285
10946
  var PAGE_GLOB = "**/*.{md,mdx}";
10286
10947
  var META_GLOB = "**/meta.json";
10287
10948
  var IGNORE = ["**/node_modules/**"];
10949
+ var FUMADOCS_LEFTOVERS = [
10950
+ "next.config.ts",
10951
+ "next.config.mjs",
10952
+ "next.config.js",
10953
+ "source.config.ts",
10954
+ "source.config.mjs",
10955
+ "source.config.js",
10956
+ "mdx-components.tsx",
10957
+ "next-env.d.ts",
10958
+ "app"
10959
+ ];
10288
10960
  var movePage = async (abs, base, root) => {
10289
- const rel = relative14(base, abs);
10290
- const dest = join26(root, "docs", rel);
10291
- if (existsSync14(dest)) {
10961
+ const rel = relative15(base, abs);
10962
+ const dest = join29(root, "docs", rel);
10963
+ if (existsSync17(dest)) {
10292
10964
  return {
10293
10965
  includeWarnings: [],
10294
10966
  moved: 0,
@@ -10297,18 +10969,18 @@ var movePage = async (abs, base, root) => {
10297
10969
  unsupported: []
10298
10970
  };
10299
10971
  }
10300
- const raw = await readFile15(abs, "utf-8");
10972
+ const raw = await readFile17(abs, "utf-8");
10301
10973
  const included = await inlineFumadocsIncludes(raw, { filePath: abs });
10302
10974
  let text = stripFumadocsImports(included.content);
10303
10975
  text = rewriteFumadocsCallouts(text);
10304
10976
  text = rewriteFumadocsContainers(text);
10305
10977
  text = rewriteFumadocsTabs(text);
10306
10978
  const unsupported = unsupportedFumadocsComponents(text);
10307
- const parsed = matter11(text);
10979
+ const parsed = frontmatter_default(text);
10308
10980
  const { data, removed } = normalizeFumadocsPageMeta(parsed.data);
10309
- const content = Object.keys(data).length > 0 ? matter11.stringify(parsed.content, data) : parsed.content;
10310
- await mkdir7(dirname12(dest), { recursive: true });
10311
- await writeFile10(dest, content, "utf-8");
10981
+ const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
10982
+ await mkdir8(dirname14(dest), { recursive: true });
10983
+ await writeFile11(dest, content, "utf-8");
10312
10984
  await rm3(abs, { force: true });
10313
10985
  return {
10314
10986
  includeWarnings: included.warnings,
@@ -10318,39 +10990,56 @@ var movePage = async (abs, base, root) => {
10318
10990
  unsupported
10319
10991
  };
10320
10992
  };
10993
+ var writeMeta = async (dest, meta, rel, warnings) => {
10994
+ if (Object.keys(meta).length === 0) {
10995
+ return warnings;
10996
+ }
10997
+ if (existsSync17(dest)) {
10998
+ return [...warnings, `Skipped ${rel} (target already exists)`];
10999
+ }
11000
+ await mkdir8(dirname14(dest), { recursive: true });
11001
+ await writeFile11(dest, renderMetaModule(meta), "utf-8");
11002
+ return warnings;
11003
+ };
10321
11004
  var convertMeta = async (abs, base, root) => {
10322
- const rel = relative14(base, abs);
10323
- const raw = await readFile15(abs, "utf-8");
11005
+ const rel = relative15(base, abs);
11006
+ const raw = await readFile17(abs, "utf-8");
10324
11007
  let parsed;
10325
11008
  try {
10326
11009
  parsed = JSON.parse(raw);
10327
11010
  } catch {
10328
- const dest = join26(root, "docs", rel);
10329
- if (existsSync14(dest)) {
11011
+ const dest2 = join29(root, "docs", rel);
11012
+ if (existsSync17(dest2)) {
10330
11013
  return [`Skipped ${rel} (target already exists)`];
10331
11014
  }
10332
- await mkdir7(dirname12(dest), { recursive: true });
10333
- await rename2(abs, dest);
11015
+ await mkdir8(dirname14(dest2), { recursive: true });
11016
+ await rename3(abs, dest2);
10334
11017
  return [
10335
11018
  `Could not parse ${rel}; moved as-is — convert it to meta.ts by hand.`
10336
11019
  ];
10337
11020
  }
10338
- const { meta, warnings } = translateFumadocsMeta(parsed);
10339
- const dir = dirname12(rel) === "." ? "" : dirname12(rel);
10340
- if (Object.keys(meta).length > 0) {
10341
- const dest = join26(root, "docs", dir, "meta.ts");
10342
- if (existsSync14(dest)) {
10343
- await rm3(abs, { force: true });
10344
- return [...warnings, `Skipped ${rel} (target already exists)`];
10345
- }
10346
- await mkdir7(dirname12(dest), { recursive: true });
10347
- await writeFile10(dest, `import { defineMeta } from "blume";
10348
-
10349
- export default defineMeta(${JSON.stringify(meta, null, 2)});
10350
- `, "utf-8");
11021
+ const dir = dirname14(rel) === "." ? "" : dirname14(rel);
11022
+ const docsDir = join29(root, "docs", dir);
11023
+ const dest = join29(docsDir, "meta.ts");
11024
+ const structure = parseFumadocsPages(parsed.pages);
11025
+ if (structure.hasSections && !existsSync17(dest)) {
11026
+ const self = translateFumadocsSelfMeta(parsed);
11027
+ const reshape = await reshapeFumadocsGroups(structure, docsDir);
11028
+ const meta2 = { ...self.meta };
11029
+ if (reshape.order.length > 0) {
11030
+ meta2.pages = reshape.order;
11031
+ }
11032
+ const result2 = await writeMeta(dest, meta2, rel, [
11033
+ ...self.warnings,
11034
+ ...reshape.warnings
11035
+ ]);
11036
+ await rm3(abs, { force: true });
11037
+ return result2;
10351
11038
  }
11039
+ const { meta, warnings } = translateFumadocsMeta(parsed);
11040
+ const result = await writeMeta(dest, meta, rel, warnings);
10352
11041
  await rm3(abs, { force: true });
10353
- return warnings;
11042
+ return result;
10354
11043
  };
10355
11044
  var summarizePages = (results) => {
10356
11045
  let moved = 0;
@@ -10380,9 +11069,9 @@ var summarizePages = (results) => {
10380
11069
  };
10381
11070
  };
10382
11071
  var cleanupSourceDirs = async (root) => {
10383
- const docs = join26(root, "content", "docs");
10384
- if (existsSync14(docs)) {
10385
- const remaining = await glob7(["**/*"], { cwd: docs, dot: true });
11072
+ const docs = join29(root, "content", "docs");
11073
+ if (existsSync17(docs)) {
11074
+ const remaining = await glob8(["**/*"], { cwd: docs, dot: true });
10386
11075
  if (remaining.length > 0) {
10387
11076
  return [
10388
11077
  `Kept ${remaining.length} non-page file(s) under content/docs; move them into docs/ manually.`
@@ -10390,9 +11079,9 @@ var cleanupSourceDirs = async (root) => {
10390
11079
  }
10391
11080
  await rm3(docs, { force: true, recursive: true });
10392
11081
  }
10393
- const content = join26(root, "content");
10394
- if (existsSync14(content)) {
10395
- const remaining = await glob7(["**/*"], { cwd: content, dot: true });
11082
+ const content = join29(root, "content");
11083
+ if (existsSync17(content)) {
11084
+ const remaining = await glob8(["**/*"], { cwd: content, dot: true });
10396
11085
  if (remaining.length === 0) {
10397
11086
  await rm3(content, { force: true, recursive: true });
10398
11087
  }
@@ -10401,8 +11090,8 @@ var cleanupSourceDirs = async (root) => {
10401
11090
  };
10402
11091
  var migrateFumadocsProject = async (root) => {
10403
11092
  const { config, warnings: configWarnings } = await loadFumadocsConfig(root);
10404
- const base = join26(root, SOURCE_DIR);
10405
- if (!existsSync14(base)) {
11093
+ const base = join29(root, SOURCE_DIR);
11094
+ if (!existsSync17(base)) {
10406
11095
  await writeBlumeConfig(root, config);
10407
11096
  return {
10408
11097
  moved: 0,
@@ -10411,22 +11100,28 @@ var migrateFumadocsProject = async (root) => {
10411
11100
  ]
10412
11101
  };
10413
11102
  }
10414
- const pageFiles = await glob7([PAGE_GLOB], {
11103
+ const pageFiles = await glob8([PAGE_GLOB], {
10415
11104
  absolute: true,
10416
11105
  cwd: base,
10417
11106
  ignore: IGNORE
10418
11107
  });
10419
- const metaFiles = await glob7([META_GLOB], {
11108
+ const metaFiles = await glob8([META_GLOB], {
10420
11109
  absolute: true,
10421
11110
  cwd: base,
10422
11111
  ignore: IGNORE
10423
11112
  });
10424
11113
  const pageResults = await Promise.all(pageFiles.map((abs) => movePage(abs, base, root)));
10425
11114
  const pages = summarizePages(pageResults);
10426
- const metaResults = await Promise.all(metaFiles.map((abs) => convertMeta(abs, base, root)));
10427
- const metaWarnings = metaResults.flat();
11115
+ const orderedMetas = metaFiles.toSorted((a, b) => b.split("/").length - a.split("/").length);
11116
+ const metaWarnings = [];
11117
+ for (const abs of orderedMetas) {
11118
+ metaWarnings.push(...await convertMeta(abs, base, root));
11119
+ }
10428
11120
  await writeBlumeConfig(root, config);
10429
11121
  const cleanupWarnings = await cleanupSourceDirs(root);
11122
+ const scriptsRewritten = await rewriteFrameworkScripts(root, /\bnext\b/u, /fumadocs/u);
11123
+ const gitignoreAdded = await ensureGitignore(root, [".blume/", "dist/"]);
11124
+ const leftovers = leftoverFiles(root, FUMADOCS_LEFTOVERS);
10430
11125
  const warnings = [
10431
11126
  ...configWarnings,
10432
11127
  ...metaWarnings,
@@ -10440,15 +11135,24 @@ var migrateFumadocsProject = async (root) => {
10440
11135
  if (pages.unsupported.length > 0) {
10441
11136
  warnings.push(`Components without a drop-in Blume equivalent need manual review: ${pages.unsupported.join(", ")}.`);
10442
11137
  }
11138
+ if (scriptsRewritten) {
11139
+ warnings.push("Repointed the dev/build/start scripts at the Blume CLI.");
11140
+ }
11141
+ if (gitignoreAdded.length > 0) {
11142
+ warnings.push(`Added ${gitignoreAdded.join(", ")} to .gitignore.`);
11143
+ }
11144
+ if (leftovers.length > 0) {
11145
+ warnings.push(`Safe to delete once the migration looks right: ${leftovers.join(", ")}. Also drop the \`next\` plugin from tsconfig.json and the \`.next\`/\`.source\` lines from .gitignore.`);
11146
+ }
10443
11147
  warnings.push("Review blume.config.ts and the generated meta.ts files.");
10444
11148
  return { moved: pages.moved, warnings };
10445
11149
  };
10446
11150
 
10447
11151
  // src/migrate/mintlify/index.ts
10448
- import { existsSync as existsSync15 } from "node:fs";
10449
- import { mkdir as mkdir8, readFile as readFile16, rename as rename3, rm as rm4, writeFile as writeFile11 } from "node:fs/promises";
10450
- import { dirname as dirname13, join as join27 } from "pathe";
10451
- import { glob as glob8 } from "tinyglobby";
11152
+ import { existsSync as existsSync18 } from "node:fs";
11153
+ import { mkdir as mkdir9, readFile as readFile18, rename as rename4, rm as rm4, writeFile as writeFile12 } from "node:fs/promises";
11154
+ import { dirname as dirname15, join as join30 } from "pathe";
11155
+ import { glob as glob9 } from "tinyglobby";
10452
11156
  var prune = (value) => {
10453
11157
  if (Array.isArray(value)) {
10454
11158
  return value.map(prune);
@@ -10477,7 +11181,7 @@ var writeBlumeConfig2 = async (root, config) => {
10477
11181
 
10478
11182
  export default defineConfig(${JSON.stringify(prune(config), null, 2)});
10479
11183
  `;
10480
- await writeFile11(join27(root, "blume.config.ts"), body, "utf-8");
11184
+ await writeFile12(join30(root, "blume.config.ts"), body, "utf-8");
10481
11185
  };
10482
11186
  var relocateAssets = async (root, refs) => {
10483
11187
  const segments = new Set;
@@ -10492,30 +11196,30 @@ var relocateAssets = async (root, refs) => {
10492
11196
  }
10493
11197
  const moved = [];
10494
11198
  for (const segment of segments) {
10495
- const source = join27(root, segment);
10496
- if (!existsSync15(source) || segment === "public") {
11199
+ const source = join30(root, segment);
11200
+ if (!existsSync18(source) || segment === "public") {
10497
11201
  continue;
10498
11202
  }
10499
- const dest = join27(root, "public", segment);
10500
- if (existsSync15(dest)) {
11203
+ const dest = join30(root, "public", segment);
11204
+ if (existsSync18(dest)) {
10501
11205
  continue;
10502
11206
  }
10503
- await mkdir8(join27(root, "public"), { recursive: true });
10504
- await rename3(source, dest);
11207
+ await mkdir9(join30(root, "public"), { recursive: true });
11208
+ await rename4(source, dest);
10505
11209
  moved.push(segment);
10506
11210
  }
10507
11211
  return moved;
10508
11212
  };
10509
11213
  var cleanupSnippets = async (root, kept, warnings) => {
10510
- const dir = join27(root, "snippets");
10511
- if (!existsSync15(dir)) {
11214
+ const dir = join30(root, "snippets");
11215
+ if (!existsSync18(dir)) {
10512
11216
  return;
10513
11217
  }
10514
- const markdown = await glob8(["**/*.{md,mdx}"], { absolute: true, cwd: dir });
11218
+ const markdown = await glob9(["**/*.{md,mdx}"], { absolute: true, cwd: dir });
10515
11219
  for (const file of markdown) {
10516
11220
  await rm4(file, { force: true });
10517
11221
  }
10518
- const remaining = await glob8(["**/*"], { cwd: dir, dot: true });
11222
+ const remaining = await glob9(["**/*"], { cwd: dir, dot: true });
10519
11223
  if (remaining.length === 0) {
10520
11224
  await rm4(dir, { force: true, recursive: true });
10521
11225
  warnings.push("Inlined and removed the /snippets directory.");
@@ -10545,11 +11249,11 @@ var assetRefs = (config) => {
10545
11249
  };
10546
11250
  var migrateMintlifyProject = async (root) => {
10547
11251
  const warnings = [];
10548
- const configFile = existsSync15(join27(root, "docs.json")) ? join27(root, "docs.json") : join27(root, "mint.json");
11252
+ const configFile = existsSync18(join30(root, "docs.json")) ? join30(root, "docs.json") : join30(root, "mint.json");
10549
11253
  let config;
10550
- if (existsSync15(configFile)) {
11254
+ if (existsSync18(configFile)) {
10551
11255
  config = await loadMintlifyConfig(root, configFile);
10552
- const spec = JSON.parse(await readFile16(configFile, "utf-8"));
11256
+ const spec = JSON.parse(await readFile18(configFile, "utf-8"));
10553
11257
  const i18n = mintlifyI18n(spec);
10554
11258
  if (i18n) {
10555
11259
  config.i18n = i18n;
@@ -10564,7 +11268,7 @@ var migrateMintlifyProject = async (root) => {
10564
11268
  }
10565
11269
  const variables = config.variables ?? {};
10566
11270
  config.variables = undefined;
10567
- const files = await glob8(["**/*.{md,mdx}"], {
11271
+ const files = await glob9(["**/*.{md,mdx}"], {
10568
11272
  absolute: true,
10569
11273
  cwd: root,
10570
11274
  ignore: [
@@ -10580,15 +11284,15 @@ var migrateMintlifyProject = async (root) => {
10580
11284
  const unsupported = new Set;
10581
11285
  const keptComponents = new Set;
10582
11286
  for (const file of files) {
10583
- const raw = await readFile16(file, "utf-8");
11287
+ const raw = await readFile18(file, "utf-8");
10584
11288
  const result = await transformMintlifyContent(raw, {
10585
11289
  filePath: file,
10586
11290
  root,
10587
11291
  variables
10588
11292
  });
10589
11293
  if (result.content !== raw) {
10590
- await mkdir8(dirname13(file), { recursive: true });
10591
- await writeFile11(file, result.content, "utf-8");
11294
+ await mkdir9(dirname15(file), { recursive: true });
11295
+ await writeFile12(file, result.content, "utf-8");
10592
11296
  }
10593
11297
  for (const key of result.removed) {
10594
11298
  removedKeys.add(key);
@@ -10624,11 +11328,10 @@ var migrateMintlifyProject = async (root) => {
10624
11328
  };
10625
11329
 
10626
11330
  // src/migrate/nextra/index.ts
10627
- import { existsSync as existsSync16 } from "node:fs";
10628
- import { mkdir as mkdir9, readFile as readFile17, rename as rename4, rm as rm5, writeFile as writeFile12 } from "node:fs/promises";
10629
- import matter12 from "gray-matter";
10630
- import { basename as basename4, dirname as dirname14, extname as extname6, join as join28, relative as relative15 } from "pathe";
10631
- import { glob as glob9 } from "tinyglobby";
11331
+ import { existsSync as existsSync19 } from "node:fs";
11332
+ import { mkdir as mkdir10, readFile as readFile19, rename as rename5, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
11333
+ import { basename as basename6, dirname as dirname16, extname as extname6, join as join31, relative as relative16 } from "pathe";
11334
+ import { glob as glob10 } from "tinyglobby";
10632
11335
 
10633
11336
  // src/migrate/nextra/content.ts
10634
11337
  var CALLOUT_TYPE_DIRECTIVES3 = {
@@ -10857,30 +11560,30 @@ var indexFolders = (base, pageFiles, metaFiles) => {
10857
11560
  let current = normalizeDir(dir);
10858
11561
  allDirs.add(current);
10859
11562
  while (current !== "") {
10860
- current = normalizeDir(dirname14(current));
11563
+ current = normalizeDir(dirname16(current));
10861
11564
  allDirs.add(current);
10862
11565
  }
10863
11566
  };
10864
11567
  for (const abs of pageFiles) {
10865
- const rel = relative15(base, abs);
10866
- const dir = normalizeDir(dirname14(rel));
10867
- const slug = basename4(rel).replace(/\.mdx?$/u, "");
11568
+ const rel = relative16(base, abs);
11569
+ const dir = normalizeDir(dirname16(rel));
11570
+ const slug = basename6(rel).replace(/\.mdx?$/u, "");
10868
11571
  const folder = pagesByDir.get(dir) ?? new Map;
10869
11572
  folder.set(slug, abs);
10870
11573
  pagesByDir.set(dir, folder);
10871
11574
  registerDir(dir);
10872
11575
  }
10873
11576
  for (const abs of metaFiles) {
10874
- registerDir(normalizeDir(dirname14(relative15(base, abs))));
11577
+ registerDir(normalizeDir(dirname16(relative16(base, abs))));
10875
11578
  }
10876
11579
  const childDirs = new Map;
10877
11580
  for (const dir of allDirs) {
10878
11581
  if (dir === "") {
10879
11582
  continue;
10880
11583
  }
10881
- const parent = normalizeDir(dirname14(dir));
11584
+ const parent = normalizeDir(dirname16(dir));
10882
11585
  const children = childDirs.get(parent) ?? new Set;
10883
- children.add(basename4(dir));
11586
+ children.add(basename6(dir));
10884
11587
  childDirs.set(parent, children);
10885
11588
  }
10886
11589
  return { childDirs, pagesByDir };
@@ -10896,7 +11599,7 @@ var planMetas = (metas, index) => {
10896
11599
  warnings: []
10897
11600
  };
10898
11601
  for (const meta of metas) {
10899
- const dir = normalizeDir(dirname14(meta.rel));
11602
+ const dir = normalizeDir(dirname16(meta.rel));
10900
11603
  const entries = parseNextraMeta(meta.raw, meta.ext);
10901
11604
  if (!entries) {
10902
11605
  plan.unparseableMetas.push({ abs: meta.abs, rel: meta.rel });
@@ -10909,7 +11612,7 @@ var planMetas = (metas, index) => {
10909
11612
  plan.metaByDir.set(dir, conversion.folderMeta);
10910
11613
  plan.consumedMetas.push(meta.abs);
10911
11614
  for (const [slug, title] of Object.entries(conversion.folderTitles)) {
10912
- plan.folderTitleByDir.set(normalizeDir(join28(dir, slug)), title);
11615
+ plan.folderTitleByDir.set(normalizeDir(join31(dir, slug)), title);
10913
11616
  }
10914
11617
  for (const [slug, label] of Object.entries(conversion.pageLabels)) {
10915
11618
  const pageAbs = index.pagesByDir.get(dir)?.get(slug);
@@ -10933,19 +11636,19 @@ var planMetas = (metas, index) => {
10933
11636
  return plan;
10934
11637
  };
10935
11638
  var movePage2 = async (abs, options) => {
10936
- const rel = relative15(options.base, abs);
10937
- const dest = join28(options.root, "docs", rel);
10938
- if (existsSync16(dest)) {
11639
+ const rel = relative16(options.base, abs);
11640
+ const dest = join31(options.root, "docs", rel);
11641
+ if (existsSync19(dest)) {
10939
11642
  return { moved: 0, removed: [], skipped: rel, unsupported: [] };
10940
11643
  }
10941
- const raw = await readFile17(abs, "utf-8");
11644
+ const raw = await readFile19(abs, "utf-8");
10942
11645
  const text = rewriteNextraCallouts(stripNextraImports(raw));
10943
11646
  const unsupported = unsupportedNextraComponents(text);
10944
- const parsed = matter12(text);
11647
+ const parsed = frontmatter_default(text);
10945
11648
  const { data, removed } = normalizeNextraPageMeta(parsed.data, options.overrides.get(abs) ?? {});
10946
- const content = Object.keys(data).length > 0 ? matter12.stringify(parsed.content, data) : parsed.content;
10947
- await mkdir9(dirname14(dest), { recursive: true });
10948
- await writeFile12(dest, content, "utf-8");
11649
+ const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
11650
+ await mkdir10(dirname16(dest), { recursive: true });
11651
+ await writeFile13(dest, content, "utf-8");
10949
11652
  await rm5(abs, { force: true });
10950
11653
  return { moved: 1, removed, skipped: null, unsupported };
10951
11654
  };
@@ -10987,9 +11690,9 @@ var writeFolderMetas = async (root, plan) => {
10987
11690
  if (Object.keys(finalMeta).length === 0) {
10988
11691
  return;
10989
11692
  }
10990
- const dest = join28(root, "docs", dir, "meta.ts");
10991
- await mkdir9(dirname14(dest), { recursive: true });
10992
- await writeFile12(dest, `import { defineMeta } from "blume";
11693
+ const dest = join31(root, "docs", dir, "meta.ts");
11694
+ await mkdir10(dirname16(dest), { recursive: true });
11695
+ await writeFile13(dest, `import { defineMeta } from "blume";
10993
11696
 
10994
11697
  export default defineMeta(${JSON.stringify(finalMeta, null, 2)});
10995
11698
  `, "utf-8");
@@ -10998,20 +11701,20 @@ export default defineMeta(${JSON.stringify(finalMeta, null, 2)});
10998
11701
  var relocateUnparseableMetas = async (root, metas) => {
10999
11702
  const warnings = [];
11000
11703
  await Promise.all(metas.map(async ({ abs, rel }) => {
11001
- const dest = join28(root, "docs", rel);
11002
- if (existsSync16(dest)) {
11704
+ const dest = join31(root, "docs", rel);
11705
+ if (existsSync19(dest)) {
11003
11706
  warnings.push(`Skipped ${rel} (target already exists)`);
11004
11707
  return;
11005
11708
  }
11006
- await mkdir9(dirname14(dest), { recursive: true });
11007
- await rename4(abs, dest);
11709
+ await mkdir10(dirname16(dest), { recursive: true });
11710
+ await rename5(abs, dest);
11008
11711
  warnings.push(`Could not parse ${rel}; moved as-is — convert it to meta.ts by hand.`);
11009
11712
  }));
11010
11713
  return warnings;
11011
11714
  };
11012
11715
  var buildConfig = (tabs) => tabs.length > 0 ? { navigation: { tabs }, title: "Documentation" } : { title: "Documentation" };
11013
11716
  var migrateNextraProject = async (root) => {
11014
- const sourceDir = SOURCE_DIRS.find((dir) => existsSync16(join28(root, dir)));
11717
+ const sourceDir = SOURCE_DIRS.find((dir) => existsSync19(join31(root, dir)));
11015
11718
  if (!sourceDir) {
11016
11719
  await writeBlumeConfig(root, { title: "Documentation" });
11017
11720
  return {
@@ -11021,13 +11724,13 @@ var migrateNextraProject = async (root) => {
11021
11724
  ]
11022
11725
  };
11023
11726
  }
11024
- const base = join28(root, sourceDir);
11025
- const pageFiles = await glob9([PAGE_GLOB2], {
11727
+ const base = join31(root, sourceDir);
11728
+ const pageFiles = await glob10([PAGE_GLOB2], {
11026
11729
  absolute: true,
11027
11730
  cwd: base,
11028
11731
  ignore: IGNORE2
11029
11732
  });
11030
- const metaFiles = await glob9([META_GLOB2], {
11733
+ const metaFiles = await glob10([META_GLOB2], {
11031
11734
  absolute: true,
11032
11735
  cwd: base,
11033
11736
  ignore: IGNORE2
@@ -11036,8 +11739,8 @@ var migrateNextraProject = async (root) => {
11036
11739
  const metas = await Promise.all(metaFiles.map(async (abs) => ({
11037
11740
  abs,
11038
11741
  ext: extname6(abs),
11039
- raw: await readFile17(abs, "utf-8"),
11040
- rel: relative15(base, abs)
11742
+ raw: await readFile19(abs, "utf-8"),
11743
+ rel: relative16(base, abs)
11041
11744
  })));
11042
11745
  const plan = planMetas(metas, index);
11043
11746
  const moves = await Promise.all(pageFiles.map((abs) => movePage2(abs, { base, overrides: plan.pageOverrides, root })));
@@ -11062,16 +11765,15 @@ var migrateNextraProject = async (root) => {
11062
11765
  };
11063
11766
 
11064
11767
  // src/migrate/starlight/index.ts
11065
- import { existsSync as existsSync18 } from "node:fs";
11066
- import { readFile as readFile19, writeFile as writeFile13 } from "node:fs/promises";
11067
- import matter13 from "gray-matter";
11068
- import { join as join30 } from "pathe";
11069
- import { glob as glob10 } from "tinyglobby";
11768
+ import { existsSync as existsSync21 } from "node:fs";
11769
+ import { readFile as readFile21, writeFile as writeFile14 } from "node:fs/promises";
11770
+ import { join as join33 } from "pathe";
11771
+ import { glob as glob11 } from "tinyglobby";
11070
11772
 
11071
11773
  // src/migrate/starlight/config.ts
11072
- import { existsSync as existsSync17 } from "node:fs";
11073
- import { readFile as readFile18 } from "node:fs/promises";
11074
- import { join as join29 } from "pathe";
11774
+ import { existsSync as existsSync20 } from "node:fs";
11775
+ import { readFile as readFile20 } from "node:fs/promises";
11776
+ import { join as join32 } from "pathe";
11075
11777
  var CONFIG_FILES = [
11076
11778
  "astro.config.mjs",
11077
11779
  "astro.config.mts",
@@ -11102,7 +11804,7 @@ var extractStarlightOptions = (source) => {
11102
11804
  return isLiteralObject(parsed) ? parsed : "unparseable";
11103
11805
  };
11104
11806
  var loadStarlightConfig = async (root) => {
11105
- const file = CONFIG_FILES.map((name) => join29(root, name)).find((path) => existsSync17(path));
11807
+ const file = CONFIG_FILES.map((name) => join32(root, name)).find((path) => existsSync20(path));
11106
11808
  if (!file) {
11107
11809
  return {
11108
11810
  options: {},
@@ -11111,7 +11813,7 @@ var loadStarlightConfig = async (root) => {
11111
11813
  ]
11112
11814
  };
11113
11815
  }
11114
- const source = await readFile18(file, "utf-8");
11816
+ const source = await readFile20(file, "utf-8");
11115
11817
  const result = extractStarlightOptions(source);
11116
11818
  if (result === "missing") {
11117
11819
  return {
@@ -11512,17 +12214,17 @@ var starlightI18n = (options) => {
11512
12214
  // src/migrate/starlight/index.ts
11513
12215
  var CONTENT_DIR = "src/content/docs";
11514
12216
  var transformPage = async (file) => {
11515
- const raw = await readFile19(file, "utf-8");
12217
+ const raw = await readFile21(file, "utf-8");
11516
12218
  let text = stripStarlightImports(raw);
11517
12219
  text = rewriteStarlightAsides(text);
11518
12220
  text = rewriteStarlightComponents(text);
11519
12221
  const unsupported = unsupportedStarlightComponents(text);
11520
12222
  const aliased = hasAliasedAssets(text);
11521
- const parsed = matter13(text);
12223
+ const parsed = frontmatter_default(text);
11522
12224
  const { data, removed } = normalizeStarlightPageMeta(parsed.data);
11523
- const content = Object.keys(data).length > 0 ? matter13.stringify(parsed.content, data) : parsed.content;
12225
+ const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
11524
12226
  if (content !== raw) {
11525
- await writeFile13(file, content, "utf-8");
12227
+ await writeFile14(file, content, "utf-8");
11526
12228
  }
11527
12229
  return { aliased, removed, unsupported };
11528
12230
  };
@@ -11534,8 +12236,8 @@ var migrateStarlightProject = async (root) => {
11534
12236
  config.i18n = i18n;
11535
12237
  warnings.push(`Mapped ${i18n.locales.length} locale(s) to i18n (default: ${i18n.defaultLocale}); review the locale labels.`);
11536
12238
  }
11537
- const base = join30(root, CONTENT_DIR);
11538
- if (!existsSync18(base)) {
12239
+ const base = join33(root, CONTENT_DIR);
12240
+ if (!existsSync21(base)) {
11539
12241
  await writeBlumeConfig(root, config);
11540
12242
  return {
11541
12243
  moved: 0,
@@ -11545,7 +12247,7 @@ var migrateStarlightProject = async (root) => {
11545
12247
  ]
11546
12248
  };
11547
12249
  }
11548
- const files = await glob10(["**/*.{md,mdx}"], { absolute: true, cwd: base });
12250
+ const files = await glob11(["**/*.{md,mdx}"], { absolute: true, cwd: base });
11549
12251
  const results = await Promise.all(files.map(transformPage));
11550
12252
  await writeBlumeConfig(root, config);
11551
12253
  const removedKeys = new Set;
@@ -11623,10 +12325,10 @@ var migrateCommand = defineCommand7({
11623
12325
  });
11624
12326
 
11625
12327
  // src/cli/commands/preview.ts
11626
- import { existsSync as existsSync19 } from "node:fs";
12328
+ import { existsSync as existsSync22 } from "node:fs";
11627
12329
  import { preview } from "astro";
11628
12330
  import { defineCommand as defineCommand8 } from "citty";
11629
- import { join as join31 } from "pathe";
12331
+ import { join as join34 } from "pathe";
11630
12332
  var previewCommand = defineCommand8({
11631
12333
  args: {
11632
12334
  host: { description: "Network host to bind.", type: "string" },
@@ -11640,7 +12342,7 @@ var previewCommand = defineCommand8({
11640
12342
  const root = process.cwd();
11641
12343
  const { config } = await loadConfig(root);
11642
12344
  const context = resolveProjectContext(root, config);
11643
- if (!existsSync19(join31(context.outDir, "astro.config.mjs"))) {
12345
+ if (!existsSync22(join34(context.outDir, "astro.config.mjs"))) {
11644
12346
  logger.error("No build found. Run `blume build` first.");
11645
12347
  process.exit(1);
11646
12348
  }
@@ -11658,7 +12360,7 @@ var previewCommand = defineCommand8({
11658
12360
  // src/cli/commands/sync.ts
11659
12361
  import { rm as rm6 } from "node:fs/promises";
11660
12362
  import { defineCommand as defineCommand9 } from "citty";
11661
- import { join as join32 } from "pathe";
12363
+ import { join as join35 } from "pathe";
11662
12364
  var syncCommand = defineCommand9({
11663
12365
  args: {
11664
12366
  force: {
@@ -11680,7 +12382,7 @@ var syncCommand = defineCommand9({
11680
12382
  if (args.force) {
11681
12383
  const { config } = await loadConfig(root);
11682
12384
  const context = resolveProjectContext(root, config);
11683
- await rm6(join32(context.outDir, "cache"), { force: true, recursive: true });
12385
+ await rm6(join35(context.outDir, "cache"), { force: true, recursive: true });
11684
12386
  logger.info("Cleared source cache.");
11685
12387
  }
11686
12388
  await prepareProject({
@@ -11695,13 +12397,13 @@ var syncCommand = defineCommand9({
11695
12397
  });
11696
12398
 
11697
12399
  // src/cli/commands/validate.ts
11698
- import { existsSync as existsSync21 } from "node:fs";
12400
+ import { existsSync as existsSync24 } from "node:fs";
11699
12401
  import { defineCommand as defineCommand10 } from "citty";
11700
- import { join as join34 } from "pathe";
12402
+ import { join as join37 } from "pathe";
11701
12403
 
11702
12404
  // src/core/links.ts
11703
- import { existsSync as existsSync20 } from "node:fs";
11704
- import { join as join33 } from "pathe";
12405
+ import { existsSync as existsSync23 } from "node:fs";
12406
+ import { join as join36 } from "pathe";
11705
12407
  var HTTP = /^https?:\/\//iu;
11706
12408
  var PROTOCOL_RELATIVE = /^\/\//u;
11707
12409
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -11761,7 +12463,7 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
11761
12463
  if (ctx.publicDir === null) {
11762
12464
  return "asset-unchecked";
11763
12465
  }
11764
- if (existsSync20(join33(ctx.publicDir, resolved))) {
12466
+ if (existsSync23(join36(ctx.publicDir, resolved))) {
11765
12467
  return null;
11766
12468
  }
11767
12469
  return {
@@ -11773,16 +12475,19 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
11773
12475
  };
11774
12476
  }
11775
12477
  const route = toRoute(resolved);
11776
- if (!ctx.routes.has(route)) {
11777
- return {
11778
- ...site,
11779
- code: "BLUME_BROKEN_LINK",
11780
- message: `Broken link to ${target}: no page resolves to ${route}.`,
11781
- severity: "error",
11782
- suggestion: "Check the path, or create the target page."
11783
- };
12478
+ if (ctx.routes.has(route)) {
12479
+ return fragment ? checkAnchor(route, fragment, site, ctx) : null;
12480
+ }
12481
+ if (ctx.redirects.has(route)) {
12482
+ return null;
11784
12483
  }
11785
- return fragment ? checkAnchor(route, fragment, site, ctx) : null;
12484
+ return {
12485
+ ...site,
12486
+ code: "BLUME_BROKEN_LINK",
12487
+ message: `Broken link to ${target}: no page resolves to ${route}.`,
12488
+ severity: "error",
12489
+ suggestion: "Check the path, or create the target page."
12490
+ };
11786
12491
  };
11787
12492
  var request = async (url, method) => {
11788
12493
  const controller = new AbortController;
@@ -11891,6 +12596,7 @@ var validateLinks = async (graph, options) => {
11891
12596
  const ctx = {
11892
12597
  anchors: buildAnchorIndex(graph.pages),
11893
12598
  publicDir: options.publicDir,
12599
+ redirects: new Set((options.redirects ?? []).map((redirect) => toRoute(redirect.from))),
11894
12600
  routes: new Set(graph.routes.keys())
11895
12601
  };
11896
12602
  const diagnostics = [];
@@ -11941,10 +12647,11 @@ var validateCommand = defineCommand10({
11941
12647
  try {
11942
12648
  const project = await scanProject(root, { mode: "build" });
11943
12649
  diagnostics.push(...project.diagnostics);
11944
- const publicDir = join34(root, "public");
12650
+ const publicDir = join37(root, "public");
11945
12651
  diagnostics.push(...await validateLinks(project.graph, {
11946
12652
  checkExternal: Boolean(args.external),
11947
- publicDir: existsSync21(publicDir) ? publicDir : null
12653
+ publicDir: existsSync24(publicDir) ? publicDir : null,
12654
+ redirects: project.config.redirects
11948
12655
  }));
11949
12656
  } catch (error) {
11950
12657
  if (error instanceof BlumeError) {
@@ -11985,5 +12692,5 @@ var main = defineCommand11({
11985
12692
  });
11986
12693
  runMain(main);
11987
12694
 
11988
- //# debugId=6F4400D6CD52CE0064756E2164756E21
12695
+ //# debugId=37F2F7467962582064756E2164756E21
11989
12696
  //# sourceMappingURL=index.js.map