blume 0.1.0 → 0.1.2

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 (106) hide show
  1. package/dist/cli/index.js +1213 -473
  2. package/dist/cli/index.js.map +33 -29
  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 +3461 -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 +103 -1
  62. package/package.json +13 -3
  63. package/src/ai/llms.ts +1 -2
  64. package/src/astro/examples.ts +106 -0
  65. package/src/astro/generate.ts +300 -75
  66. package/src/astro/index.ts +1 -1
  67. package/src/astro/islands.ts +1 -1
  68. package/src/astro/pages.ts +59 -0
  69. package/src/astro/templates.ts +180 -22
  70. package/src/cli/commands/validate.ts +1 -0
  71. package/src/components/content/CodeBlock.astro +28 -0
  72. package/src/components/content/Component.astro +65 -0
  73. package/src/components/content/Diff.astro +44 -0
  74. package/src/components/content/Tab.astro +8 -2
  75. package/src/components/content/diff.ts +95 -0
  76. package/src/components/layout/Favicon.astro +11 -4
  77. package/src/components/layout/PageLayout.astro +173 -0
  78. package/src/components/layout/ReferenceLayout.astro +6 -1
  79. package/src/components/layout/RootLayout.astro +9 -5
  80. package/src/components/layout/head-scripts.ts +19 -0
  81. package/src/core/data.ts +128 -0
  82. package/src/core/frontmatter.ts +43 -0
  83. package/src/core/links.ts +26 -10
  84. package/src/core/schema.ts +8 -0
  85. package/src/core/sources/filesystem.ts +1 -1
  86. package/src/core/sources/mdx-remote.ts +1 -2
  87. package/src/core/sources/mintlify.ts +1 -1
  88. package/src/core/sources/normalize.ts +19 -3
  89. package/src/core/sources/notion.ts +1 -1
  90. package/src/core/sources/sanity.ts +1 -1
  91. package/src/core/tsconfig-aliases.ts +201 -0
  92. package/src/index.ts +12 -0
  93. package/src/markdown/index.ts +69 -0
  94. package/src/migrate/fumadocs/config.ts +53 -4
  95. package/src/migrate/fumadocs/content.ts +1 -1
  96. package/src/migrate/fumadocs/groups.ts +230 -0
  97. package/src/migrate/fumadocs/index.ts +104 -19
  98. package/src/migrate/fumadocs/meta.ts +140 -10
  99. package/src/migrate/mintlify/snippets.ts +2 -1
  100. package/src/migrate/mintlify/transform.ts +1 -2
  101. package/src/migrate/nextra/index.ts +1 -1
  102. package/src/migrate/shared.ts +98 -1
  103. package/src/migrate/starlight/index.ts +1 -1
  104. package/src/registry/eject.ts +57 -10
  105. package/src/search/documents.ts +1 -2
  106. 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 { basename as basename2, 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,31 @@ var runtimeDependencies = (options) => {
3061
3212
  }
3062
3213
  return deps;
3063
3214
  };
3215
+ var RENDER_EXTERNAL_DEPS = [
3216
+ "@astrojs/markdown-satteri",
3217
+ "@pierre/diffs",
3218
+ "@shikijs/transformers",
3219
+ "@takumi-rs/core",
3220
+ "@takumi-rs/helpers",
3221
+ "github-slugger",
3222
+ "katex",
3223
+ "shiki",
3224
+ "simple-icons",
3225
+ "zod"
3226
+ ];
3227
+ var renderUserAliases = (aliases) => Object.entries(aliases ?? {}).toSorted(([a], [b]) => b.length - a.length).map(([find, replacement]) => `
3228
+ ${JSON.stringify(find)}: ${JSON.stringify(replacement)},`).join("");
3064
3229
  var astroConfigTemplate = (options) => {
3065
3230
  const { context, config, needsReact, pages, dataPath, themePath } = options;
3066
- const { contentRoutes, needsSvelte, needsVue, searchClientPath } = options;
3231
+ const {
3232
+ contentRoutes,
3233
+ examplesPath,
3234
+ needsSvelte,
3235
+ needsVue,
3236
+ searchClientPath
3237
+ } = options;
3067
3238
  const { deployment } = config;
3239
+ const userAliasLines = renderUserAliases(options.aliases);
3068
3240
  const server = deployment.output === "server";
3069
3241
  const fsAllow = [...new Set([findWorkspaceRoot(context.root), context.root])];
3070
3242
  const adapterImport = server && deployment.adapter ? `import adapter from "${ADAPTER_IMPORTS[deployment.adapter]}";
@@ -3099,7 +3271,7 @@ var astroConfigTemplate = (options) => {
3099
3271
  ` : "";
3100
3272
  const svelteImport = needsSvelte ? `import svelte from "@astrojs/svelte";
3101
3273
  ` : "";
3102
- const blumeImport = `import { blumeIntegration } from "blume/astro";
3274
+ const blumeImport = `import { blumeIntegration, prerenderDepsPlugin } from "blume/astro";
3103
3275
  `;
3104
3276
  const twoslashImport = `import { transformerTwoslash } from "@shikijs/twoslash";
3105
3277
  `;
@@ -3150,25 +3322,21 @@ export default defineConfig({
3150
3322
  },
3151
3323
  devToolbar: { enabled: false },
3152
3324
  vite: {
3153
- plugins: [tailwindcss()],
3154
- // @takumi-rs/core (OG image rendering) is a native NAPI addon that loads a
3155
- // platform-specific .node binding via createRequire(import.meta.url). Astro's
3156
- // build bundles it into the per-environment output by default, which
3157
- // relocates import.meta.url and breaks the binding lookup ("Cannot find
3158
- // native binding") on other platforms (e.g. the Linux CI runner). Astro 7
3159
- // configures externalization per Vite environment, so it must be forced
3160
- // external on the prerender (static) and ssr (server) environments -- a
3161
- // top-level ssr.external only reaches the latter -- so the binding resolves
3162
- // from node_modules at runtime instead.
3325
+ plugins: [tailwindcss(), prerenderDepsPlugin()],
3326
+ // Blume's render-time deps are forced external on both build environments so
3327
+ // native bindings resolve at runtime and isolated linkers don't bundle
3328
+ // symlinked store copies (which would surface their children as unresolvable
3329
+ // imports). See RENDER_EXTERNAL_DEPS / prerenderDepsPlugin.
3163
3330
  environments: {
3164
- prerender: { resolve: { external: ["@takumi-rs/core"] } },
3165
- ssr: { resolve: { external: ["@takumi-rs/core"] } },
3331
+ prerender: { resolve: { external: ${JSON.stringify(RENDER_EXTERNAL_DEPS)} } },
3332
+ ssr: { resolve: { external: ${JSON.stringify(RENDER_EXTERNAL_DEPS)} } },
3166
3333
  },
3167
3334
  resolve: {
3168
3335
  alias: {
3169
3336
  "blume:data": ${JSON.stringify(dataPath)},
3337
+ "blume:examples": ${JSON.stringify(examplesPath)},
3170
3338
  "blume:search-client": ${JSON.stringify(searchClientPath)},
3171
- "blume:theme": ${JSON.stringify(themePath)},
3339
+ "blume:theme": ${JSON.stringify(themePath)},${userAliasLines}
3172
3340
  },
3173
3341
  },
3174
3342
  server: {
@@ -3180,7 +3348,7 @@ export default defineConfig({
3180
3348
  });
3181
3349
  `;
3182
3350
  };
3183
- var stagedContentDir = (outDir) => join6(outDir, "content");
3351
+ var stagedContentDir = (outDir) => join7(outDir, "content");
3184
3352
  var contentConfigTemplate = (options) => {
3185
3353
  const { context, config } = options;
3186
3354
  const stagedBase = options.stagedBase ?? stagedContentDir(context.outDir);
@@ -3426,24 +3594,44 @@ export function GET({ props }) {
3426
3594
  });
3427
3595
  }
3428
3596
  `;
3429
- var ogEndpointTemplate = () => `// Generated by Blume. Do not edit.
3597
+ var ogEndpointTemplate = (customRoutes = []) => `// Generated by Blume. Do not edit.
3430
3598
  import { renderOgImage } from "blume/og";
3431
3599
  import data from "../../generated/data.json";
3432
3600
 
3433
3601
  export const prerender = true;
3434
3602
 
3603
+ // Custom (non-content) pages opted into a generated card, baked in at build.
3604
+ const customRoutes = ${JSON.stringify(customRoutes)};
3605
+
3435
3606
  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
- }));
3607
+ const seen = new Set();
3608
+ const paths = [];
3609
+ const add = (slug, title, eyebrow) => {
3610
+ if (seen.has(slug)) {
3611
+ return;
3612
+ }
3613
+ seen.add(slug);
3614
+ paths.push({ params: { slug }, props: { eyebrow, title } });
3615
+ };
3616
+ // A custom page wins over a content route sharing its path, so add it first.
3617
+ for (const route of customRoutes) {
3618
+ add(route.slug, route.title, route.eyebrow);
3619
+ }
3620
+ for (const route of data.routes) {
3621
+ add(
3622
+ route.path === "/" ? "index" : route.path.slice(1),
3623
+ route.title,
3624
+ data.config.title
3625
+ );
3626
+ }
3627
+ return paths;
3440
3628
  }
3441
3629
 
3442
3630
  export async function GET({ props }) {
3443
3631
  const png = await renderOgImage({
3444
- title: props.title,
3445
- eyebrow: data.config.title,
3446
3632
  accent: data.config.theme.accent,
3633
+ eyebrow: props.eyebrow,
3634
+ title: props.title,
3447
3635
  });
3448
3636
  return new Response(png, {
3449
3637
  headers: {
@@ -3470,6 +3658,7 @@ const configuration = ${JSON.stringify(options.configuration, null, 2)};
3470
3658
  fontCssVars={data.fontCssVars}
3471
3659
  logo={data.config.logo}
3472
3660
  favicon={data.config.favicon}
3661
+ appleIcon={data.config.appleIcon}
3473
3662
  navigation={data.navigation}
3474
3663
  pageTitle={${JSON.stringify(options.title)}}
3475
3664
  route={${JSON.stringify(options.route)}}
@@ -3501,12 +3690,15 @@ import Badge from "blume/components/content/Badge.astro";
3501
3690
  import Callout from "blume/components/content/Callout.astro";
3502
3691
  import Card from "blume/components/content/Card.astro";
3503
3692
  import CardGroup from "blume/components/content/CardGroup.astro";
3693
+ import CodeBlock from "blume/components/content/CodeBlock.astro";
3504
3694
  import CodeGroup from "blume/components/content/CodeGroup.astro";
3505
3695
  import ColorRoot from "blume/components/content/Color.astro";
3506
3696
  import ColorItem from "blume/components/content/ColorItem.astro";
3507
3697
  import ColorRow from "blume/components/content/ColorRow.astro";
3508
3698
  import Column from "blume/components/content/Column.astro";
3509
3699
  import Columns from "blume/components/content/Columns.astro";
3700
+ import Component from "blume/components/content/Component.astro";
3701
+ import Diff from "blume/components/content/Diff.astro";
3510
3702
  import Expandable from "blume/components/content/Expandable.astro";
3511
3703
  import FileTree from "blume/components/content/FileTree.astro";
3512
3704
  import Frame from "blume/components/content/Frame.astro";
@@ -3546,10 +3738,13 @@ const components = {
3546
3738
  Callout,
3547
3739
  Card,
3548
3740
  CardGroup,
3741
+ CodeBlock,
3549
3742
  CodeGroup,
3550
3743
  Color,
3551
3744
  Column,
3552
3745
  Columns,
3746
+ Component,
3747
+ Diff,
3553
3748
  Expandable,
3554
3749
  FileTree,
3555
3750
  Frame,
@@ -3670,6 +3865,7 @@ const localeSwitch = i18n
3670
3865
  logo={data.config.logo}
3671
3866
  mcp={data.config.mcp}
3672
3867
  favicon={data.config.favicon}
3868
+ appleIcon={data.config.appleIcon}
3673
3869
  banner={data.config.banner}
3674
3870
  analytics={data.config.analytics}
3675
3871
  imageZoom={data.config.imageZoom}
@@ -3796,6 +3992,7 @@ const canonical = base ? base + "/changelog" : null;
3796
3992
  logo={data.config.logo}
3797
3993
  mcp={data.config.mcp}
3798
3994
  favicon={data.config.favicon}
3995
+ appleIcon={data.config.appleIcon}
3799
3996
  banner={data.config.banner}
3800
3997
  analytics={data.config.analytics}
3801
3998
  imageZoom={data.config.imageZoom}
@@ -3875,8 +4072,43 @@ ${entries}
3875
4072
  };
3876
4073
  `;
3877
4074
  };
4075
+ var exampleDirective = (spec) => {
4076
+ if (spec.framework === "astro" || !spec.client) {
4077
+ return "";
4078
+ }
4079
+ return spec.client === "only" ? `client:only="${spec.framework}" ` : `client:${spec.client} `;
4080
+ };
4081
+ var exampleSlug = (path) => path.replaceAll("/", "__").replaceAll(/[^a-zA-Z0-9_]+/gu, "-");
4082
+ var exampleWrapperTemplate = (spec) => `---
4083
+ // Generated by Blume. Do not edit.
4084
+ import Example from ${JSON.stringify(spec.file)};
4085
+ ---
4086
+ <Example ${exampleDirective(spec)}{...Astro.props}><slot /></Example>
4087
+ `;
4088
+ var exampleMapTemplate = (specs) => {
4089
+ if (specs.length === 0) {
4090
+ return `// Generated by Blume. Do not edit.
4091
+ export const examples = {};
4092
+ `;
4093
+ }
4094
+ const imports = specs.map((spec, index) => `import E${index} from "./examples/${exampleSlug(spec.path)}.astro";`).join(`
4095
+ `);
4096
+ const entries = specs.map((spec, index) => ` ${JSON.stringify(spec.path)}: { Component: E${index}, code: ${JSON.stringify(spec.source)}, lang: ${JSON.stringify(spec.lang)} },`).join(`
4097
+ `);
4098
+ return `// Generated by Blume. Do not edit.
4099
+ ${imports}
4100
+ export const examples = {
4101
+ ${entries}
4102
+ };
4103
+ `;
4104
+ };
3878
4105
  var envTemplate = () => `/// <reference path="../.astro/types.d.ts" />
3879
4106
  /// <reference types="astro/client" />
4107
+
4108
+ declare module "blume:data" {
4109
+ const data: import("blume").BlumeData;
4110
+ export default data;
4111
+ }
3880
4112
  `;
3881
4113
  var runtimePackageTemplate = (dependencies = []) => `${JSON.stringify({
3882
4114
  dependencies: Object.fromEntries([...dependencies].toSorted().map((name) => [name, "*"])),
@@ -4057,7 +4289,7 @@ var specConfiguration = async (spec, root) => {
4057
4289
  if (URL_SPEC.test(spec)) {
4058
4290
  return { config: { url: spec } };
4059
4291
  }
4060
- const absolute = isAbsolute(spec) ? spec : join7(root, spec);
4292
+ const absolute = isAbsolute2(spec) ? spec : join8(root, spec);
4061
4293
  try {
4062
4294
  return { config: { content: await readFile4(absolute, "utf-8") } };
4063
4295
  } catch {
@@ -4419,6 +4651,19 @@ ${options.sources.map((source) => `@source "${source}";`).join(`
4419
4651
  text-decoration: var(--shiki-dark-text-decoration, inherit);
4420
4652
  }
4421
4653
 
4654
+ /* The Diff component (@pierre/diffs) renders into a declarative shadow root and
4655
+ themes itself with light-dark(). Drive that from Blume's data-theme instead
4656
+ of the OS, scoped to the diff host: the custom property pierces the shadow
4657
+ boundary, where the appended host color-scheme rule consumes it. */
4658
+ blume-diff {
4659
+ --blume-diff-color-scheme: light;
4660
+ display: block;
4661
+ }
4662
+
4663
+ :root[data-theme="dark"] blume-diff {
4664
+ --blume-diff-color-scheme: dark;
4665
+ }
4666
+
4422
4667
  .prose :where(pre[data-language]) {
4423
4668
  padding-top: 3.75rem;
4424
4669
  }
@@ -4492,6 +4737,22 @@ blume-tabs pre,
4492
4737
  background: var(--blume-code-background);
4493
4738
  }
4494
4739
 
4740
+ /* The Component source pane fills its tab's fixed height (set inline to match the
4741
+ preview tab) and scrolls the inner code element, leaving the pre static so the
4742
+ copy button — absolute, pinned to the pre — doesn't drift while it scrolls. */
4743
+ pre.blume-source {
4744
+ display: flex;
4745
+ flex-direction: column;
4746
+ height: 100%;
4747
+ overflow: hidden;
4748
+ }
4749
+
4750
+ pre.blume-source > code {
4751
+ flex: 1;
4752
+ min-height: 0;
4753
+ overflow: auto;
4754
+ }
4755
+
4495
4756
  /* Code blocks inside tabs sit flush; the tab container owns the frame. These
4496
4757
  rules are unlayered like the base \`pre\` styles, so they win over Tailwind's
4497
4758
  layered utilities (which a class on the panel cannot). The second selector
@@ -4701,9 +4962,9 @@ ${options.userTheme}
4701
4962
  `;
4702
4963
 
4703
4964
  // 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);
4965
+ import { readFileSync as readFileSync4 } from "node:fs";
4966
+ import { createRequire as createRequire3 } from "node:module";
4967
+ var require2 = createRequire3(import.meta.url);
4707
4968
  var OVERRIDES = `
4708
4969
  /* Twoslash: theme the rich renderer with Blume tokens. */
4709
4970
  :root {
@@ -4763,13 +5024,18 @@ var OVERRIDES = `
4763
5024
  `;
4764
5025
  var twoslashCss = () => {
4765
5026
  const file = require2.resolve("@shikijs/twoslash/style-rich.css");
4766
- return `${readFileSync3(file, "utf-8")}
5027
+ return `${readFileSync4(file, "utf-8")}
4767
5028
  ${OVERRIDES}`;
4768
5029
  };
4769
5030
 
5031
+ // src/astro/examples.ts
5032
+ import { readFile as readFile6 } from "node:fs/promises";
5033
+ import { join as join10, relative as relative3 } from "pathe";
5034
+ import { glob as glob2 } from "tinyglobby";
5035
+
4770
5036
  // src/astro/islands.ts
4771
5037
  import { readFile as readFile5 } from "node:fs/promises";
4772
- import { basename, join as join8 } from "pathe";
5038
+ import { basename, join as join9 } from "pathe";
4773
5039
  import { glob } from "tinyglobby";
4774
5040
  var DEFAULT_CLIENT = "visible";
4775
5041
  var VALID_MODES = new Set([
@@ -4798,7 +5064,7 @@ var readClientMode = (source, file, warnings) => {
4798
5064
  return mode;
4799
5065
  };
4800
5066
  var discoverIslands = async (root) => {
4801
- const dir = join8(root, "islands");
5067
+ const dir = join9(root, "islands");
4802
5068
  const matches = await glob(["**/*.{jsx,svelte,tsx,vue}"], {
4803
5069
  absolute: true,
4804
5070
  cwd: dir,
@@ -4837,50 +5103,157 @@ var discoverIslands = async (root) => {
4837
5103
  return { islands, warnings };
4838
5104
  };
4839
5105
 
5106
+ // src/astro/examples.ts
5107
+ var FRAMEWORK_BY_EXT2 = {
5108
+ astro: "astro",
5109
+ jsx: "react",
5110
+ svelte: "svelte",
5111
+ tsx: "react",
5112
+ vue: "vue"
5113
+ };
5114
+ var EXAMPLE_FILE = /\.(?<ext>astro|jsx|svelte|tsx|vue)$/u;
5115
+ var discoverExamples = async (root, subdir = "examples") => {
5116
+ const dir = join10(root, subdir);
5117
+ const matches = await glob2(["**/*.{astro,jsx,svelte,tsx,vue}"], {
5118
+ absolute: true,
5119
+ cwd: dir,
5120
+ onlyFiles: true
5121
+ });
5122
+ const files = matches.toSorted();
5123
+ const sources = await Promise.all(files.map((file) => readFile6(file, "utf-8")));
5124
+ const examples = [];
5125
+ const warnings = [];
5126
+ const seen = new Map;
5127
+ for (const [index, file] of files.entries()) {
5128
+ const ext = file.match(EXAMPLE_FILE)?.groups?.ext;
5129
+ const framework = ext ? FRAMEWORK_BY_EXT2[ext] : undefined;
5130
+ if (!(ext && framework)) {
5131
+ continue;
5132
+ }
5133
+ const path = relative3(dir, file).slice(0, -(ext.length + 1));
5134
+ const existing = seen.get(path);
5135
+ if (existing) {
5136
+ warnings.push(`Two examples both resolve to "${path}" ("${existing}" and "${file}"); ignoring the second. Give them distinct paths.`);
5137
+ continue;
5138
+ }
5139
+ seen.set(path, file);
5140
+ const source = sources[index] ?? "";
5141
+ examples.push({
5142
+ client: framework === "astro" ? undefined : readClientMode(source, file, warnings),
5143
+ file,
5144
+ framework,
5145
+ lang: ext,
5146
+ path,
5147
+ source
5148
+ });
5149
+ }
5150
+ return { examples, warnings };
5151
+ };
5152
+
4840
5153
  // src/astro/pages.ts
4841
- import { extname, relative as relative3 } from "pathe";
4842
- import { glob as glob2 } from "tinyglobby";
5154
+ import { extname, relative as relative4 } from "pathe";
5155
+ import { glob as glob3 } from "tinyglobby";
4843
5156
  var discoverPages = async (pagesRoot) => {
4844
- const files = await glob2(["**/*.astro"], {
5157
+ const files = await glob3(["**/*.astro"], {
4845
5158
  absolute: true,
4846
5159
  cwd: pagesRoot,
4847
5160
  onlyFiles: true
4848
5161
  });
4849
5162
  files.sort();
4850
5163
  return files.map((file) => {
4851
- const rel = relative3(pagesRoot, file);
5164
+ const rel = relative4(pagesRoot, file);
4852
5165
  const withoutExt = rel.slice(0, rel.length - extname(rel).length);
4853
5166
  const parts = withoutExt.split("/").filter((part) => part !== "index");
4854
5167
  const pattern = parts.length === 0 ? "/" : `/${parts.join("/")}`;
4855
5168
  return { entrypoint: file, pattern };
4856
5169
  });
4857
5170
  };
5171
+ var PRIVATE_SEGMENT = /^[._]/u;
5172
+ var humanizeSegment = (segment) => segment.split(/[-_]/u).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
5173
+ var customOgRoutes = (pages, siteTitle, siteDescription) => {
5174
+ const seen = new Set;
5175
+ const routes = [];
5176
+ for (const { pattern } of pages) {
5177
+ const segments = pattern.split("/").filter(Boolean);
5178
+ if (segments.some((part) => PRIVATE_SEGMENT.test(part) || part.includes("["))) {
5179
+ continue;
5180
+ }
5181
+ const slug = segments.length === 0 ? "index" : segments.join("/");
5182
+ if (seen.has(slug)) {
5183
+ continue;
5184
+ }
5185
+ seen.add(slug);
5186
+ const last = segments.at(-1);
5187
+ routes.push(last ? { eyebrow: siteTitle, slug, title: humanizeSegment(last) } : { eyebrow: siteDescription, slug, title: siteTitle });
5188
+ }
5189
+ return routes;
5190
+ };
4858
5191
 
4859
5192
  // src/astro/generate.ts
4860
- var BLUME_SRC = join9(packageRoot(), "src");
4861
- var BLUME_NODE_MODULES = join9(packageRoot(), "node_modules");
5193
+ var BLUME_SRC = join11(packageRoot(), "src");
4862
5194
  var canResolveFrom = (fromDir, spec) => {
4863
5195
  try {
4864
- createRequire3(pathToFileURL(join9(fromDir, "_.js")).href).resolve(spec);
5196
+ createRequire4(pathToFileURL2(join11(fromDir, "_.js")).href).resolve(spec);
4865
5197
  return true;
4866
5198
  } catch {
4867
5199
  return false;
4868
5200
  }
4869
5201
  };
4870
- var canResolveAstro = (fromDir) => canResolveFrom(fromDir, "astro/package.json");
4871
- var ensureDepsLink = async (outDir) => {
4872
- if (canResolveAstro(outDir)) {
4873
- return;
5202
+ var resolvedAstroPath = (fromDir) => {
5203
+ try {
5204
+ const pkg = createRequire4(pathToFileURL2(join11(fromDir, "_.js")).href).resolve("astro/package.json");
5205
+ return realpathSync(pkg);
5206
+ } catch {
5207
+ return null;
4874
5208
  }
4875
- if (!existsSync4(join9(BLUME_NODE_MODULES, "astro"))) {
4876
- return;
5209
+ };
5210
+ var blumeDepsDir = (pkgDir = packageRoot()) => {
5211
+ const candidates = [join11(pkgDir, "node_modules"), dirname6(pkgDir)];
5212
+ return candidates.find((dir) => existsSync5(join11(dir, "astro"))) ?? null;
5213
+ };
5214
+ var linkDepsJunction = async (link, depsDir) => {
5215
+ const existing = await lstat(link).catch(() => null);
5216
+ if (existing) {
5217
+ if (!existing.isSymbolicLink()) {
5218
+ return;
5219
+ }
5220
+ await rm(link, { force: true });
4877
5221
  }
4878
- const link = join9(outDir, "node_modules");
4879
- if (existsSync4(link)) {
4880
- return;
5222
+ await mkdir2(dirname6(link), { recursive: true });
5223
+ await symlink(depsDir, link, "junction");
5224
+ };
5225
+ var readPkgVersion = (pkgJsonPath) => {
5226
+ if (!pkgJsonPath) {
5227
+ return null;
5228
+ }
5229
+ try {
5230
+ return JSON.parse(readFileSync5(pkgJsonPath, "utf-8")).version ?? null;
5231
+ } catch {
5232
+ return null;
5233
+ }
5234
+ };
5235
+ var astroConflictWarning = (blumeAstroPkg, shadowAstroPkg) => {
5236
+ const blume = readPkgVersion(blumeAstroPkg);
5237
+ const shadow = readPkgVersion(shadowAstroPkg);
5238
+ const versions = blume && shadow ? `astro@${shadow} shadowing Blume's astro@${blume}` : "a second copy of Astro shadowing Blume's";
5239
+ const pin = blume ?? "<Blume's astro version>";
5240
+ return `Astro version conflict: another dependency hoisted ${versions} to the project root, so @astrojs/mdx binds to the wrong copy and the build fails on a missing export (e.g. "chunkToString"). A single symlink can't reconcile a split install — pin Blume's Astro by adding a package.json "overrides" (npm/bun/pnpm) or "resolutions" (yarn) entry { "astro": "${pin}" }, then reinstall. Run \`npm ls astro\` to find the dependency pulling the older copy.`;
5241
+ };
5242
+ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
5243
+ const depsDir = blumeDepsDir(pkgDir);
5244
+ if (!depsDir) {
5245
+ return null;
5246
+ }
5247
+ const blumeAstro = resolvedAstroPath(depsDir);
5248
+ const outDirAstro = resolvedAstroPath(outDir);
5249
+ if (blumeAstro && outDirAstro === blumeAstro) {
5250
+ return null;
5251
+ }
5252
+ if (existsSync5(join11(depsDir, "@astrojs", "mdx"))) {
5253
+ await linkDepsJunction(join11(outDir, "node_modules"), depsDir);
5254
+ return null;
4881
5255
  }
4882
- await mkdir2(outDir, { recursive: true });
4883
- await symlink(BLUME_NODE_MODULES, link, "junction");
5256
+ return astroConflictWarning(blumeAstro, outDirAstro);
4884
5257
  };
4885
5258
  var ISLAND_FRAMEWORK_DEPS = {
4886
5259
  svelte: "@astrojs/svelte",
@@ -4901,13 +5274,13 @@ var readOptional = async (path) => {
4901
5274
  return "";
4902
5275
  }
4903
5276
  try {
4904
- return await readFile6(path, "utf-8");
5277
+ return await readFile7(path, "utf-8");
4905
5278
  } catch {
4906
5279
  return "";
4907
5280
  }
4908
5281
  };
4909
5282
  var detectNeedsReact = async (root) => {
4910
- const matches = await glob3(["**/*.{tsx,jsx}"], {
5283
+ const matches = await glob4(["**/*.{tsx,jsx}"], {
4911
5284
  cwd: root,
4912
5285
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
4913
5286
  onlyFiles: true
@@ -4917,14 +5290,14 @@ var detectNeedsReact = async (root) => {
4917
5290
  var writeIfChanged = async (path, content) => {
4918
5291
  let existing = null;
4919
5292
  try {
4920
- existing = await readFile6(path, "utf-8");
5293
+ existing = await readFile7(path, "utf-8");
4921
5294
  } catch {
4922
5295
  existing = null;
4923
5296
  }
4924
5297
  if (existing === content) {
4925
5298
  return false;
4926
5299
  }
4927
- await mkdir2(dirname5(path), { recursive: true });
5300
+ await mkdir2(dirname6(path), { recursive: true });
4928
5301
  const tmp = `${path}.${process.pid}.tmp`;
4929
5302
  await writeFile2(tmp, content, "utf-8");
4930
5303
  try {
@@ -4936,7 +5309,7 @@ var writeIfChanged = async (path, content) => {
4936
5309
  return true;
4937
5310
  };
4938
5311
  var pruneOrphans = async (srcDir, written) => {
4939
- const existing = await glob3("**/*", {
5312
+ const existing = await glob4("**/*", {
4940
5313
  absolute: true,
4941
5314
  cwd: srcDir,
4942
5315
  onlyFiles: true
@@ -4956,11 +5329,11 @@ var writeStagedContent = async (out, staged) => {
4956
5329
  const contentDir = stagedContentDir(out);
4957
5330
  const written = new Set;
4958
5331
  await Promise.all([...staged].map(async ([entryId, text]) => {
4959
- const path = join9(contentDir, entryId);
5332
+ const path = join11(contentDir, entryId);
4960
5333
  written.add(normalize(path));
4961
5334
  await writeIfChanged(path, text);
4962
5335
  }));
4963
- if (existsSync4(contentDir)) {
5336
+ if (existsSync5(contentDir)) {
4964
5337
  await pruneOrphans(contentDir, written);
4965
5338
  }
4966
5339
  };
@@ -4977,11 +5350,11 @@ var resolveLogo = (project) => {
4977
5350
  if (light && light === dark && light.toLowerCase().endsWith(".svg")) {
4978
5351
  const rel = light.replace(/^\//u, "");
4979
5352
  const file = [
4980
- join9(project.context.root, "public", rel),
4981
- join9(project.context.root, rel)
4982
- ].find((path) => existsSync4(path));
5353
+ join11(project.context.root, "public", rel),
5354
+ join11(project.context.root, rel)
5355
+ ].find((path) => existsSync5(path));
4983
5356
  if (file) {
4984
- return { alt, href, svg: readFileSync4(file, "utf-8") };
5357
+ return { alt, href, svg: readFileSync5(file, "utf-8") };
4985
5358
  }
4986
5359
  }
4987
5360
  return { alt, dark, href, light };
@@ -4996,6 +5369,8 @@ var FAVICON_CANDIDATES = [
4996
5369
  ];
4997
5370
  var FAVICON_TYPES = {
4998
5371
  ico: "image/x-icon",
5372
+ jpeg: "image/jpeg",
5373
+ jpg: "image/jpeg",
4999
5374
  png: "image/png",
5000
5375
  svg: "image/svg+xml"
5001
5376
  };
@@ -5003,27 +5378,35 @@ var faviconType = (name) => {
5003
5378
  const ext = name.split(".").pop()?.toLowerCase();
5004
5379
  return ext ? FAVICON_TYPES[ext] : undefined;
5005
5380
  };
5006
- var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync4(file).toString("base64")}`;
5381
+ var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync5(file).toString("base64")}`;
5007
5382
  var defaultFavicon = () => ({
5008
- href: inlineDataUri(join9(BLUME_SRC, "assets", "icon.png"), "image/png"),
5383
+ href: inlineDataUri(join11(BLUME_SRC, "assets", "icon.png"), "image/png"),
5009
5384
  type: "image/png"
5010
5385
  });
5011
- var resolveFavicon = (project) => {
5386
+ var APPLE_ICON_CANDIDATES = [
5387
+ "apple-icon.png",
5388
+ "apple-icon.jpg",
5389
+ "apple-icon.jpeg",
5390
+ "apple-touch-icon.png"
5391
+ ];
5392
+ var resolveIconFile = (project, candidates) => {
5012
5393
  const { root } = project.context;
5013
- for (const name of FAVICON_CANDIDATES) {
5014
- if (existsSync4(join9(root, "public", name))) {
5394
+ for (const name of candidates) {
5395
+ if (existsSync5(join11(root, "public", name))) {
5015
5396
  return { href: `/${name}`, type: faviconType(name) };
5016
5397
  }
5017
5398
  }
5018
- for (const name of FAVICON_CANDIDATES) {
5019
- const file = join9(root, name);
5020
- if (existsSync4(file)) {
5399
+ for (const name of candidates) {
5400
+ const file = join11(root, name);
5401
+ if (existsSync5(file)) {
5021
5402
  const type = faviconType(name);
5022
5403
  return { href: inlineDataUri(file, type ?? "image/x-icon"), type };
5023
5404
  }
5024
5405
  }
5025
- return defaultFavicon();
5406
+ return null;
5026
5407
  };
5408
+ var resolveFavicon = (project) => resolveIconFile(project, FAVICON_CANDIDATES) ?? defaultFavicon();
5409
+ var resolveAppleIcon = (project) => resolveIconFile(project, APPLE_ICON_CANDIDATES);
5027
5410
  var resolveBanner = (config) => {
5028
5411
  const { banner } = config;
5029
5412
  if (!banner) {
@@ -5048,7 +5431,7 @@ var buildRuntimeData = (project) => {
5048
5431
  if (!(editBase && sourcePath)) {
5049
5432
  return null;
5050
5433
  }
5051
- const rel = relative4(context.root, sourcePath).split("\\").join("/");
5434
+ const rel = relative5(context.root, sourcePath).split("\\").join("/");
5052
5435
  return `${editBase}/${github?.dir ? `${github.dir}/${rel}` : rel}`;
5053
5436
  };
5054
5437
  const { i18n } = config;
@@ -5081,6 +5464,7 @@ var buildRuntimeData = (project) => {
5081
5464
  const data = {
5082
5465
  config: {
5083
5466
  analytics: config.analytics ?? null,
5467
+ appleIcon: resolveAppleIcon(project),
5084
5468
  banner: resolveBanner(config),
5085
5469
  codeWrap: config.markdown.code.wrap,
5086
5470
  description: config.description,
@@ -5099,7 +5483,7 @@ var buildRuntimeData = (project) => {
5099
5483
  imageZoom: config.markdown.imageZoom,
5100
5484
  logo: resolveLogo(project),
5101
5485
  mcp: config.mcp.enabled ? { name: config.mcp.name ?? config.title, route: config.mcp.route } : null,
5102
- og: { enabled: config.seo.og.enabled },
5486
+ og: { enabled: config.seo.og.enabled ?? false },
5103
5487
  repoUrl,
5104
5488
  search: {
5105
5489
  enabled: config.search.provider !== "none",
@@ -5141,7 +5525,7 @@ var buildRuntimeData = (project) => {
5141
5525
  var planMcp = (project, srcDir) => {
5142
5526
  const { config } = project;
5143
5527
  const { route } = config.mcp;
5144
- const dir = join9(srcDir, "blume-mcp");
5528
+ const dir = join11(srcDir, "blume-mcp");
5145
5529
  const base = {
5146
5530
  dir,
5147
5531
  discoveryPages: [],
@@ -5165,11 +5549,11 @@ var planMcp = (project, srcDir) => {
5165
5549
  ...base,
5166
5550
  discoveryPages: [
5167
5551
  {
5168
- entrypoint: join9(dir, "discovery.ts"),
5552
+ entrypoint: join11(dir, "discovery.ts"),
5169
5553
  pattern: "/.well-known/mcp.json"
5170
5554
  },
5171
5555
  {
5172
- entrypoint: join9(dir, "server-card.ts"),
5556
+ entrypoint: join11(dir, "server-card.ts"),
5173
5557
  pattern: "/.well-known/mcp/server-card.json"
5174
5558
  }
5175
5559
  ],
@@ -5188,49 +5572,57 @@ var writeMcpFiles = async (project, plan, write) => {
5188
5572
  version: data.version
5189
5573
  };
5190
5574
  await Promise.all([
5191
- write(join9(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
5575
+ write(join11(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
5192
5576
  `),
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)))
5577
+ write(join11(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
5578
+ write(join11(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
5579
+ write(join11(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
5196
5580
  ]);
5197
5581
  };
5198
5582
  var generateRuntime = async (project) => {
5199
5583
  const { context, config } = project;
5200
5584
  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");
5585
+ const srcDir = join11(out, "src");
5586
+ const dataPath = join11(srcDir, "generated", "data.json");
5587
+ const themePath = join11(srcDir, "generated", "app.css");
5588
+ const searchClientPath = join11(srcDir, "generated", "search-client.ts");
5589
+ const examplesPath = join11(srcDir, "generated", "examples.ts");
5205
5590
  const written = new Set;
5206
5591
  const write = (path, content) => {
5207
5592
  written.add(normalize(path));
5208
5593
  return writeIfChanged(path, content);
5209
5594
  };
5210
- await ensureDepsLink(out);
5595
+ const depsLinkWarning = await ensureDepsLink(out);
5211
5596
  const askEnabled = config.ai.ask?.enabled ?? false;
5212
5597
  const exportPdf = config.export.pdf;
5213
5598
  const exportEpub = config.export.epub;
5214
- const [pages, detectedReact, userTheme, islandDiscovery] = await Promise.all([
5599
+ const [pages, detectedReact, userTheme, islandDiscovery, exampleDiscovery] = await Promise.all([
5215
5600
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
5216
5601
  detectNeedsReact(context.root),
5217
5602
  readOptional(context.themeFile),
5218
- discoverIslands(context.root)
5603
+ discoverIslands(context.root),
5604
+ discoverExamples(context.root, config.examples)
5605
+ ]);
5606
+ const frameworks = new Set([
5607
+ ...islandDiscovery.islands.map((island) => island.framework),
5608
+ ...exampleDiscovery.examples.map((example) => example.framework)
5219
5609
  ]);
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");
5610
+ const needsReact = detectedReact || askEnabled || frameworks.has("react");
5611
+ const needsVue = frameworks.has("vue");
5612
+ const needsSvelte = frameworks.has("svelte");
5613
+ const ogRoutes = customOgRoutes(pages, config.title, config.description);
5224
5614
  const mcp = planMcp(project, srcDir);
5225
5615
  pages.push(...mcp.discoveryPages);
5226
5616
  const staged = collectStaged(project);
5227
5617
  const hasStaged = staged.size > 0;
5228
5618
  const structural = await Promise.all([
5229
- write(join9(out, "astro.config.mjs"), astroConfigTemplate({
5619
+ write(join11(out, "astro.config.mjs"), astroConfigTemplate({
5620
+ aliases: resolveTsconfigAliases(context.root),
5230
5621
  config,
5231
5622
  contentRoutes: project.manifest.routes.map((route) => route.path),
5232
5623
  context,
5233
5624
  dataPath,
5625
+ examplesPath,
5234
5626
  needsReact,
5235
5627
  needsSvelte,
5236
5628
  needsVue,
@@ -5238,18 +5630,19 @@ var generateRuntime = async (project) => {
5238
5630
  searchClientPath,
5239
5631
  themePath
5240
5632
  })),
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({
5633
+ write(join11(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
5634
+ write(join11(out, "tsconfig.json"), runtimeTsconfigTemplate()),
5635
+ write(join11(srcDir, "env.d.ts"), envTemplate()),
5636
+ write(join11(srcDir, "content.config.ts"), contentConfigTemplate({ config, context, staged: hasStaged })),
5637
+ write(join11(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
5246
5638
  askEnabled,
5247
5639
  exportEpub,
5248
5640
  exportPdf,
5249
5641
  mathEnabled: config.markdown.math
5250
5642
  })),
5251
- write(join9(srcDir, "generated", "components.ts"), userComponentsTemplate(context.componentsFile)),
5252
- write(join9(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
5643
+ write(join11(srcDir, "generated", "components.ts"), userComponentsTemplate(context.componentsFile)),
5644
+ write(join11(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
5645
+ write(join11(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples)),
5253
5646
  write(themePath, tailwindEntryTemplate({
5254
5647
  configTokens: `${buildThemeCss(config.theme)}${buildFontsCss(config.theme.fonts)}`,
5255
5648
  sources: [
@@ -5260,52 +5653,58 @@ var generateRuntime = async (project) => {
5260
5653
  userTheme
5261
5654
  }))
5262
5655
  ]);
5263
- await Promise.all(islandDiscovery.islands.map((island) => write(join9(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island))));
5656
+ await Promise.all(islandDiscovery.islands.map((island) => write(join11(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island))));
5657
+ await Promise.all(exampleDiscovery.examples.map((example) => write(join11(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example))));
5264
5658
  if (askEnabled) {
5265
- await write(join9(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(config.ai.ask)));
5659
+ await write(join11(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(config.ai.ask)));
5266
5660
  }
5267
5661
  await writeMcpFiles(project, mcp, write);
5268
5662
  if (config.seo.og.enabled) {
5269
- await write(join9(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate());
5663
+ await write(join11(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes));
5270
5664
  }
5271
5665
  const hasChangelog = project.graph.pages.some((page) => page.contentType === "changelog" && !(page.meta.draft || page.meta.sidebar.hidden));
5272
5666
  const changelogRouteTaken = project.graph.pages.some((page) => page.route === "/changelog");
5273
5667
  if (hasChangelog && !changelogRouteTaken) {
5274
- await write(join9(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({ askEnabled, exportEpub, exportPdf }));
5668
+ await write(join11(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({ askEnabled, exportEpub, exportPdf }));
5275
5669
  }
5276
5670
  await write(searchClientPath, searchClientTemplate(config));
5277
5671
  if (servesStaticIndex(config.search.provider)) {
5278
5672
  const documents = await buildSearchDocuments(project);
5279
- await write(join9(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
5673
+ await write(join11(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
5280
5674
  `);
5281
- await write(join9(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
5675
+ await write(join11(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
5282
5676
  }
5283
5677
  if (config.search.provider === "mixedbread") {
5284
- await write(join9(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
5678
+ await write(join11(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
5285
5679
  }
5286
5680
  const rawMarkdown = await buildRawMarkdown(project);
5287
5681
  await Promise.all([
5288
- write(join9(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
5682
+ write(join11(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
5289
5683
  `),
5290
- write(join9(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate()),
5291
- write(join9(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate())
5684
+ write(join11(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate()),
5685
+ write(join11(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate())
5292
5686
  ]);
5293
5687
  const feeds = buildRssFeeds(project);
5294
5688
  if (feeds.length > 0) {
5295
5689
  const feedXml = Object.fromEntries(feeds.map((feed) => [feed.type, renderRssFeed(feed)]));
5296
5690
  await Promise.all([
5297
- write(join9(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
5691
+ write(join11(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
5298
5692
  `),
5299
- write(join9(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
5693
+ write(join11(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
5300
5694
  ]);
5301
5695
  }
5302
- const warnings = [...mcp.warnings, ...islandDiscovery.warnings];
5696
+ const warnings = [
5697
+ ...depsLinkWarning ? [depsLinkWarning] : [],
5698
+ ...mcp.warnings,
5699
+ ...islandDiscovery.warnings,
5700
+ ...exampleDiscovery.warnings
5701
+ ];
5303
5702
  for (const dep of searchProviderMeta(config.search.provider).runtimeDeps) {
5304
5703
  if (!canResolveFrom(context.root, dep)) {
5305
5704
  warnings.push(`Search provider "${config.search.provider}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`);
5306
5705
  }
5307
5706
  }
5308
- warnings.push(...islandFrameworkWarnings(islandFrameworks, context.root));
5707
+ warnings.push(...islandFrameworkWarnings(frameworks, context.root));
5309
5708
  if (hasReferences(config)) {
5310
5709
  const references = await buildReferenceFiles({
5311
5710
  config,
@@ -5313,10 +5712,10 @@ var generateRuntime = async (project) => {
5313
5712
  root: context.root
5314
5713
  });
5315
5714
  warnings.push(...references.warnings);
5316
- await Promise.all(references.files.map((file) => write(join9(srcDir, "pages", file.pagePath), file.content)));
5715
+ await Promise.all(references.files.map((file) => write(join11(srcDir, "pages", file.pagePath), file.content)));
5317
5716
  }
5318
- await write(join9(srcDir, "generated", "data.json"), buildRuntimeData(project));
5319
- await write(join9(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
5717
+ await write(join11(srcDir, "generated", "data.json"), buildRuntimeData(project));
5718
+ await write(join11(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
5320
5719
  `);
5321
5720
  await writeStagedContent(out, staged);
5322
5721
  await pruneOrphans(srcDir, written);
@@ -5324,13 +5723,13 @@ var generateRuntime = async (project) => {
5324
5723
  };
5325
5724
 
5326
5725
  // 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";
5726
+ import { existsSync as existsSync6 } from "node:fs";
5727
+ import { readFile as readFile9 } from "node:fs/promises";
5728
+ import { join as join12 } from "pathe";
5330
5729
 
5331
5730
  // 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";
5731
+ import { readFile as readFile8 } from "node:fs/promises";
5732
+ import { dirname as dirname7, relative as relative6, resolve as resolve3 } from "pathe";
5334
5733
  var MINTLIFY_DEFAULT_IGNORES = [
5335
5734
  "**/_*",
5336
5735
  "**/.*",
@@ -5366,12 +5765,12 @@ var asDirectoryMode = (value) => value === "accordion" || value === "card" || va
5366
5765
  var withoutUndefined = (value) => Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
5367
5766
  var hasOwn = (object, key) => Object.hasOwn(object, key);
5368
5767
  var isInsideRoot = (root, candidate) => {
5369
- const rel = relative5(root, candidate);
5768
+ const rel = relative6(root, candidate);
5370
5769
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
5371
5770
  };
5372
5771
  var readJsonFile = async (file) => {
5373
5772
  try {
5374
- return JSON.parse(await readFile7(file, "utf-8"));
5773
+ return JSON.parse(await readFile8(file, "utf-8"));
5375
5774
  } catch (error) {
5376
5775
  throw new BlumeError({
5377
5776
  code: "BLUME_MINTLIFY_CONFIG_INVALID",
@@ -5391,7 +5790,7 @@ var resolveRefs = async (value, options) => {
5391
5790
  }
5392
5791
  const ref = asString(object.$ref);
5393
5792
  if (ref) {
5394
- const refFile = resolve2(dirname6(options.file), ref);
5793
+ const refFile = resolve3(dirname7(options.file), ref);
5395
5794
  if (!isInsideRoot(options.root, refFile)) {
5396
5795
  throw new BlumeError({
5397
5796
  code: "BLUME_MINTLIFY_REF_OUTSIDE_ROOT",
@@ -5791,7 +6190,7 @@ var mintlifyNavbar = (value) => {
5791
6190
  };
5792
6191
  var mintignorePatterns = async (root) => {
5793
6192
  try {
5794
- const raw = await readFile7(resolve2(root, ".mintignore"), "utf-8");
6193
+ const raw = await readFile8(resolve3(root, ".mintignore"), "utf-8");
5795
6194
  return raw.split(`
5796
6195
  `).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).filter((line) => !line.startsWith("!")).map((line) => line.endsWith("/") ? `${line}**` : line);
5797
6196
  } catch {
@@ -6015,8 +6414,8 @@ var mintlifyIcons = (value) => {
6015
6414
  });
6016
6415
  };
6017
6416
  var loadMintlifyConfig = async (root, file) => {
6018
- const projectRoot = resolve2(root);
6019
- const configFile = resolve2(file);
6417
+ const projectRoot = resolve3(root);
6418
+ const configFile = resolve3(file);
6020
6419
  const spec = asObject(await resolveRefs(await readJsonFile(configFile), {
6021
6420
  file: configFile,
6022
6421
  root: projectRoot,
@@ -6117,12 +6516,12 @@ var mintlifyI18n = (spec) => {
6117
6516
  // src/core/bridge.ts
6118
6517
  var MINTLIFY_CONFIG_FILES = ["docs.json", "mint.json"];
6119
6518
  var detectMintlifyBridge = async (root) => {
6120
- const configFile = MINTLIFY_CONFIG_FILES.map((name) => join10(root, name)).find((candidate) => existsSync5(candidate));
6519
+ const configFile = MINTLIFY_CONFIG_FILES.map((name) => join12(root, name)).find((candidate) => existsSync6(candidate));
6121
6520
  if (!configFile) {
6122
6521
  return null;
6123
6522
  }
6124
6523
  const config = await loadMintlifyConfig(root, configFile);
6125
- const spec = JSON.parse(await readFile8(configFile, "utf-8"));
6524
+ const spec = JSON.parse(await readFile9(configFile, "utf-8"));
6126
6525
  const i18n = mintlifyI18n(spec);
6127
6526
  if (i18n) {
6128
6527
  config.i18n = i18n;
@@ -6206,8 +6605,8 @@ var createModuleLoader = () => {
6206
6605
  };
6207
6606
 
6208
6607
  // 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";
6608
+ import { existsSync as existsSync7 } from "node:fs";
6609
+ import { isAbsolute as isAbsolute3, join as join13, resolve as resolve4 } from "pathe";
6211
6610
  var CONFIG_FILENAMES = [
6212
6611
  "blume.config.ts",
6213
6612
  "blume.config.mjs",
@@ -6217,8 +6616,8 @@ var THEME_FILENAMES = ["theme.css"];
6217
6616
  var COMPONENTS_FILENAMES = ["components.tsx", "components.ts"];
6218
6617
  var firstExisting = (root, names) => {
6219
6618
  for (const name of names) {
6220
- const candidate = join11(root, name);
6221
- if (existsSync6(candidate)) {
6619
+ const candidate = join13(root, name);
6620
+ if (existsSync7(candidate)) {
6222
6621
  return candidate;
6223
6622
  }
6224
6623
  }
@@ -6226,15 +6625,15 @@ var firstExisting = (root, names) => {
6226
6625
  };
6227
6626
  var findConfigFile = (root) => firstExisting(root, CONFIG_FILENAMES);
6228
6627
  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;
6628
+ const absoluteRoot = resolve4(root);
6629
+ const contentRoot = isAbsolute3(config.content.root) ? config.content.root : join13(absoluteRoot, config.content.root);
6630
+ const pagesPath = join13(absoluteRoot, config.content.pages);
6631
+ const pagesRoot = existsSync7(pagesPath) ? pagesPath : null;
6233
6632
  return {
6234
6633
  componentsFile: firstExisting(absoluteRoot, COMPONENTS_FILENAMES),
6235
6634
  configFile: findConfigFile(absoluteRoot),
6236
6635
  contentRoot,
6237
- outDir: join11(absoluteRoot, ".blume"),
6636
+ outDir: join13(absoluteRoot, ".blume"),
6238
6637
  pagesRoot,
6239
6638
  root: absoluteRoot,
6240
6639
  themeFile: firstExisting(absoluteRoot, THEME_FILENAMES)
@@ -6772,6 +7171,7 @@ var blumeConfigSchema = z2.object({
6772
7171
  contextual: contextualConfigSchema.default({}),
6773
7172
  deployment: deploymentConfigSchema.default({}),
6774
7173
  description: z2.string().optional(),
7174
+ examples: z2.string().default("examples"),
6775
7175
  export: exportConfigSchema.default(false),
6776
7176
  favicon: faviconConfigSchema.optional(),
6777
7177
  feedback: z2.boolean().default(true),
@@ -7183,7 +7583,7 @@ var buildContentGraph = (pages, options) => {
7183
7583
 
7184
7584
  // src/core/last-modified.ts
7185
7585
  import { execFileSync } from "node:child_process";
7186
- import { relative as relative6 } from "pathe";
7586
+ import { relative as relative7 } from "pathe";
7187
7587
  var resolveLastModifiedConfig = (value) => {
7188
7588
  if (value === false) {
7189
7589
  return { enabled: false, source: "git" };
@@ -7223,7 +7623,7 @@ var gitLastModifiedTimes = (root, contentRoot, sourcePaths) => {
7223
7623
  const byRepoPath = parseGitLog(output);
7224
7624
  const result = new Map;
7225
7625
  for (const sourcePath of sourcePaths) {
7226
- const iso = byRepoPath.get(relative6(gitRoot, sourcePath));
7626
+ const iso = byRepoPath.get(relative7(gitRoot, sourcePath));
7227
7627
  if (iso) {
7228
7628
  result.set(sourcePath, iso);
7229
7629
  }
@@ -7235,8 +7635,8 @@ var gitLastModifiedTimes = (root, contentRoot, sourcePaths) => {
7235
7635
  };
7236
7636
 
7237
7637
  // src/core/meta.ts
7238
- import { basename as basename2, dirname as dirname7, relative as relative7 } from "pathe";
7239
- import { glob as glob4 } from "tinyglobby";
7638
+ import { basename as basename3, dirname as dirname8, relative as relative8 } from "pathe";
7639
+ import { glob as glob5 } from "tinyglobby";
7240
7640
  var META_FILES = [
7241
7641
  "**/meta.ts",
7242
7642
  "**/meta.js",
@@ -7247,16 +7647,16 @@ var META_FILES = [
7247
7647
  ];
7248
7648
  var resolveMeta = async (mod) => typeof mod === "function" ? await mod() : mod;
7249
7649
  var discoverFolderMeta = async (contentRoot) => {
7250
- const files = await glob4(META_FILES, {
7650
+ const files = await glob5(META_FILES, {
7251
7651
  absolute: true,
7252
7652
  cwd: contentRoot,
7253
7653
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
7254
7654
  onlyFiles: true
7255
7655
  });
7256
- const load = createModuleLoader();
7656
+ const load2 = createModuleLoader();
7257
7657
  const loaded = await Promise.all(files.map(async (file) => {
7258
7658
  try {
7259
- return { file, ok: true, value: await resolveMeta(await load(file)) };
7659
+ return { file, ok: true, value: await resolveMeta(await load2(file)) };
7260
7660
  } catch (error) {
7261
7661
  return { error, file, ok: false };
7262
7662
  }
@@ -7265,7 +7665,7 @@ var discoverFolderMeta = async (contentRoot) => {
7265
7665
  const shared = new Map;
7266
7666
  const diagnostics = [];
7267
7667
  for (const entry of loaded) {
7268
- const dir = relative7(contentRoot, dirname7(entry.file));
7668
+ const dir = relative8(contentRoot, dirname8(entry.file));
7269
7669
  if (!entry.ok) {
7270
7670
  diagnostics.push({
7271
7671
  code: "BLUME_META_LOAD_FAILED",
@@ -7277,7 +7677,7 @@ var discoverFolderMeta = async (contentRoot) => {
7277
7677
  }
7278
7678
  const result = folderMetaSchema.safeParse(entry.value);
7279
7679
  if (result.success) {
7280
- const target = basename2(entry.file).startsWith("meta.$.") ? shared : meta;
7680
+ const target = basename3(entry.file).startsWith("meta.$.") ? shared : meta;
7281
7681
  target.set(dir, result.data);
7282
7682
  } else {
7283
7683
  diagnostics.push(...diagnosticsFromZod(result.error, {
@@ -7290,6 +7690,7 @@ var discoverFolderMeta = async (contentRoot) => {
7290
7690
  };
7291
7691
 
7292
7692
  // src/core/sources/normalize.ts
7693
+ import GithubSlugger from "github-slugger";
7293
7694
  import { extname as extname3 } from "pathe";
7294
7695
  var NUMERIC_PREFIX2 = /^\d+[-_.]/u;
7295
7696
  var GROUP_FOLDER2 = /^\((?<label>.+)\)$/u;
@@ -7322,6 +7723,7 @@ var CODE_FENCE2 = /^```/u;
7322
7723
  var ATX_HEADING = /^(?<hashes>#{1,6})\s+(?<text>.+?)\s*#*$/u;
7323
7724
  var extractHeadings = (body) => {
7324
7725
  const headings = [];
7726
+ const slugger = new GithubSlugger;
7325
7727
  let inFence = false;
7326
7728
  for (const line of body.split(`
7327
7729
  `)) {
@@ -7336,7 +7738,7 @@ var extractHeadings = (body) => {
7336
7738
  if (match?.groups) {
7337
7739
  const depth = match.groups.hashes?.length ?? 1;
7338
7740
  const text = (match.groups.text ?? "").trim();
7339
- headings.push({ depth, slug: slugify2(text), text });
7741
+ headings.push({ depth, slug: slugger.slug(text), text });
7340
7742
  }
7341
7743
  }
7342
7744
  return headings;
@@ -7433,18 +7835,17 @@ var normalizeEntry = (entry, ctx) => {
7433
7835
  };
7434
7836
 
7435
7837
  // src/core/sources/resolve.ts
7436
- import { join as join20 } from "pathe";
7838
+ import { join as join22 } from "pathe";
7437
7839
 
7438
7840
  // 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";
7841
+ import { existsSync as existsSync8, watch as fsWatch } from "node:fs";
7842
+ import { readFile as readFile10 } from "node:fs/promises";
7843
+ import { extname as extname4, isAbsolute as isAbsolute4, join as join14, relative as relative9, resolve as resolve5 } from "pathe";
7844
+ import { glob as glob6 } from "tinyglobby";
7444
7845
  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, {
7846
+ const contentRoot = isAbsolute4(options.root) ? options.root : join14(resolve5(options.projectRoot), options.root);
7847
+ const load2 = async () => {
7848
+ const files = await glob6(options.include, {
7448
7849
  absolute: true,
7449
7850
  cwd: contentRoot,
7450
7851
  ignore: options.exclude,
@@ -7452,21 +7853,21 @@ var filesystemSource = (options) => {
7452
7853
  });
7453
7854
  files.sort();
7454
7855
  const entries = await Promise.all(files.map(async (file) => {
7455
- const source = await readFile9(file, "utf-8");
7856
+ const source = await readFile10(file, "utf-8");
7456
7857
  const ext = extname4(file).toLowerCase();
7457
7858
  const format = ext === ".mdx" ? "mdx" : "md";
7458
- const parsed = matter3(source);
7859
+ const parsed = frontmatter_default(source);
7459
7860
  return {
7460
7861
  body: { format, text: parsed.content },
7461
7862
  data: parsed.data,
7462
- ref: relative8(contentRoot, file),
7863
+ ref: relative9(contentRoot, file),
7463
7864
  sourcePath: file
7464
7865
  };
7465
7866
  }));
7466
7867
  return { diagnostics: [], entries };
7467
7868
  };
7468
7869
  const validate = () => {
7469
- if (!existsSync7(contentRoot)) {
7870
+ if (!existsSync8(contentRoot)) {
7470
7871
  throw new BlumeError({
7471
7872
  code: options.missingCode ?? "BLUME_CONTENT_ROOT_MISSING",
7472
7873
  file: contentRoot,
@@ -7477,7 +7878,7 @@ var filesystemSource = (options) => {
7477
7878
  }
7478
7879
  };
7479
7880
  const watch = (onChange) => {
7480
- if (!existsSync7(contentRoot)) {
7881
+ if (!existsSync8(contentRoot)) {
7481
7882
  return () => {};
7482
7883
  }
7483
7884
  const watcher = fsWatch(contentRoot, { recursive: true }, onChange);
@@ -7485,22 +7886,19 @@ var filesystemSource = (options) => {
7485
7886
  };
7486
7887
  return {
7487
7888
  contentRoot,
7488
- load,
7889
+ load: load2,
7489
7890
  name: options.name,
7490
7891
  prefix: options.prefix,
7491
- read: (ref) => readFile9(join12(contentRoot, ref), "utf-8"),
7892
+ read: (ref) => readFile10(join14(contentRoot, ref), "utf-8"),
7492
7893
  staged: false,
7493
7894
  validate,
7494
7895
  watch
7495
7896
  };
7496
7897
  };
7497
7898
 
7498
- // src/core/sources/mdx-remote.ts
7499
- import matter4 from "gray-matter";
7500
-
7501
7899
  // 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";
7900
+ import { mkdir as mkdir3, readFile as readFile11, writeFile as writeFile3 } from "node:fs/promises";
7901
+ import { join as join15 } from "pathe";
7504
7902
  var hashText = (text) => {
7505
7903
  let hash = 5381;
7506
7904
  for (let i = 0;i < text.length; i += 1) {
@@ -7509,11 +7907,11 @@ var hashText = (text) => {
7509
7907
  return hash.toString(36);
7510
7908
  };
7511
7909
  var entriesDigest = (entries) => hashText(entries.map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`).join("|"));
7512
- var pollingWatch = (load, intervalSeconds) => (onChange) => {
7910
+ var pollingWatch = (load2, intervalSeconds) => (onChange) => {
7513
7911
  let last = "";
7514
7912
  const tick = async () => {
7515
7913
  try {
7516
- const { entries } = await load();
7914
+ const { entries } = await load2();
7517
7915
  const next = entriesDigest(entries);
7518
7916
  if (last && next !== last) {
7519
7917
  onChange();
@@ -7527,11 +7925,11 @@ var pollingWatch = (load, intervalSeconds) => (onChange) => {
7527
7925
  return () => clearInterval(timer);
7528
7926
  };
7529
7927
  var snapshotCache = (cacheDir) => {
7530
- const file = join13(cacheDir, "entries.json");
7928
+ const file = join15(cacheDir, "entries.json");
7531
7929
  return {
7532
7930
  read: async () => {
7533
7931
  try {
7534
- return JSON.parse(await readFile10(file, "utf-8"));
7932
+ return JSON.parse(await readFile11(file, "utf-8"));
7535
7933
  } catch {
7536
7934
  return [];
7537
7935
  }
@@ -7663,7 +8061,7 @@ var mdxRemoteSource = (options, ctx) => {
7663
8061
  throw new Error(`${item.fetchUrl} -> ${res.status}`);
7664
8062
  }
7665
8063
  const text = await res.text();
7666
- const parsed = matter4(text);
8064
+ const parsed = frontmatter_default(text);
7667
8065
  const format = item.ref.toLowerCase().endsWith(".mdx") ? "mdx" : "md";
7668
8066
  return {
7669
8067
  body: { format, text: parsed.content },
@@ -7674,7 +8072,7 @@ var mdxRemoteSource = (options, ctx) => {
7674
8072
  ref: item.ref
7675
8073
  };
7676
8074
  };
7677
- const load = async () => {
8075
+ const load2 = async () => {
7678
8076
  const result = await loadWithCache(options.name, cache, async () => {
7679
8077
  const refs = await enumerate();
7680
8078
  return await Promise.all(refs.map(fetchEntry));
@@ -7692,35 +8090,91 @@ var mdxRemoteSource = (options, ctx) => {
7692
8090
  return entry?.raw ?? entry?.body.text ?? "";
7693
8091
  };
7694
8092
  return {
7695
- load,
8093
+ load: load2,
7696
8094
  name: options.name,
7697
8095
  prefix: options.prefix,
7698
8096
  read,
7699
8097
  staged: true,
7700
- watch: options.pollInterval ? pollingWatch(load, options.pollInterval) : undefined
8098
+ watch: options.pollInterval ? pollingWatch(load2, options.pollInterval) : undefined
7701
8099
  };
7702
8100
  };
7703
8101
 
7704
8102
  // 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";
8103
+ import { existsSync as existsSync10, watch as fsWatch2 } from "node:fs";
8104
+ import { readFile as readFile13 } from "node:fs/promises";
8105
+ import { isAbsolute as isAbsolute5, join as join18, relative as relative12, resolve as resolve7 } from "pathe";
8106
+ import { glob as glob7 } from "tinyglobby";
7713
8107
 
7714
8108
  // src/migrate/shared.ts
7715
- import { writeFile as writeFile4 } from "node:fs/promises";
7716
- import { join as join14 } from "pathe";
8109
+ import { existsSync as existsSync9 } from "node:fs";
8110
+ import { readFile as readFile12, writeFile as writeFile4 } from "node:fs/promises";
8111
+ import { join as join16 } from "pathe";
7717
8112
  var writeBlumeConfig = async (root, config) => {
7718
8113
  const body = `import { defineConfig } from "blume";
7719
8114
 
7720
8115
  export default defineConfig(${JSON.stringify(config, null, 2)});
7721
8116
  `;
7722
- await writeFile4(join14(root, "blume.config.ts"), body, "utf-8");
8117
+ await writeFile4(join16(root, "blume.config.ts"), body, "utf-8");
8118
+ };
8119
+ var BLUME_SCRIPTS = {
8120
+ build: "blume build",
8121
+ dev: "blume dev",
8122
+ start: "blume preview"
8123
+ };
8124
+ var rewriteFrameworkScripts = async (root, cli, remove) => {
8125
+ const pkgPath = join16(root, "package.json");
8126
+ if (!existsSync9(pkgPath)) {
8127
+ return false;
8128
+ }
8129
+ let pkg;
8130
+ try {
8131
+ pkg = JSON.parse(await readFile12(pkgPath, "utf-8"));
8132
+ } catch {
8133
+ return false;
8134
+ }
8135
+ const { scripts } = pkg;
8136
+ if (!scripts || typeof scripts !== "object") {
8137
+ return false;
8138
+ }
8139
+ const next = {};
8140
+ let changed = false;
8141
+ for (const [name, command] of Object.entries(scripts)) {
8142
+ const blume = BLUME_SCRIPTS[name];
8143
+ if (typeof command === "string" && remove?.test(command)) {
8144
+ changed = true;
8145
+ } else if (typeof command === "string" && blume && cli.test(command) && command !== blume) {
8146
+ next[name] = blume;
8147
+ changed = true;
8148
+ } else {
8149
+ next[name] = command;
8150
+ }
8151
+ }
8152
+ if (changed) {
8153
+ pkg.scripts = next;
8154
+ await writeFile4(pkgPath, `${JSON.stringify(pkg, null, 2)}
8155
+ `, "utf-8");
8156
+ }
8157
+ return changed;
7723
8158
  };
8159
+ var gitignoreKey = (line) => line.trim().replace(/\/+$/u, "");
8160
+ var ensureGitignore = async (root, entries) => {
8161
+ const path = join16(root, ".gitignore");
8162
+ const existing = existsSync9(path) ? await readFile12(path, "utf-8") : "";
8163
+ const present = new Set(existing.split(`
8164
+ `).map(gitignoreKey).filter(Boolean));
8165
+ const added = entries.filter((entry) => !present.has(gitignoreKey(entry)));
8166
+ if (added.length === 0) {
8167
+ return [];
8168
+ }
8169
+ const gap = existing.length > 0 && !existing.endsWith(`
8170
+ `) ? `
8171
+ ` : "";
8172
+ await writeFile4(path, `${existing}${gap}${added.join(`
8173
+ `)}
8174
+ `, "utf-8");
8175
+ return added;
8176
+ };
8177
+ var leftoverFiles = (root, candidates) => candidates.filter((candidate) => existsSync9(join16(root, candidate)));
7724
8178
  var attribute = (attrs, name) => {
7725
8179
  const match = attrs.match(new RegExp(`\\b${name}=(?:"(?<dq>[^"]*)"|'(?<sq>[^']*)')`, "u"));
7726
8180
  return match?.groups?.dq ?? match?.groups?.sq;
@@ -8117,7 +8571,7 @@ var isLiteralObject = (value) => typeof value === "object" && value !== null &&
8117
8571
  var asLiteralArray = (value) => Array.isArray(value) ? value : undefined;
8118
8572
 
8119
8573
  // src/migrate/mintlify/content.ts
8120
- import { dirname as dirname8, join as join15, relative as relative9 } from "pathe";
8574
+ import { dirname as dirname9, join as join17, relative as relative10 } from "pathe";
8121
8575
  var CALLOUT_DIRECTIVES = {
8122
8576
  Check: "success",
8123
8577
  Danger: "danger",
@@ -8153,9 +8607,9 @@ var rewriteSnippetImports = (source, options) => {
8153
8607
  if (/\.mdx?$/u.test(importSource)) {
8154
8608
  return "";
8155
8609
  }
8156
- const target = join15(options.root, importSource.replace(/^\/+/u, ""));
8610
+ const target = join17(options.root, importSource.replace(/^\/+/u, ""));
8157
8611
  components.push(importSource.replace(/^\/+/u, ""));
8158
- let rel = relative9(dirname8(options.filePath), target);
8612
+ let rel = relative10(dirname9(options.filePath), target);
8159
8613
  if (!rel.startsWith(".")) {
8160
8614
  rel = `./${rel}`;
8161
8615
  }
@@ -8345,8 +8799,7 @@ var rewriteMintlifySvgIconProps = (source) => {
8345
8799
 
8346
8800
  // src/migrate/mintlify/snippets.ts
8347
8801
  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";
8802
+ import { dirname as dirname10, relative as relative11, resolve as resolve6 } from "pathe";
8350
8803
  var MARKDOWN_SNIPPET_IMPORT = /^import\s+(?<name>[$A-Z_a-z][$\w]*)\s+from\s+["'](?<source>[^"']+\.mdx?)["'];?\s*$/gmu;
8351
8804
  var NAMED_SNIPPET_IMPORT = /^import\s+\{(?<names>[^}]+)\}\s+from\s+["'](?<source>[^"']+\.mdx?)["'];?\s*$/gmu;
8352
8805
  var EXPORTED_STRING_CONST = /^export\s+const\s+(?<name>[$A-Z_a-z][$\w]*)\s*=\s*(?:"(?<double>(?:\\.|[^"\\])*)"|'(?<single>(?:\\.|[^'\\])*)'|`(?<template>(?:\\.|[^`\\])*)`)\s*;?\s*$/gmu;
@@ -8356,14 +8809,14 @@ var GLOBAL_VARIABLE = /\{\{\s*(?<name>[A-Za-z0-9-]+)\s*\}\}/gu;
8356
8809
  var FRONTMATTER_BLOCK = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/u;
8357
8810
  var USER_EXPORT = /^(?:export\s+)?(?:const|let|var)\s+user\s*=|^import\s+\{\s*user\s*\}/mu;
8358
8811
  var isInsideRoot2 = (root, candidate) => {
8359
- const rel = relative10(root, candidate);
8812
+ const rel = relative11(root, candidate);
8360
8813
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
8361
8814
  };
8362
8815
  var escapeRegExp = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
8363
8816
  var snippetSelfClosingTagPattern = (name) => new RegExp(`<${escapeRegExp(name)}(?<attrs>[^>]*?)\\s*/>`, "gu");
8364
8817
  var snippetPairedTagPattern = (name) => new RegExp(`<${escapeRegExp(name)}(?<attrs>[^>]*?)>[\\s\\S]*?</${escapeRegExp(name)}>`, "gu");
8365
8818
  var rootRelativePath = (root, file) => {
8366
- const rel = relative10(root, file);
8819
+ const rel = relative11(root, file);
8367
8820
  return rel ? `/${rel}` : "/";
8368
8821
  };
8369
8822
  var snippetCycleMessage = (root, file, trail) => {
@@ -8372,7 +8825,7 @@ var snippetCycleMessage = (root, file, trail) => {
8372
8825
  return `Circular Mintlify snippet import detected: ${cycle}`;
8373
8826
  };
8374
8827
  var resolveSnippetPath = (options) => {
8375
- const target = options.source.startsWith("/") ? resolve5(options.root, options.source.slice(1)) : resolve5(dirname9(options.filePath), options.source);
8828
+ const target = options.source.startsWith("/") ? resolve6(options.root, options.source.slice(1)) : resolve6(dirname10(options.filePath), options.source);
8376
8829
  return isInsideRoot2(options.root, target) ? target : null;
8377
8830
  };
8378
8831
  var collectImports = (source) => [...source.matchAll(MARKDOWN_SNIPPET_IMPORT)].flatMap((match) => {
@@ -8435,10 +8888,10 @@ var rewriteMintlifyMarkdownSnippets = async (source, options) => {
8435
8888
  throw new Error(snippetCycleMessage(options.root, file, options.trail ?? []));
8436
8889
  }
8437
8890
  seen.add(file);
8438
- const readFile11 = options.readFile ?? readFileFromDisk;
8891
+ const readFile13 = options.readFile ?? readFileFromDisk;
8439
8892
  try {
8440
- const raw = await readFile11(file);
8441
- const content = matter5(raw).content.trim();
8893
+ const raw = await readFile13(file);
8894
+ const content = frontmatter_default(raw).content.trim();
8442
8895
  const transformed = await rewriteMintlifyMarkdownSnippets(content, {
8443
8896
  ...options,
8444
8897
  filePath: file,
@@ -8478,7 +8931,7 @@ var rewriteMintlifyMarkdownSnippets = async (source, options) => {
8478
8931
  return await inlineAt(0, source);
8479
8932
  };
8480
8933
  var rewriteMintlifySnippetVariables = async (source, options) => {
8481
- const readFile11 = options.readFile ?? readFileFromDisk;
8934
+ const readFile13 = options.readFile ?? readFileFromDisk;
8482
8935
  const inlineImport = async (current, variableImport) => {
8483
8936
  const file = resolveSnippetPath({
8484
8937
  filePath: options.filePath,
@@ -8488,7 +8941,7 @@ var rewriteMintlifySnippetVariables = async (source, options) => {
8488
8941
  if (!file) {
8489
8942
  return current;
8490
8943
  }
8491
- const exports = collectStringExports(matter5(await readFile11(file)).content);
8944
+ const exports = collectStringExports(frontmatter_default(await readFile13(file)).content);
8492
8945
  let next = current;
8493
8946
  for (const name of variableImport.names) {
8494
8947
  const value = exports.get(name.imported);
@@ -8544,10 +8997,10 @@ var transformMintlifyContent = async (raw, options) => {
8544
8997
  text = rewriteMintlifyExampleBlocks(text);
8545
8998
  text = rewriteMintlifyCallouts(text);
8546
8999
  const unsupported = unsupportedMintlifyComponents(text);
8547
- const parsed = matter6(text);
9000
+ const parsed = frontmatter_default(text);
8548
9001
  const mapped = normalizeMintlifyPageMeta(parsed.data);
8549
9002
  const { data, removed } = stripUnknownPageMeta(mapped);
8550
- const content = Object.keys(data).length > 0 ? matter6.stringify(parsed.content, data) : parsed.content;
9003
+ const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
8551
9004
  return {
8552
9005
  components: snippetImports.components,
8553
9006
  content,
@@ -8566,15 +9019,15 @@ var MINTLIFY_SOURCE_IGNORES = [
8566
9019
  "snippets/**"
8567
9020
  ];
8568
9021
  var mintlifySource = (options) => {
8569
- const contentRoot = isAbsolute4(options.root) ? options.root : join16(resolve6(options.projectRoot), options.root);
9022
+ const contentRoot = isAbsolute5(options.root) ? options.root : join18(resolve7(options.projectRoot), options.root);
8570
9023
  const ignore = [...new Set([...options.exclude, ...MINTLIFY_SOURCE_IGNORES])];
8571
9024
  const transform = (raw, file) => transformMintlifyContent(raw, {
8572
9025
  filePath: file,
8573
- root: resolve6(options.projectRoot),
9026
+ root: resolve7(options.projectRoot),
8574
9027
  variables: options.variables
8575
9028
  });
8576
- const load = async () => {
8577
- const files = await glob6(options.include, {
9029
+ const load2 = async () => {
9030
+ const files = await glob7(options.include, {
8578
9031
  absolute: true,
8579
9032
  cwd: contentRoot,
8580
9033
  ignore,
@@ -8583,16 +9036,16 @@ var mintlifySource = (options) => {
8583
9036
  files.sort();
8584
9037
  const unsupported = new Set;
8585
9038
  const entries = await Promise.all(files.map(async (file) => {
8586
- const result = await transform(await readFile11(file, "utf-8"), file);
9039
+ const result = await transform(await readFile13(file, "utf-8"), file);
8587
9040
  for (const name of result.unsupported) {
8588
9041
  unsupported.add(name);
8589
9042
  }
8590
- const parsed = matter7(result.content);
9043
+ const parsed = frontmatter_default(result.content);
8591
9044
  return {
8592
9045
  body: { format: "mdx", text: parsed.content },
8593
9046
  data: parsed.data,
8594
9047
  raw: result.content,
8595
- ref: relative11(contentRoot, file),
9048
+ ref: relative12(contentRoot, file),
8596
9049
  sourcePath: file
8597
9050
  };
8598
9051
  }));
@@ -8606,7 +9059,7 @@ var mintlifySource = (options) => {
8606
9059
  return { diagnostics, entries };
8607
9060
  };
8608
9061
  const validate = () => {
8609
- if (!existsSync8(contentRoot)) {
9062
+ if (!existsSync10(contentRoot)) {
8610
9063
  throw new BlumeError({
8611
9064
  code: "BLUME_CONTENT_ROOT_MISSING",
8612
9065
  file: contentRoot,
@@ -8618,11 +9071,11 @@ var mintlifySource = (options) => {
8618
9071
  };
8619
9072
  const watch = (onChange) => {
8620
9073
  const disposers = [];
8621
- if (existsSync8(contentRoot)) {
9074
+ if (existsSync10(contentRoot)) {
8622
9075
  const watcher = fsWatch2(contentRoot, { recursive: true }, onChange);
8623
9076
  disposers.push(() => watcher.close());
8624
9077
  }
8625
- if (options.configFile && existsSync8(options.configFile)) {
9078
+ if (options.configFile && existsSync10(options.configFile)) {
8626
9079
  const watcher = fsWatch2(options.configFile, onChange);
8627
9080
  disposers.push(() => watcher.close());
8628
9081
  }
@@ -8633,13 +9086,13 @@ var mintlifySource = (options) => {
8633
9086
  };
8634
9087
  };
8635
9088
  const read = async (ref) => {
8636
- const file = join16(contentRoot, ref);
8637
- const result = await transform(await readFile11(file, "utf-8"), file);
9089
+ const file = join18(contentRoot, ref);
9090
+ const result = await transform(await readFile13(file, "utf-8"), file);
8638
9091
  return result.content;
8639
9092
  };
8640
9093
  return {
8641
9094
  contentRoot,
8642
- load,
9095
+ load: load2,
8643
9096
  name: options.name,
8644
9097
  prefix: options.prefix,
8645
9098
  read,
@@ -8650,12 +9103,11 @@ var mintlifySource = (options) => {
8650
9103
  };
8651
9104
 
8652
9105
  // src/core/sources/notion.ts
8653
- import matter8 from "gray-matter";
8654
- import { join as join18 } from "pathe";
9106
+ import { join as join20 } from "pathe";
8655
9107
 
8656
9108
  // src/core/sources/assets.ts
8657
9109
  import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
8658
- import { extname as extname5, join as join17 } from "pathe";
9110
+ import { extname as extname5, join as join19 } from "pathe";
8659
9111
  var MD_IMAGE = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
8660
9112
  var REMOTE = /^https?:\/\//u;
8661
9113
  var SAFE_EXT = /^\.[a-z0-9]+$/iu;
@@ -8684,7 +9136,7 @@ var materializeAssets = async (markdown, ctx) => {
8684
9136
  const bytes = new Uint8Array(await res.arrayBuffer());
8685
9137
  const file = `${hashText(url)}${extFor(url)}`;
8686
9138
  await mkdir4(ctx.assetsDir, { recursive: true });
8687
- await writeFile5(join17(ctx.assetsDir, file), bytes);
9139
+ await writeFile5(join19(ctx.assetsDir, file), bytes);
8688
9140
  rewrites.set(url, `${ctx.assetsBaseUrl}/${file}`);
8689
9141
  } catch (error) {
8690
9142
  diagnostics.push({
@@ -8778,8 +9230,8 @@ ${text}
8778
9230
  };
8779
9231
  var notionSource = (options, ctx) => {
8780
9232
  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);
9233
+ const cache = snapshotCache(ctx?.cacheDir ?? join20(".blume", "cache", options.name));
9234
+ const assetsDir = ctx?.assetsDir ?? join20(".blume", "public", "blume-assets", options.name);
8783
9235
  const assetsBaseUrl = ctx?.assetsBaseUrl ?? `/blume-assets/${options.name}`;
8784
9236
  let snapshot = new Map;
8785
9237
  const resolveClient = async () => {
@@ -8895,7 +9347,7 @@ ${rendered.join(`
8895
9347
  assetsDir,
8896
9348
  fetchImpl: options.fetchImpl
8897
9349
  });
8898
- const raw = matter8.stringify(assets.markdown, data);
9350
+ const raw = frontmatter_default.stringify(assets.markdown, data);
8899
9351
  return {
8900
9352
  diagnostics: assets.diagnostics,
8901
9353
  entry: {
@@ -8908,7 +9360,7 @@ ${rendered.join(`
8908
9360
  }
8909
9361
  };
8910
9362
  };
8911
- const load = async () => {
9363
+ const load2 = async () => {
8912
9364
  const assetDiagnostics = [];
8913
9365
  const result = await loadWithCache(options.name, cache, async () => {
8914
9366
  const client = await resolveClient();
@@ -8937,18 +9389,17 @@ ${rendered.join(`
8937
9389
  return all.find((e) => e.ref === ref)?.raw ?? "";
8938
9390
  };
8939
9391
  return {
8940
- load,
9392
+ load: load2,
8941
9393
  name: options.name,
8942
9394
  prefix: options.prefix,
8943
9395
  read,
8944
9396
  staged: true,
8945
- watch: options.pollInterval ? pollingWatch(load, options.pollInterval) : undefined
9397
+ watch: options.pollInterval ? pollingWatch(load2, options.pollInterval) : undefined
8946
9398
  };
8947
9399
  };
8948
9400
 
8949
9401
  // src/core/sources/sanity.ts
8950
- import matter9 from "gray-matter";
8951
- import { join as join19 } from "pathe";
9402
+ import { join as join21 } from "pathe";
8952
9403
 
8953
9404
  // src/core/sources/portable-text.ts
8954
9405
  var HEADING_STYLES = {
@@ -9087,7 +9538,7 @@ var resolveClient = async (options, preview) => {
9087
9538
  };
9088
9539
  var sanitySource = (options, ctx) => {
9089
9540
  const fields = options.fields ?? {};
9090
- const cache = snapshotCache(ctx?.cacheDir ?? join19(".blume", "cache", options.name));
9541
+ const cache = snapshotCache(ctx?.cacheDir ?? join21(".blume", "cache", options.name));
9091
9542
  let snapshot = new Map;
9092
9543
  const toEntry = (doc) => {
9093
9544
  const slugValue = asString2(getPath(doc, fields.slug ?? "slug.current")) ?? asString2(doc._id) ?? "untitled";
@@ -9109,7 +9560,7 @@ var sanitySource = (options, ctx) => {
9109
9560
  },
9110
9561
  serializers: options.serializers
9111
9562
  }) : "";
9112
- const raw = matter9.stringify(markdown, data);
9563
+ const raw = frontmatter_default.stringify(markdown, data);
9113
9564
  return {
9114
9565
  body: { format: "md", text: markdown },
9115
9566
  data,
@@ -9119,7 +9570,7 @@ var sanitySource = (options, ctx) => {
9119
9570
  ref: `${slug}.md`
9120
9571
  };
9121
9572
  };
9122
- const load = async () => {
9573
+ const load2 = async () => {
9123
9574
  const result = await loadWithCache(options.name, cache, async () => {
9124
9575
  const client = await resolveClient(options, ctx?.preview ?? false);
9125
9576
  const docs = await client.fetch(options.query);
@@ -9137,12 +9588,12 @@ var sanitySource = (options, ctx) => {
9137
9588
  return all.find((e) => e.ref === ref)?.raw ?? "";
9138
9589
  };
9139
9590
  return {
9140
- load,
9591
+ load: load2,
9141
9592
  name: options.name,
9142
9593
  prefix: options.prefix,
9143
9594
  read,
9144
9595
  staged: true,
9145
- watch: options.pollInterval ? pollingWatch(load, options.pollInterval) : undefined
9596
+ watch: options.pollInterval ? pollingWatch(load2, options.pollInterval) : undefined
9146
9597
  };
9147
9598
  };
9148
9599
 
@@ -9162,8 +9613,8 @@ var uniqueNamer = () => {
9162
9613
  };
9163
9614
  var sourceContext = (context, name, runtime) => ({
9164
9615
  assetsBaseUrl: `/blume-assets/${name}`,
9165
- assetsDir: join20(context.outDir, "public", "blume-assets", name),
9166
- cacheDir: join20(context.outDir, "cache", name),
9616
+ assetsDir: join22(context.outDir, "public", "blume-assets", name),
9617
+ cacheDir: join22(context.outDir, "cache", name),
9167
9618
  mode: runtime.mode,
9168
9619
  preview: runtime.preview,
9169
9620
  projectRoot: context.root,
@@ -9396,7 +9847,7 @@ var buildCommand = defineCommand2({
9396
9847
  logLevel: "info",
9397
9848
  root: project.context.outDir
9398
9849
  });
9399
- const distDir = join21(root, "dist");
9850
+ const distDir = join23(root, "dist");
9400
9851
  if (project.config.search.provider === "pagefind") {
9401
9852
  logger.start("Building search index");
9402
9853
  const indexed = await buildSearchIndex(distDir);
@@ -9410,19 +9861,19 @@ var buildCommand = defineCommand2({
9410
9861
  if (project.config.ai.llmsTxt) {
9411
9862
  const { index, full } = await buildLlmsFiles(project);
9412
9863
  await Promise.all([
9413
- writeFile6(join21(distDir, "llms.txt"), index, "utf-8"),
9414
- writeFile6(join21(distDir, "llms-full.txt"), full, "utf-8")
9864
+ writeFile6(join23(distDir, "llms.txt"), index, "utf-8"),
9865
+ writeFile6(join23(distDir, "llms-full.txt"), full, "utf-8")
9415
9866
  ]);
9416
9867
  logger.success("Generated llms.txt and llms-full.txt");
9417
9868
  }
9418
9869
  const sitemap = buildSitemap(project);
9419
- if (sitemap && !existsSync9(join21(distDir, "sitemap.xml"))) {
9420
- await writeFile6(join21(distDir, "sitemap.xml"), sitemap, "utf-8");
9870
+ if (sitemap && !existsSync11(join23(distDir, "sitemap.xml"))) {
9871
+ await writeFile6(join23(distDir, "sitemap.xml"), sitemap, "utf-8");
9421
9872
  logger.success("Generated sitemap.xml");
9422
9873
  }
9423
9874
  const robots = buildRobots(project);
9424
- if (robots && !existsSync9(join21(distDir, "robots.txt"))) {
9425
- await writeFile6(join21(distDir, "robots.txt"), robots, "utf-8");
9875
+ if (robots && !existsSync11(join23(distDir, "robots.txt"))) {
9876
+ await writeFile6(join23(distDir, "robots.txt"), robots, "utf-8");
9426
9877
  logger.success("Generated robots.txt");
9427
9878
  }
9428
9879
  const { config } = project;
@@ -9590,39 +10041,47 @@ var doctorCommand = defineCommand4({
9590
10041
  });
9591
10042
 
9592
10043
  // src/cli/commands/eject.ts
9593
- import { readFile as readFile13, writeFile as writeFile8 } from "node:fs/promises";
10044
+ import { readFile as readFile15, writeFile as writeFile8 } from "node:fs/promises";
9594
10045
  import { defineCommand as defineCommand5 } from "citty";
9595
- import { join as join23, relative as relative13 } from "pathe";
10046
+ import { join as join25, relative as relative14 } from "pathe";
9596
10047
 
9597
10048
  // 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";
10049
+ import { existsSync as existsSync12 } from "node:fs";
10050
+ import { cp, mkdir as mkdir5, readFile as readFile14, rm as rm2, writeFile as writeFile7 } from "node:fs/promises";
10051
+ import { join as join24, relative as relative13 } from "pathe";
9601
10052
  var POSIX = (path) => path.split("\\").join("/");
9602
10053
  var eject = async (root) => {
9603
10054
  const project = await scanProject(root, { mode: "build" });
9604
10055
  const { context, config } = project;
9605
- const srcDir = join22(root, "src");
9606
- const genDir = join22(srcDir, "generated");
10056
+ const srcDir = join24(root, "src");
10057
+ const genDir = join24(srcDir, "generated");
9607
10058
  const askEnabled = config.ai.ask?.enabled ?? false;
9608
10059
  const exportPdf = config.export.pdf;
9609
10060
  const exportEpub = config.export.epub;
9610
- const [pages, needsReactRaw, userTheme, rawMarkdown] = await Promise.all([
10061
+ const [pages, needsReactRaw, userTheme, rawMarkdown, islands, examples] = await Promise.all([
9611
10062
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
9612
10063
  detectNeedsReact(root),
9613
- context.themeFile ? readFile12(context.themeFile, "utf-8") : Promise.resolve(""),
9614
- buildRawMarkdown(project)
10064
+ context.themeFile ? readFile14(context.themeFile, "utf-8") : Promise.resolve(""),
10065
+ buildRawMarkdown(project),
10066
+ discoverIslands(root),
10067
+ discoverExamples(root)
10068
+ ]);
10069
+ const frameworks = new Set([
10070
+ ...islands.islands.map((island) => island.framework),
10071
+ ...examples.examples.map((example) => example.framework)
9615
10072
  ]);
9616
- const needsReact = needsReactRaw || askEnabled;
10073
+ const needsReact = needsReactRaw || askEnabled || frameworks.has("react");
10074
+ const needsVue = frameworks.has("vue");
10075
+ const needsSvelte = frameworks.has("svelte");
9617
10076
  const relContext = {
9618
10077
  ...context,
9619
- contentRoot: POSIX(relative12(root, context.contentRoot)),
10078
+ contentRoot: POSIX(relative13(root, context.contentRoot)),
9620
10079
  outDir: ".",
9621
10080
  root: "."
9622
10081
  };
9623
- const componentsImport = context.componentsFile ? `../../${POSIX(relative12(root, context.componentsFile))}` : null;
10082
+ const componentsImport = context.componentsFile ? `../../${POSIX(relative13(root, context.componentsFile))}` : null;
9624
10083
  const relPages = pages.map((page) => ({
9625
- entrypoint: POSIX(relative12(root, page.entrypoint)),
10084
+ entrypoint: POSIX(relative13(root, page.entrypoint)),
9626
10085
  pattern: page.pattern
9627
10086
  }));
9628
10087
  const staged = collectStaged(project);
@@ -9635,18 +10094,21 @@ var eject = async (root) => {
9635
10094
  contentRoutes: project.manifest.routes.map((route) => route.path),
9636
10095
  context: relContext,
9637
10096
  dataPath: "./src/generated/data.json",
10097
+ examplesPath: "./src/generated/examples.ts",
9638
10098
  needsReact,
10099
+ needsSvelte,
10100
+ needsVue,
9639
10101
  pages: relPages,
9640
10102
  searchClientPath: "./src/generated/search-client.ts",
9641
10103
  themePath: "./src/generated/app.css"
9642
10104
  }),
9643
- path: join22(root, "astro.config.mjs")
10105
+ path: join24(root, "astro.config.mjs")
9644
10106
  },
9645
10107
  {
9646
10108
  content: runtimeTsconfigTemplate(),
9647
- path: join22(root, "tsconfig.json")
10109
+ path: join24(root, "tsconfig.json")
9648
10110
  },
9649
- { content: envTemplate(), path: join22(srcDir, "env.d.ts") },
10111
+ { content: envTemplate(), path: join24(srcDir, "env.d.ts") },
9650
10112
  {
9651
10113
  content: contentConfigTemplate({
9652
10114
  config,
@@ -9654,7 +10116,7 @@ var eject = async (root) => {
9654
10116
  staged: hasStaged,
9655
10117
  stagedBase: stagedDir
9656
10118
  }),
9657
- path: join22(srcDir, "content.config.ts")
10119
+ path: join24(srcDir, "content.config.ts")
9658
10120
  },
9659
10121
  {
9660
10122
  content: catchAllPageTemplate({
@@ -9663,11 +10125,19 @@ var eject = async (root) => {
9663
10125
  exportPdf,
9664
10126
  mathEnabled: config.markdown.math
9665
10127
  }),
9666
- path: join22(srcDir, "pages", "[...slug].astro")
10128
+ path: join24(srcDir, "pages", "[...slug].astro")
9667
10129
  },
9668
10130
  {
9669
10131
  content: userComponentsTemplate(componentsImport),
9670
- path: join22(genDir, "components.ts")
10132
+ path: join24(genDir, "components.ts")
10133
+ },
10134
+ {
10135
+ content: islandMapTemplate(islands.islands),
10136
+ path: join24(genDir, "islands.ts")
10137
+ },
10138
+ {
10139
+ content: exampleMapTemplate(examples.examples),
10140
+ path: join24(genDir, "examples.ts")
9671
10141
  },
9672
10142
  {
9673
10143
  content: tailwindEntryTemplate({
@@ -9679,54 +10149,54 @@ var eject = async (root) => {
9679
10149
  twoslashCss: twoslashCss(),
9680
10150
  userTheme
9681
10151
  }),
9682
- path: join22(genDir, "app.css")
10152
+ path: join24(genDir, "app.css")
9683
10153
  },
9684
- { content: buildRuntimeData(project), path: join22(genDir, "data.json") },
10154
+ { content: buildRuntimeData(project), path: join24(genDir, "data.json") },
9685
10155
  {
9686
10156
  content: `${JSON.stringify(rawMarkdown)}
9687
10157
  `,
9688
- path: join22(genDir, "raw-markdown.json")
10158
+ path: join24(genDir, "raw-markdown.json")
9689
10159
  },
9690
10160
  {
9691
10161
  content: rawMarkdownEndpointTemplate(),
9692
- path: join22(srcDir, "pages", "[...slug].md.ts")
10162
+ path: join24(srcDir, "pages", "[...slug].md.ts")
9693
10163
  },
9694
10164
  {
9695
10165
  content: rawMarkdownEndpointTemplate(),
9696
- path: join22(srcDir, "pages", "[...slug].mdx.ts")
10166
+ path: join24(srcDir, "pages", "[...slug].mdx.ts")
9697
10167
  }
9698
10168
  ];
9699
10169
  if (askEnabled) {
9700
10170
  files.push({
9701
10171
  content: askEndpointTemplate(resolveAskBackend(config.ai.ask)),
9702
- path: join22(srcDir, "pages", "api", "ask.ts")
10172
+ path: join24(srcDir, "pages", "api", "ask.ts")
9703
10173
  });
9704
10174
  }
9705
10175
  if (config.seo.og.enabled) {
9706
10176
  files.push({
9707
- content: ogEndpointTemplate(),
9708
- path: join22(srcDir, "pages", "og", "[...slug].png.ts")
10177
+ content: ogEndpointTemplate(customOgRoutes(pages, config.title, config.description)),
10178
+ path: join24(srcDir, "pages", "og", "[...slug].png.ts")
9709
10179
  });
9710
10180
  }
9711
10181
  files.push({
9712
10182
  content: searchClientTemplate(config),
9713
- path: join22(genDir, "search-client.ts")
10183
+ path: join24(genDir, "search-client.ts")
9714
10184
  });
9715
10185
  if (servesStaticIndex(config.search.provider)) {
9716
10186
  const documents = await buildSearchDocuments(project);
9717
10187
  files.push({
9718
10188
  content: `${JSON.stringify(documents)}
9719
10189
  `,
9720
- path: join22(genDir, "search.json")
10190
+ path: join24(genDir, "search.json")
9721
10191
  }, {
9722
10192
  content: searchEndpointTemplate(),
9723
- path: join22(srcDir, "pages", "blume-search.json.ts")
10193
+ path: join24(srcDir, "pages", "blume-search.json.ts")
9724
10194
  });
9725
10195
  }
9726
10196
  if (config.search.provider === "mixedbread") {
9727
10197
  files.push({
9728
10198
  content: mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""),
9729
- path: join22(srcDir, "pages", "api", "search.ts")
10199
+ path: join24(srcDir, "pages", "api", "search.ts")
9730
10200
  });
9731
10201
  }
9732
10202
  const feeds = buildRssFeeds(project);
@@ -9735,10 +10205,10 @@ var eject = async (root) => {
9735
10205
  files.push({
9736
10206
  content: `${JSON.stringify(feedXml)}
9737
10207
  `,
9738
- path: join22(genDir, "rss.json")
10208
+ path: join24(genDir, "rss.json")
9739
10209
  }, {
9740
10210
  content: rssEndpointTemplate(),
9741
- path: join22(srcDir, "pages", "[section]", "rss.xml.ts")
10211
+ path: join24(srcDir, "pages", "[section]", "rss.xml.ts")
9742
10212
  });
9743
10213
  }
9744
10214
  if (hasReferences(config)) {
@@ -9750,20 +10220,27 @@ var eject = async (root) => {
9750
10220
  for (const file of references.files) {
9751
10221
  files.push({
9752
10222
  content: file.content,
9753
- path: join22(srcDir, "pages", file.pagePath)
10223
+ path: join24(srcDir, "pages", file.pagePath)
9754
10224
  });
9755
10225
  }
9756
10226
  }
10227
+ files.push(...islands.islands.map((island) => ({
10228
+ content: islandWrapperTemplate(island),
10229
+ path: join24(genDir, "islands", `${island.name}.astro`)
10230
+ })), ...examples.examples.map((example) => ({
10231
+ content: exampleWrapperTemplate(example),
10232
+ path: join24(genDir, "examples", `${exampleSlug(example.path)}.astro`)
10233
+ })));
9757
10234
  for (const [entryId, content] of staged) {
9758
- files.push({ content, path: join22(root, stagedDir, entryId) });
10235
+ files.push({ content, path: join24(root, stagedDir, entryId) });
9759
10236
  }
9760
10237
  await Promise.all(files.map(async (file) => {
9761
- await mkdir5(join22(file.path, ".."), { recursive: true });
10238
+ await mkdir5(join24(file.path, ".."), { recursive: true });
9762
10239
  await writeFile7(file.path, file.content, "utf-8");
9763
10240
  }));
9764
- const assetsSrc = join22(context.outDir, "public", "blume-assets");
9765
- if (existsSync10(assetsSrc)) {
9766
- await cp(assetsSrc, join22(root, "public", "blume-assets"), {
10241
+ const assetsSrc = join24(context.outDir, "public", "blume-assets");
10242
+ if (existsSync12(assetsSrc)) {
10243
+ await cp(assetsSrc, join24(root, "public", "blume-assets"), {
9767
10244
  recursive: true
9768
10245
  });
9769
10246
  }
@@ -9773,10 +10250,10 @@ var eject = async (root) => {
9773
10250
 
9774
10251
  // src/cli/commands/eject.ts
9775
10252
  var updatePackageScripts = async (root) => {
9776
- const pkgPath = join23(root, "package.json");
10253
+ const pkgPath = join25(root, "package.json");
9777
10254
  let pkg;
9778
10255
  try {
9779
- pkg = JSON.parse(await readFile13(pkgPath, "utf-8"));
10256
+ pkg = JSON.parse(await readFile15(pkgPath, "utf-8"));
9780
10257
  } catch {
9781
10258
  return;
9782
10259
  }
@@ -9809,7 +10286,7 @@ var ejectCommand = defineCommand5({
9809
10286
  await updatePackageScripts(root);
9810
10287
  logger.success(`Ejected ${files.length} file(s):`);
9811
10288
  for (const file of files) {
9812
- process.stdout.write(` ${relative13(root, file)}
10289
+ process.stdout.write(` ${relative14(root, file)}
9813
10290
  `);
9814
10291
  }
9815
10292
  logger.box(`Your project is now a standalone Astro app.
@@ -9822,10 +10299,10 @@ The blume package remains importable.`);
9822
10299
  });
9823
10300
 
9824
10301
  // src/cli/commands/init.ts
9825
- import { existsSync as existsSync11 } from "node:fs";
10302
+ import { existsSync as existsSync13 } from "node:fs";
9826
10303
  import { mkdir as mkdir6, writeFile as writeFile9 } from "node:fs/promises";
9827
10304
  import { defineCommand as defineCommand6 } from "citty";
9828
- import { basename as basename3, dirname as dirname10, join as join24 } from "pathe";
10305
+ import { basename as basename4, dirname as dirname11, join as join26 } from "pathe";
9829
10306
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
9830
10307
  var packageTemplate = (name, version) => `{
9831
10308
  "name": ${JSON.stringify(name)},
@@ -9860,11 +10337,11 @@ Welcome to **Blume** — markdown-first docs powered by Astro and Vite.
9860
10337
  Edit \`docs/index.mdx\` to get started, then run \`blume dev\`.
9861
10338
  `;
9862
10339
  var writeFileSafe = async (path, content) => {
9863
- if (existsSync11(path)) {
10340
+ if (existsSync13(path)) {
9864
10341
  logger.info(`Skipped existing ${path}`);
9865
10342
  return false;
9866
10343
  }
9867
- await mkdir6(dirname10(path), { recursive: true });
10344
+ await mkdir6(dirname11(path), { recursive: true });
9868
10345
  await writeFile9(path, content, "utf-8");
9869
10346
  logger.success(`Created ${path}`);
9870
10347
  return true;
@@ -9885,9 +10362,9 @@ var initCommand = defineCommand6({
9885
10362
  async run({ args }) {
9886
10363
  const root = process.cwd();
9887
10364
  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);
10365
+ const createdPackage = await writeFileSafe(join26(root, "package.json"), packageTemplate(toPackageName(basename4(root)), getBlumeVersion()));
10366
+ await writeFileSafe(join26(root, "blume.config.ts"), CONFIG_TEMPLATE);
10367
+ await writeFileSafe(join26(root, contentDir, "index.mdx"), INDEX_TEMPLATE);
9891
10368
  const nextSteps = createdPackage ? `Next steps:
9892
10369
 
9893
10370
  npm install
@@ -9904,16 +10381,15 @@ var initCommand = defineCommand6({
9904
10381
  import { defineCommand as defineCommand7 } from "citty";
9905
10382
 
9906
10383
  // 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";
10384
+ import { existsSync as existsSync17 } from "node:fs";
10385
+ import { mkdir as mkdir8, readFile as readFile17, rename as rename3, rm as rm3, writeFile as writeFile11 } from "node:fs/promises";
10386
+ import { dirname as dirname14, join as join29, relative as relative15 } from "pathe";
10387
+ import { glob as glob8 } from "tinyglobby";
9912
10388
 
9913
10389
  // 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";
10390
+ import { existsSync as existsSync14 } from "node:fs";
10391
+ import { readFile as readFile16 } from "node:fs/promises";
10392
+ import { basename as basename5, dirname as dirname12, join as join27 } from "pathe";
9917
10393
  var SOURCE_FILES = [
9918
10394
  "lib/source.ts",
9919
10395
  "app/source.ts",
@@ -9930,25 +10406,62 @@ var prettifyTitle = (name) => {
9930
10406
  }
9931
10407
  return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
9932
10408
  };
10409
+ var GENERIC_NAMES = new Set([
10410
+ "api",
10411
+ "app",
10412
+ "client",
10413
+ "frontend",
10414
+ "server",
10415
+ "site",
10416
+ "web",
10417
+ "www"
10418
+ ]);
10419
+ var gitRepoRoot = (start) => {
10420
+ let dir = start;
10421
+ for (;; ) {
10422
+ if (existsSync14(join27(dir, ".git"))) {
10423
+ return dir;
10424
+ }
10425
+ const parent = dirname12(dir);
10426
+ if (parent === dir) {
10427
+ return null;
10428
+ }
10429
+ dir = parent;
10430
+ }
10431
+ };
10432
+ var bareName = (name) => name.includes("/") ? name.slice(name.lastIndexOf("/") + 1) : name;
9933
10433
  var readTitle = async (root) => {
9934
- const packageJson = join25(root, "package.json");
9935
- if (!existsSync12(packageJson)) {
10434
+ const packageJson = join27(root, "package.json");
10435
+ if (!existsSync14(packageJson)) {
9936
10436
  return "Documentation";
9937
10437
  }
9938
10438
  try {
9939
- const parsed = JSON.parse(await readFile14(packageJson, "utf-8"));
9940
- return typeof parsed.name === "string" && parsed.name.trim() ? prettifyTitle(parsed.name) : "Documentation";
10439
+ const parsed = JSON.parse(await readFile16(packageJson, "utf-8"));
10440
+ const { name } = parsed;
10441
+ if (typeof name !== "string" || !name.trim()) {
10442
+ return "Documentation";
10443
+ }
10444
+ if (GENERIC_NAMES.has(bareName(name).toLowerCase())) {
10445
+ const repoRoot = gitRepoRoot(root);
10446
+ if (repoRoot && repoRoot !== root) {
10447
+ const repoTitle = prettifyTitle(basename5(repoRoot));
10448
+ if (repoTitle !== "Documentation") {
10449
+ return repoTitle;
10450
+ }
10451
+ }
10452
+ }
10453
+ return prettifyTitle(name);
9941
10454
  } catch {
9942
10455
  return "Documentation";
9943
10456
  }
9944
10457
  };
9945
10458
  var scrapeBaseUrl = async (root) => {
9946
10459
  for (const candidate of SOURCE_FILES) {
9947
- const file = join25(root, candidate);
9948
- if (!existsSync12(file)) {
10460
+ const file = join27(root, candidate);
10461
+ if (!existsSync14(file)) {
9949
10462
  continue;
9950
10463
  }
9951
- const base = BASE_URL.exec(await readFile14(file, "utf-8"))?.groups?.base;
10464
+ const base = BASE_URL.exec(await readFile16(file, "utf-8"))?.groups?.base;
9952
10465
  if (base) {
9953
10466
  return base;
9954
10467
  }
@@ -9977,10 +10490,9 @@ var loadFumadocsConfig = async (root) => {
9977
10490
  };
9978
10491
 
9979
10492
  // src/migrate/fumadocs/content.ts
9980
- import { existsSync as existsSync13 } from "node:fs";
10493
+ import { existsSync as existsSync15 } from "node:fs";
9981
10494
  import { readFile as readFileFromDisk2 } from "node:fs/promises";
9982
- import matter10 from "gray-matter";
9983
- import { dirname as dirname11, resolve as resolve7 } from "pathe";
10495
+ import { dirname as dirname13, resolve as resolve8 } from "pathe";
9984
10496
  var FUMADOCS_IMPORT = /^import\s+[\s\S]*?\s+from\s+["']fumadocs-(?:ui|core|mdx)(?:\/[^"']*)?["'];?[ \t]*\n?/gmu;
9985
10497
  var stripFumadocsImports = (source) => {
9986
10498
  const stripped = source.replace(FUMADOCS_IMPORT, "");
@@ -10181,18 +10693,18 @@ var inlineFumadocsIncludes = async (source, options) => {
10181
10693
  if (!rawPath) {
10182
10694
  continue;
10183
10695
  }
10184
- const target = resolve7(dirname11(options.filePath), rawPath);
10696
+ const target = resolve8(dirname13(options.filePath), rawPath);
10185
10697
  if (seen.has(target)) {
10186
10698
  warnings.push(`Circular <include> "${rawPath}" — left as-is.`);
10187
10699
  continue;
10188
10700
  }
10189
- if (!existsSync13(target)) {
10701
+ if (!existsSync15(target)) {
10190
10702
  warnings.push(`<include> target "${rawPath}" not found — left as-is.`);
10191
10703
  continue;
10192
10704
  }
10193
10705
  seen.add(target);
10194
10706
  const raw = await read(target);
10195
- const nested = await inlineFumadocsIncludes(matter10(raw).content.trim(), {
10707
+ const nested = await inlineFumadocsIncludes(frontmatter_default(raw).content.trim(), {
10196
10708
  ...options,
10197
10709
  filePath: target,
10198
10710
  seen
@@ -10217,10 +10729,16 @@ var normalizeFumadocsPageMeta = (value) => {
10217
10729
  return stripUnknownPageMeta(data);
10218
10730
  };
10219
10731
 
10732
+ // src/migrate/fumadocs/groups.ts
10733
+ import { existsSync as existsSync16, statSync as statSync2 } from "node:fs";
10734
+ import { mkdir as mkdir7, rename as rename2, writeFile as writeFile10 } from "node:fs/promises";
10735
+ import { basename as basename6, join as join28 } from "pathe";
10736
+
10220
10737
  // src/migrate/fumadocs/meta.ts
10221
10738
  var SEPARATOR = /^---(?<label>.*)---$/u;
10222
10739
  var LINK2 = /^(?:\[[^\]]*\])?\[(?<text>[^\]]*)\]\((?<href>[^)]*)\)$/u;
10223
10740
  var REST = "...";
10741
+ var EXTRACT = /^\.\.\.(?<folder>.+)$/u;
10224
10742
  var filterPages = (raw) => {
10225
10743
  if (!Array.isArray(raw)) {
10226
10744
  return { pages: [], warnings: [] };
@@ -10235,6 +10753,15 @@ var filterPages = (raw) => {
10235
10753
  if (value === REST || value === "") {
10236
10754
  continue;
10237
10755
  }
10756
+ const extract = EXTRACT.exec(value);
10757
+ if (extract) {
10758
+ const folder = extract.groups?.folder?.trim() ?? "";
10759
+ if (folder) {
10760
+ pages.push(folder);
10761
+ warnings.push(`Sidebar extract "...${folder}" became a normal "${folder}" group (its pages are not flattened into the parent).`);
10762
+ }
10763
+ continue;
10764
+ }
10238
10765
  const separator = SEPARATOR.exec(value);
10239
10766
  if (separator) {
10240
10767
  const label = separator.groups?.label?.trim();
@@ -10250,7 +10777,11 @@ var filterPages = (raw) => {
10250
10777
  }
10251
10778
  return { pages, warnings };
10252
10779
  };
10253
- var translateFumadocsMeta = (value) => {
10780
+ var renderMetaModule = (meta) => `import { defineMeta } from "blume";
10781
+
10782
+ export default defineMeta(${JSON.stringify(meta, null, 2)});
10783
+ `;
10784
+ var translateFumadocsSelfMeta = (value) => {
10254
10785
  if (!value || typeof value !== "object" || Array.isArray(value)) {
10255
10786
  return { meta: {}, warnings: [] };
10256
10787
  }
@@ -10272,23 +10803,197 @@ var translateFumadocsMeta = (value) => {
10272
10803
  if (typeof source.description === "string") {
10273
10804
  warnings.push("Dropped folder `description` — Blume folders have no description field.");
10274
10805
  }
10275
- const { pages, warnings: pageWarnings } = filterPages(source.pages);
10276
- if (pages.length > 0) {
10277
- meta.pages = pages;
10806
+ return { meta, warnings };
10807
+ };
10808
+ var translateFumadocsMeta = (value) => {
10809
+ const { meta, warnings } = translateFumadocsSelfMeta(value);
10810
+ if (value && typeof value === "object" && !Array.isArray(value)) {
10811
+ const { pages, warnings: pageWarnings } = filterPages(value.pages);
10812
+ if (pages.length > 0) {
10813
+ meta.pages = pages;
10814
+ }
10815
+ warnings.push(...pageWarnings);
10278
10816
  }
10279
- warnings.push(...pageWarnings);
10280
10817
  return { meta, warnings };
10281
10818
  };
10819
+ var parseFumadocsPages = (raw) => {
10820
+ const lead = [];
10821
+ const sections = [];
10822
+ if (!Array.isArray(raw)) {
10823
+ return { hasSections: false, lead, sections };
10824
+ }
10825
+ let current = null;
10826
+ const push = (item) => {
10827
+ (current ? current.items : lead).push(item);
10828
+ };
10829
+ for (const entry of raw) {
10830
+ if (typeof entry !== "string") {
10831
+ continue;
10832
+ }
10833
+ const value = entry.trim();
10834
+ if (value === REST || value === "") {
10835
+ continue;
10836
+ }
10837
+ const separator = SEPARATOR.exec(value);
10838
+ if (separator) {
10839
+ current = {
10840
+ items: [],
10841
+ label: separator.groups?.label?.trim() || "Section"
10842
+ };
10843
+ sections.push(current);
10844
+ continue;
10845
+ }
10846
+ const extract = EXTRACT.exec(value);
10847
+ if (extract) {
10848
+ const name = extract.groups?.folder?.trim();
10849
+ if (name) {
10850
+ push({ kind: "extract", name });
10851
+ }
10852
+ continue;
10853
+ }
10854
+ const link = LINK2.exec(value);
10855
+ if (link) {
10856
+ push({
10857
+ href: link.groups?.href ?? "",
10858
+ kind: "link",
10859
+ text: link.groups?.text ?? ""
10860
+ });
10861
+ continue;
10862
+ }
10863
+ push({ kind: "ref", name: value });
10864
+ }
10865
+ return { hasSections: sections.length > 0, lead, sections };
10866
+ };
10867
+
10868
+ // src/migrate/fumadocs/groups.ts
10869
+ var PAGE_EXTS = [".mdx", ".md"];
10870
+ var WORD_SPLIT3 = /[-_]/u;
10871
+ var PATH_SEP = /[/\\]/u;
10872
+ var isDirectory = (path) => {
10873
+ try {
10874
+ return statSync2(path).isDirectory();
10875
+ } catch {
10876
+ return false;
10877
+ }
10878
+ };
10879
+ var resolveEntry = (docsDir, name) => {
10880
+ for (const ext of PAGE_EXTS) {
10881
+ const file = join28(docsDir, `${name}${ext}`);
10882
+ if (existsSync16(file)) {
10883
+ return { kind: "file", path: file };
10884
+ }
10885
+ }
10886
+ const folder = join28(docsDir, name);
10887
+ return isDirectory(folder) ? { kind: "folder", path: folder } : null;
10888
+ };
10889
+ var humanize2 = (name) => name.split(WORD_SPLIT3).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
10890
+ var linkWarning = (item) => `Dropped sidebar link "${item.text}" (${item.href}) — add it to navbar.links manually.`;
10891
+ var extractWarning = (name) => `Sidebar extract "...${name}" became a normal "${name}" group (its pages are not flattened into the parent).`;
10892
+ var addUngroupedItem = (item, order, warnings) => {
10893
+ if (item.kind === "link") {
10894
+ warnings.push(linkWarning(item));
10895
+ return;
10896
+ }
10897
+ if (item.kind === "extract") {
10898
+ warnings.push(extractWarning(item.name));
10899
+ }
10900
+ order.push(item.name);
10901
+ };
10902
+ var reshapeSingleFolderSection = (section, only, order, warnings) => {
10903
+ order.push(only.name);
10904
+ if (humanize2(only.name).toLowerCase() !== section.label.toLowerCase()) {
10905
+ warnings.push(`Section "${section.label}" wraps folder "${only.name}"; set that folder's meta title to "${section.label}" to make the heading match.`);
10906
+ }
10907
+ if (only.kind === "extract") {
10908
+ warnings.push(extractWarning(only.name));
10909
+ }
10910
+ };
10911
+ var moveItemIntoGroup = async (item, docsDir, groupDir, label, warnings) => {
10912
+ if (item.kind === "link") {
10913
+ warnings.push(linkWarning(item));
10914
+ return null;
10915
+ }
10916
+ const resolved = resolveEntry(docsDir, item.name);
10917
+ if (!resolved) {
10918
+ warnings.push(`Sidebar entry "${item.name}" in section "${label}" matched no page or folder; skipped.`);
10919
+ return null;
10920
+ }
10921
+ const dest = join28(groupDir, basename6(resolved.path));
10922
+ if (existsSync16(dest)) {
10923
+ warnings.push(`Skipped moving "${item.name}" into section "${label}" (target already exists).`);
10924
+ return null;
10925
+ }
10926
+ await mkdir7(groupDir, { recursive: true });
10927
+ await rename2(resolved.path, dest);
10928
+ if (item.kind === "extract") {
10929
+ warnings.push(extractWarning(item.name));
10930
+ }
10931
+ return item.name;
10932
+ };
10933
+ var reshapeSection = async (section, docsDir, order, warnings) => {
10934
+ const movable = section.items.filter((item) => item.kind !== "link");
10935
+ if (movable.length === 1) {
10936
+ const [only] = movable;
10937
+ if (resolveEntry(docsDir, only.name)?.kind === "folder") {
10938
+ reshapeSingleFolderSection(section, only, order, warnings);
10939
+ return;
10940
+ }
10941
+ }
10942
+ if (PATH_SEP.test(section.label)) {
10943
+ warnings.push(`Section "${section.label}" has a slash in its name and can't become a group folder; its pages stay ungrouped.`);
10944
+ for (const item of section.items) {
10945
+ addUngroupedItem(item, order, warnings);
10946
+ }
10947
+ return;
10948
+ }
10949
+ const groupDir = join28(docsDir, `(${section.label})`);
10950
+ const sectionKeys = [];
10951
+ for (const item of section.items) {
10952
+ const key = await moveItemIntoGroup(item, docsDir, groupDir, section.label, warnings);
10953
+ if (key) {
10954
+ sectionKeys.push(key);
10955
+ }
10956
+ }
10957
+ if (sectionKeys.length === 0) {
10958
+ return;
10959
+ }
10960
+ order.push(section.label);
10961
+ if (sectionKeys.length > 1) {
10962
+ await writeFile10(join28(groupDir, "meta.ts"), renderMetaModule({ pages: sectionKeys }), "utf-8");
10963
+ }
10964
+ };
10965
+ var reshapeFumadocsGroups = async (structure, docsDir) => {
10966
+ const order = [];
10967
+ const warnings = [];
10968
+ for (const item of structure.lead) {
10969
+ addUngroupedItem(item, order, warnings);
10970
+ }
10971
+ for (const section of structure.sections) {
10972
+ await reshapeSection(section, docsDir, order, warnings);
10973
+ }
10974
+ return { order, warnings };
10975
+ };
10282
10976
 
10283
10977
  // src/migrate/fumadocs/index.ts
10284
10978
  var SOURCE_DIR = "content/docs";
10285
10979
  var PAGE_GLOB = "**/*.{md,mdx}";
10286
10980
  var META_GLOB = "**/meta.json";
10287
10981
  var IGNORE = ["**/node_modules/**"];
10982
+ var FUMADOCS_LEFTOVERS = [
10983
+ "next.config.ts",
10984
+ "next.config.mjs",
10985
+ "next.config.js",
10986
+ "source.config.ts",
10987
+ "source.config.mjs",
10988
+ "source.config.js",
10989
+ "mdx-components.tsx",
10990
+ "next-env.d.ts",
10991
+ "app"
10992
+ ];
10288
10993
  var movePage = async (abs, base, root) => {
10289
- const rel = relative14(base, abs);
10290
- const dest = join26(root, "docs", rel);
10291
- if (existsSync14(dest)) {
10994
+ const rel = relative15(base, abs);
10995
+ const dest = join29(root, "docs", rel);
10996
+ if (existsSync17(dest)) {
10292
10997
  return {
10293
10998
  includeWarnings: [],
10294
10999
  moved: 0,
@@ -10297,18 +11002,18 @@ var movePage = async (abs, base, root) => {
10297
11002
  unsupported: []
10298
11003
  };
10299
11004
  }
10300
- const raw = await readFile15(abs, "utf-8");
11005
+ const raw = await readFile17(abs, "utf-8");
10301
11006
  const included = await inlineFumadocsIncludes(raw, { filePath: abs });
10302
11007
  let text = stripFumadocsImports(included.content);
10303
11008
  text = rewriteFumadocsCallouts(text);
10304
11009
  text = rewriteFumadocsContainers(text);
10305
11010
  text = rewriteFumadocsTabs(text);
10306
11011
  const unsupported = unsupportedFumadocsComponents(text);
10307
- const parsed = matter11(text);
11012
+ const parsed = frontmatter_default(text);
10308
11013
  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");
11014
+ const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
11015
+ await mkdir8(dirname14(dest), { recursive: true });
11016
+ await writeFile11(dest, content, "utf-8");
10312
11017
  await rm3(abs, { force: true });
10313
11018
  return {
10314
11019
  includeWarnings: included.warnings,
@@ -10318,39 +11023,56 @@ var movePage = async (abs, base, root) => {
10318
11023
  unsupported
10319
11024
  };
10320
11025
  };
11026
+ var writeMeta = async (dest, meta, rel, warnings) => {
11027
+ if (Object.keys(meta).length === 0) {
11028
+ return warnings;
11029
+ }
11030
+ if (existsSync17(dest)) {
11031
+ return [...warnings, `Skipped ${rel} (target already exists)`];
11032
+ }
11033
+ await mkdir8(dirname14(dest), { recursive: true });
11034
+ await writeFile11(dest, renderMetaModule(meta), "utf-8");
11035
+ return warnings;
11036
+ };
10321
11037
  var convertMeta = async (abs, base, root) => {
10322
- const rel = relative14(base, abs);
10323
- const raw = await readFile15(abs, "utf-8");
11038
+ const rel = relative15(base, abs);
11039
+ const raw = await readFile17(abs, "utf-8");
10324
11040
  let parsed;
10325
11041
  try {
10326
11042
  parsed = JSON.parse(raw);
10327
11043
  } catch {
10328
- const dest = join26(root, "docs", rel);
10329
- if (existsSync14(dest)) {
11044
+ const dest2 = join29(root, "docs", rel);
11045
+ if (existsSync17(dest2)) {
10330
11046
  return [`Skipped ${rel} (target already exists)`];
10331
11047
  }
10332
- await mkdir7(dirname12(dest), { recursive: true });
10333
- await rename2(abs, dest);
11048
+ await mkdir8(dirname14(dest2), { recursive: true });
11049
+ await rename3(abs, dest2);
10334
11050
  return [
10335
11051
  `Could not parse ${rel}; moved as-is — convert it to meta.ts by hand.`
10336
11052
  ];
10337
11053
  }
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");
11054
+ const dir = dirname14(rel) === "." ? "" : dirname14(rel);
11055
+ const docsDir = join29(root, "docs", dir);
11056
+ const dest = join29(docsDir, "meta.ts");
11057
+ const structure = parseFumadocsPages(parsed.pages);
11058
+ if (structure.hasSections && !existsSync17(dest)) {
11059
+ const self = translateFumadocsSelfMeta(parsed);
11060
+ const reshape = await reshapeFumadocsGroups(structure, docsDir);
11061
+ const meta2 = { ...self.meta };
11062
+ if (reshape.order.length > 0) {
11063
+ meta2.pages = reshape.order;
11064
+ }
11065
+ const result2 = await writeMeta(dest, meta2, rel, [
11066
+ ...self.warnings,
11067
+ ...reshape.warnings
11068
+ ]);
11069
+ await rm3(abs, { force: true });
11070
+ return result2;
10351
11071
  }
11072
+ const { meta, warnings } = translateFumadocsMeta(parsed);
11073
+ const result = await writeMeta(dest, meta, rel, warnings);
10352
11074
  await rm3(abs, { force: true });
10353
- return warnings;
11075
+ return result;
10354
11076
  };
10355
11077
  var summarizePages = (results) => {
10356
11078
  let moved = 0;
@@ -10380,9 +11102,9 @@ var summarizePages = (results) => {
10380
11102
  };
10381
11103
  };
10382
11104
  var cleanupSourceDirs = async (root) => {
10383
- const docs = join26(root, "content", "docs");
10384
- if (existsSync14(docs)) {
10385
- const remaining = await glob7(["**/*"], { cwd: docs, dot: true });
11105
+ const docs = join29(root, "content", "docs");
11106
+ if (existsSync17(docs)) {
11107
+ const remaining = await glob8(["**/*"], { cwd: docs, dot: true });
10386
11108
  if (remaining.length > 0) {
10387
11109
  return [
10388
11110
  `Kept ${remaining.length} non-page file(s) under content/docs; move them into docs/ manually.`
@@ -10390,9 +11112,9 @@ var cleanupSourceDirs = async (root) => {
10390
11112
  }
10391
11113
  await rm3(docs, { force: true, recursive: true });
10392
11114
  }
10393
- const content = join26(root, "content");
10394
- if (existsSync14(content)) {
10395
- const remaining = await glob7(["**/*"], { cwd: content, dot: true });
11115
+ const content = join29(root, "content");
11116
+ if (existsSync17(content)) {
11117
+ const remaining = await glob8(["**/*"], { cwd: content, dot: true });
10396
11118
  if (remaining.length === 0) {
10397
11119
  await rm3(content, { force: true, recursive: true });
10398
11120
  }
@@ -10401,8 +11123,8 @@ var cleanupSourceDirs = async (root) => {
10401
11123
  };
10402
11124
  var migrateFumadocsProject = async (root) => {
10403
11125
  const { config, warnings: configWarnings } = await loadFumadocsConfig(root);
10404
- const base = join26(root, SOURCE_DIR);
10405
- if (!existsSync14(base)) {
11126
+ const base = join29(root, SOURCE_DIR);
11127
+ if (!existsSync17(base)) {
10406
11128
  await writeBlumeConfig(root, config);
10407
11129
  return {
10408
11130
  moved: 0,
@@ -10411,22 +11133,28 @@ var migrateFumadocsProject = async (root) => {
10411
11133
  ]
10412
11134
  };
10413
11135
  }
10414
- const pageFiles = await glob7([PAGE_GLOB], {
11136
+ const pageFiles = await glob8([PAGE_GLOB], {
10415
11137
  absolute: true,
10416
11138
  cwd: base,
10417
11139
  ignore: IGNORE
10418
11140
  });
10419
- const metaFiles = await glob7([META_GLOB], {
11141
+ const metaFiles = await glob8([META_GLOB], {
10420
11142
  absolute: true,
10421
11143
  cwd: base,
10422
11144
  ignore: IGNORE
10423
11145
  });
10424
11146
  const pageResults = await Promise.all(pageFiles.map((abs) => movePage(abs, base, root)));
10425
11147
  const pages = summarizePages(pageResults);
10426
- const metaResults = await Promise.all(metaFiles.map((abs) => convertMeta(abs, base, root)));
10427
- const metaWarnings = metaResults.flat();
11148
+ const orderedMetas = metaFiles.toSorted((a, b) => b.split("/").length - a.split("/").length);
11149
+ const metaWarnings = [];
11150
+ for (const abs of orderedMetas) {
11151
+ metaWarnings.push(...await convertMeta(abs, base, root));
11152
+ }
10428
11153
  await writeBlumeConfig(root, config);
10429
11154
  const cleanupWarnings = await cleanupSourceDirs(root);
11155
+ const scriptsRewritten = await rewriteFrameworkScripts(root, /\bnext\b/u, /fumadocs/u);
11156
+ const gitignoreAdded = await ensureGitignore(root, [".blume/", "dist/"]);
11157
+ const leftovers = leftoverFiles(root, FUMADOCS_LEFTOVERS);
10430
11158
  const warnings = [
10431
11159
  ...configWarnings,
10432
11160
  ...metaWarnings,
@@ -10440,15 +11168,24 @@ var migrateFumadocsProject = async (root) => {
10440
11168
  if (pages.unsupported.length > 0) {
10441
11169
  warnings.push(`Components without a drop-in Blume equivalent need manual review: ${pages.unsupported.join(", ")}.`);
10442
11170
  }
11171
+ if (scriptsRewritten) {
11172
+ warnings.push("Repointed the dev/build/start scripts at the Blume CLI.");
11173
+ }
11174
+ if (gitignoreAdded.length > 0) {
11175
+ warnings.push(`Added ${gitignoreAdded.join(", ")} to .gitignore.`);
11176
+ }
11177
+ if (leftovers.length > 0) {
11178
+ 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.`);
11179
+ }
10443
11180
  warnings.push("Review blume.config.ts and the generated meta.ts files.");
10444
11181
  return { moved: pages.moved, warnings };
10445
11182
  };
10446
11183
 
10447
11184
  // 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";
11185
+ import { existsSync as existsSync18 } from "node:fs";
11186
+ import { mkdir as mkdir9, readFile as readFile18, rename as rename4, rm as rm4, writeFile as writeFile12 } from "node:fs/promises";
11187
+ import { dirname as dirname15, join as join30 } from "pathe";
11188
+ import { glob as glob9 } from "tinyglobby";
10452
11189
  var prune = (value) => {
10453
11190
  if (Array.isArray(value)) {
10454
11191
  return value.map(prune);
@@ -10477,7 +11214,7 @@ var writeBlumeConfig2 = async (root, config) => {
10477
11214
 
10478
11215
  export default defineConfig(${JSON.stringify(prune(config), null, 2)});
10479
11216
  `;
10480
- await writeFile11(join27(root, "blume.config.ts"), body, "utf-8");
11217
+ await writeFile12(join30(root, "blume.config.ts"), body, "utf-8");
10481
11218
  };
10482
11219
  var relocateAssets = async (root, refs) => {
10483
11220
  const segments = new Set;
@@ -10492,30 +11229,30 @@ var relocateAssets = async (root, refs) => {
10492
11229
  }
10493
11230
  const moved = [];
10494
11231
  for (const segment of segments) {
10495
- const source = join27(root, segment);
10496
- if (!existsSync15(source) || segment === "public") {
11232
+ const source = join30(root, segment);
11233
+ if (!existsSync18(source) || segment === "public") {
10497
11234
  continue;
10498
11235
  }
10499
- const dest = join27(root, "public", segment);
10500
- if (existsSync15(dest)) {
11236
+ const dest = join30(root, "public", segment);
11237
+ if (existsSync18(dest)) {
10501
11238
  continue;
10502
11239
  }
10503
- await mkdir8(join27(root, "public"), { recursive: true });
10504
- await rename3(source, dest);
11240
+ await mkdir9(join30(root, "public"), { recursive: true });
11241
+ await rename4(source, dest);
10505
11242
  moved.push(segment);
10506
11243
  }
10507
11244
  return moved;
10508
11245
  };
10509
11246
  var cleanupSnippets = async (root, kept, warnings) => {
10510
- const dir = join27(root, "snippets");
10511
- if (!existsSync15(dir)) {
11247
+ const dir = join30(root, "snippets");
11248
+ if (!existsSync18(dir)) {
10512
11249
  return;
10513
11250
  }
10514
- const markdown = await glob8(["**/*.{md,mdx}"], { absolute: true, cwd: dir });
11251
+ const markdown = await glob9(["**/*.{md,mdx}"], { absolute: true, cwd: dir });
10515
11252
  for (const file of markdown) {
10516
11253
  await rm4(file, { force: true });
10517
11254
  }
10518
- const remaining = await glob8(["**/*"], { cwd: dir, dot: true });
11255
+ const remaining = await glob9(["**/*"], { cwd: dir, dot: true });
10519
11256
  if (remaining.length === 0) {
10520
11257
  await rm4(dir, { force: true, recursive: true });
10521
11258
  warnings.push("Inlined and removed the /snippets directory.");
@@ -10545,11 +11282,11 @@ var assetRefs = (config) => {
10545
11282
  };
10546
11283
  var migrateMintlifyProject = async (root) => {
10547
11284
  const warnings = [];
10548
- const configFile = existsSync15(join27(root, "docs.json")) ? join27(root, "docs.json") : join27(root, "mint.json");
11285
+ const configFile = existsSync18(join30(root, "docs.json")) ? join30(root, "docs.json") : join30(root, "mint.json");
10549
11286
  let config;
10550
- if (existsSync15(configFile)) {
11287
+ if (existsSync18(configFile)) {
10551
11288
  config = await loadMintlifyConfig(root, configFile);
10552
- const spec = JSON.parse(await readFile16(configFile, "utf-8"));
11289
+ const spec = JSON.parse(await readFile18(configFile, "utf-8"));
10553
11290
  const i18n = mintlifyI18n(spec);
10554
11291
  if (i18n) {
10555
11292
  config.i18n = i18n;
@@ -10564,7 +11301,7 @@ var migrateMintlifyProject = async (root) => {
10564
11301
  }
10565
11302
  const variables = config.variables ?? {};
10566
11303
  config.variables = undefined;
10567
- const files = await glob8(["**/*.{md,mdx}"], {
11304
+ const files = await glob9(["**/*.{md,mdx}"], {
10568
11305
  absolute: true,
10569
11306
  cwd: root,
10570
11307
  ignore: [
@@ -10580,15 +11317,15 @@ var migrateMintlifyProject = async (root) => {
10580
11317
  const unsupported = new Set;
10581
11318
  const keptComponents = new Set;
10582
11319
  for (const file of files) {
10583
- const raw = await readFile16(file, "utf-8");
11320
+ const raw = await readFile18(file, "utf-8");
10584
11321
  const result = await transformMintlifyContent(raw, {
10585
11322
  filePath: file,
10586
11323
  root,
10587
11324
  variables
10588
11325
  });
10589
11326
  if (result.content !== raw) {
10590
- await mkdir8(dirname13(file), { recursive: true });
10591
- await writeFile11(file, result.content, "utf-8");
11327
+ await mkdir9(dirname15(file), { recursive: true });
11328
+ await writeFile12(file, result.content, "utf-8");
10592
11329
  }
10593
11330
  for (const key of result.removed) {
10594
11331
  removedKeys.add(key);
@@ -10624,11 +11361,10 @@ var migrateMintlifyProject = async (root) => {
10624
11361
  };
10625
11362
 
10626
11363
  // 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";
11364
+ import { existsSync as existsSync19 } from "node:fs";
11365
+ import { mkdir as mkdir10, readFile as readFile19, rename as rename5, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
11366
+ import { basename as basename7, dirname as dirname16, extname as extname6, join as join31, relative as relative16 } from "pathe";
11367
+ import { glob as glob10 } from "tinyglobby";
10632
11368
 
10633
11369
  // src/migrate/nextra/content.ts
10634
11370
  var CALLOUT_TYPE_DIRECTIVES3 = {
@@ -10857,30 +11593,30 @@ var indexFolders = (base, pageFiles, metaFiles) => {
10857
11593
  let current = normalizeDir(dir);
10858
11594
  allDirs.add(current);
10859
11595
  while (current !== "") {
10860
- current = normalizeDir(dirname14(current));
11596
+ current = normalizeDir(dirname16(current));
10861
11597
  allDirs.add(current);
10862
11598
  }
10863
11599
  };
10864
11600
  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, "");
11601
+ const rel = relative16(base, abs);
11602
+ const dir = normalizeDir(dirname16(rel));
11603
+ const slug = basename7(rel).replace(/\.mdx?$/u, "");
10868
11604
  const folder = pagesByDir.get(dir) ?? new Map;
10869
11605
  folder.set(slug, abs);
10870
11606
  pagesByDir.set(dir, folder);
10871
11607
  registerDir(dir);
10872
11608
  }
10873
11609
  for (const abs of metaFiles) {
10874
- registerDir(normalizeDir(dirname14(relative15(base, abs))));
11610
+ registerDir(normalizeDir(dirname16(relative16(base, abs))));
10875
11611
  }
10876
11612
  const childDirs = new Map;
10877
11613
  for (const dir of allDirs) {
10878
11614
  if (dir === "") {
10879
11615
  continue;
10880
11616
  }
10881
- const parent = normalizeDir(dirname14(dir));
11617
+ const parent = normalizeDir(dirname16(dir));
10882
11618
  const children = childDirs.get(parent) ?? new Set;
10883
- children.add(basename4(dir));
11619
+ children.add(basename7(dir));
10884
11620
  childDirs.set(parent, children);
10885
11621
  }
10886
11622
  return { childDirs, pagesByDir };
@@ -10896,7 +11632,7 @@ var planMetas = (metas, index) => {
10896
11632
  warnings: []
10897
11633
  };
10898
11634
  for (const meta of metas) {
10899
- const dir = normalizeDir(dirname14(meta.rel));
11635
+ const dir = normalizeDir(dirname16(meta.rel));
10900
11636
  const entries = parseNextraMeta(meta.raw, meta.ext);
10901
11637
  if (!entries) {
10902
11638
  plan.unparseableMetas.push({ abs: meta.abs, rel: meta.rel });
@@ -10909,7 +11645,7 @@ var planMetas = (metas, index) => {
10909
11645
  plan.metaByDir.set(dir, conversion.folderMeta);
10910
11646
  plan.consumedMetas.push(meta.abs);
10911
11647
  for (const [slug, title] of Object.entries(conversion.folderTitles)) {
10912
- plan.folderTitleByDir.set(normalizeDir(join28(dir, slug)), title);
11648
+ plan.folderTitleByDir.set(normalizeDir(join31(dir, slug)), title);
10913
11649
  }
10914
11650
  for (const [slug, label] of Object.entries(conversion.pageLabels)) {
10915
11651
  const pageAbs = index.pagesByDir.get(dir)?.get(slug);
@@ -10933,19 +11669,19 @@ var planMetas = (metas, index) => {
10933
11669
  return plan;
10934
11670
  };
10935
11671
  var movePage2 = async (abs, options) => {
10936
- const rel = relative15(options.base, abs);
10937
- const dest = join28(options.root, "docs", rel);
10938
- if (existsSync16(dest)) {
11672
+ const rel = relative16(options.base, abs);
11673
+ const dest = join31(options.root, "docs", rel);
11674
+ if (existsSync19(dest)) {
10939
11675
  return { moved: 0, removed: [], skipped: rel, unsupported: [] };
10940
11676
  }
10941
- const raw = await readFile17(abs, "utf-8");
11677
+ const raw = await readFile19(abs, "utf-8");
10942
11678
  const text = rewriteNextraCallouts(stripNextraImports(raw));
10943
11679
  const unsupported = unsupportedNextraComponents(text);
10944
- const parsed = matter12(text);
11680
+ const parsed = frontmatter_default(text);
10945
11681
  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");
11682
+ const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
11683
+ await mkdir10(dirname16(dest), { recursive: true });
11684
+ await writeFile13(dest, content, "utf-8");
10949
11685
  await rm5(abs, { force: true });
10950
11686
  return { moved: 1, removed, skipped: null, unsupported };
10951
11687
  };
@@ -10987,9 +11723,9 @@ var writeFolderMetas = async (root, plan) => {
10987
11723
  if (Object.keys(finalMeta).length === 0) {
10988
11724
  return;
10989
11725
  }
10990
- const dest = join28(root, "docs", dir, "meta.ts");
10991
- await mkdir9(dirname14(dest), { recursive: true });
10992
- await writeFile12(dest, `import { defineMeta } from "blume";
11726
+ const dest = join31(root, "docs", dir, "meta.ts");
11727
+ await mkdir10(dirname16(dest), { recursive: true });
11728
+ await writeFile13(dest, `import { defineMeta } from "blume";
10993
11729
 
10994
11730
  export default defineMeta(${JSON.stringify(finalMeta, null, 2)});
10995
11731
  `, "utf-8");
@@ -10998,20 +11734,20 @@ export default defineMeta(${JSON.stringify(finalMeta, null, 2)});
10998
11734
  var relocateUnparseableMetas = async (root, metas) => {
10999
11735
  const warnings = [];
11000
11736
  await Promise.all(metas.map(async ({ abs, rel }) => {
11001
- const dest = join28(root, "docs", rel);
11002
- if (existsSync16(dest)) {
11737
+ const dest = join31(root, "docs", rel);
11738
+ if (existsSync19(dest)) {
11003
11739
  warnings.push(`Skipped ${rel} (target already exists)`);
11004
11740
  return;
11005
11741
  }
11006
- await mkdir9(dirname14(dest), { recursive: true });
11007
- await rename4(abs, dest);
11742
+ await mkdir10(dirname16(dest), { recursive: true });
11743
+ await rename5(abs, dest);
11008
11744
  warnings.push(`Could not parse ${rel}; moved as-is — convert it to meta.ts by hand.`);
11009
11745
  }));
11010
11746
  return warnings;
11011
11747
  };
11012
11748
  var buildConfig = (tabs) => tabs.length > 0 ? { navigation: { tabs }, title: "Documentation" } : { title: "Documentation" };
11013
11749
  var migrateNextraProject = async (root) => {
11014
- const sourceDir = SOURCE_DIRS.find((dir) => existsSync16(join28(root, dir)));
11750
+ const sourceDir = SOURCE_DIRS.find((dir) => existsSync19(join31(root, dir)));
11015
11751
  if (!sourceDir) {
11016
11752
  await writeBlumeConfig(root, { title: "Documentation" });
11017
11753
  return {
@@ -11021,13 +11757,13 @@ var migrateNextraProject = async (root) => {
11021
11757
  ]
11022
11758
  };
11023
11759
  }
11024
- const base = join28(root, sourceDir);
11025
- const pageFiles = await glob9([PAGE_GLOB2], {
11760
+ const base = join31(root, sourceDir);
11761
+ const pageFiles = await glob10([PAGE_GLOB2], {
11026
11762
  absolute: true,
11027
11763
  cwd: base,
11028
11764
  ignore: IGNORE2
11029
11765
  });
11030
- const metaFiles = await glob9([META_GLOB2], {
11766
+ const metaFiles = await glob10([META_GLOB2], {
11031
11767
  absolute: true,
11032
11768
  cwd: base,
11033
11769
  ignore: IGNORE2
@@ -11036,8 +11772,8 @@ var migrateNextraProject = async (root) => {
11036
11772
  const metas = await Promise.all(metaFiles.map(async (abs) => ({
11037
11773
  abs,
11038
11774
  ext: extname6(abs),
11039
- raw: await readFile17(abs, "utf-8"),
11040
- rel: relative15(base, abs)
11775
+ raw: await readFile19(abs, "utf-8"),
11776
+ rel: relative16(base, abs)
11041
11777
  })));
11042
11778
  const plan = planMetas(metas, index);
11043
11779
  const moves = await Promise.all(pageFiles.map((abs) => movePage2(abs, { base, overrides: plan.pageOverrides, root })));
@@ -11062,16 +11798,15 @@ var migrateNextraProject = async (root) => {
11062
11798
  };
11063
11799
 
11064
11800
  // 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";
11801
+ import { existsSync as existsSync21 } from "node:fs";
11802
+ import { readFile as readFile21, writeFile as writeFile14 } from "node:fs/promises";
11803
+ import { join as join33 } from "pathe";
11804
+ import { glob as glob11 } from "tinyglobby";
11070
11805
 
11071
11806
  // 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";
11807
+ import { existsSync as existsSync20 } from "node:fs";
11808
+ import { readFile as readFile20 } from "node:fs/promises";
11809
+ import { join as join32 } from "pathe";
11075
11810
  var CONFIG_FILES = [
11076
11811
  "astro.config.mjs",
11077
11812
  "astro.config.mts",
@@ -11102,7 +11837,7 @@ var extractStarlightOptions = (source) => {
11102
11837
  return isLiteralObject(parsed) ? parsed : "unparseable";
11103
11838
  };
11104
11839
  var loadStarlightConfig = async (root) => {
11105
- const file = CONFIG_FILES.map((name) => join29(root, name)).find((path) => existsSync17(path));
11840
+ const file = CONFIG_FILES.map((name) => join32(root, name)).find((path) => existsSync20(path));
11106
11841
  if (!file) {
11107
11842
  return {
11108
11843
  options: {},
@@ -11111,7 +11846,7 @@ var loadStarlightConfig = async (root) => {
11111
11846
  ]
11112
11847
  };
11113
11848
  }
11114
- const source = await readFile18(file, "utf-8");
11849
+ const source = await readFile20(file, "utf-8");
11115
11850
  const result = extractStarlightOptions(source);
11116
11851
  if (result === "missing") {
11117
11852
  return {
@@ -11512,17 +12247,17 @@ var starlightI18n = (options) => {
11512
12247
  // src/migrate/starlight/index.ts
11513
12248
  var CONTENT_DIR = "src/content/docs";
11514
12249
  var transformPage = async (file) => {
11515
- const raw = await readFile19(file, "utf-8");
12250
+ const raw = await readFile21(file, "utf-8");
11516
12251
  let text = stripStarlightImports(raw);
11517
12252
  text = rewriteStarlightAsides(text);
11518
12253
  text = rewriteStarlightComponents(text);
11519
12254
  const unsupported = unsupportedStarlightComponents(text);
11520
12255
  const aliased = hasAliasedAssets(text);
11521
- const parsed = matter13(text);
12256
+ const parsed = frontmatter_default(text);
11522
12257
  const { data, removed } = normalizeStarlightPageMeta(parsed.data);
11523
- const content = Object.keys(data).length > 0 ? matter13.stringify(parsed.content, data) : parsed.content;
12258
+ const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
11524
12259
  if (content !== raw) {
11525
- await writeFile13(file, content, "utf-8");
12260
+ await writeFile14(file, content, "utf-8");
11526
12261
  }
11527
12262
  return { aliased, removed, unsupported };
11528
12263
  };
@@ -11534,8 +12269,8 @@ var migrateStarlightProject = async (root) => {
11534
12269
  config.i18n = i18n;
11535
12270
  warnings.push(`Mapped ${i18n.locales.length} locale(s) to i18n (default: ${i18n.defaultLocale}); review the locale labels.`);
11536
12271
  }
11537
- const base = join30(root, CONTENT_DIR);
11538
- if (!existsSync18(base)) {
12272
+ const base = join33(root, CONTENT_DIR);
12273
+ if (!existsSync21(base)) {
11539
12274
  await writeBlumeConfig(root, config);
11540
12275
  return {
11541
12276
  moved: 0,
@@ -11545,7 +12280,7 @@ var migrateStarlightProject = async (root) => {
11545
12280
  ]
11546
12281
  };
11547
12282
  }
11548
- const files = await glob10(["**/*.{md,mdx}"], { absolute: true, cwd: base });
12283
+ const files = await glob11(["**/*.{md,mdx}"], { absolute: true, cwd: base });
11549
12284
  const results = await Promise.all(files.map(transformPage));
11550
12285
  await writeBlumeConfig(root, config);
11551
12286
  const removedKeys = new Set;
@@ -11623,10 +12358,10 @@ var migrateCommand = defineCommand7({
11623
12358
  });
11624
12359
 
11625
12360
  // src/cli/commands/preview.ts
11626
- import { existsSync as existsSync19 } from "node:fs";
12361
+ import { existsSync as existsSync22 } from "node:fs";
11627
12362
  import { preview } from "astro";
11628
12363
  import { defineCommand as defineCommand8 } from "citty";
11629
- import { join as join31 } from "pathe";
12364
+ import { join as join34 } from "pathe";
11630
12365
  var previewCommand = defineCommand8({
11631
12366
  args: {
11632
12367
  host: { description: "Network host to bind.", type: "string" },
@@ -11640,7 +12375,7 @@ var previewCommand = defineCommand8({
11640
12375
  const root = process.cwd();
11641
12376
  const { config } = await loadConfig(root);
11642
12377
  const context = resolveProjectContext(root, config);
11643
- if (!existsSync19(join31(context.outDir, "astro.config.mjs"))) {
12378
+ if (!existsSync22(join34(context.outDir, "astro.config.mjs"))) {
11644
12379
  logger.error("No build found. Run `blume build` first.");
11645
12380
  process.exit(1);
11646
12381
  }
@@ -11658,7 +12393,7 @@ var previewCommand = defineCommand8({
11658
12393
  // src/cli/commands/sync.ts
11659
12394
  import { rm as rm6 } from "node:fs/promises";
11660
12395
  import { defineCommand as defineCommand9 } from "citty";
11661
- import { join as join32 } from "pathe";
12396
+ import { join as join35 } from "pathe";
11662
12397
  var syncCommand = defineCommand9({
11663
12398
  args: {
11664
12399
  force: {
@@ -11680,7 +12415,7 @@ var syncCommand = defineCommand9({
11680
12415
  if (args.force) {
11681
12416
  const { config } = await loadConfig(root);
11682
12417
  const context = resolveProjectContext(root, config);
11683
- await rm6(join32(context.outDir, "cache"), { force: true, recursive: true });
12418
+ await rm6(join35(context.outDir, "cache"), { force: true, recursive: true });
11684
12419
  logger.info("Cleared source cache.");
11685
12420
  }
11686
12421
  await prepareProject({
@@ -11695,13 +12430,13 @@ var syncCommand = defineCommand9({
11695
12430
  });
11696
12431
 
11697
12432
  // src/cli/commands/validate.ts
11698
- import { existsSync as existsSync21 } from "node:fs";
12433
+ import { existsSync as existsSync24 } from "node:fs";
11699
12434
  import { defineCommand as defineCommand10 } from "citty";
11700
- import { join as join34 } from "pathe";
12435
+ import { join as join37 } from "pathe";
11701
12436
 
11702
12437
  // src/core/links.ts
11703
- import { existsSync as existsSync20 } from "node:fs";
11704
- import { join as join33 } from "pathe";
12438
+ import { existsSync as existsSync23 } from "node:fs";
12439
+ import { join as join36 } from "pathe";
11705
12440
  var HTTP = /^https?:\/\//iu;
11706
12441
  var PROTOCOL_RELATIVE = /^\/\//u;
11707
12442
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -11761,7 +12496,7 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
11761
12496
  if (ctx.publicDir === null) {
11762
12497
  return "asset-unchecked";
11763
12498
  }
11764
- if (existsSync20(join33(ctx.publicDir, resolved))) {
12499
+ if (existsSync23(join36(ctx.publicDir, resolved))) {
11765
12500
  return null;
11766
12501
  }
11767
12502
  return {
@@ -11773,16 +12508,19 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
11773
12508
  };
11774
12509
  }
11775
12510
  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
- };
12511
+ if (ctx.routes.has(route)) {
12512
+ return fragment ? checkAnchor(route, fragment, site, ctx) : null;
12513
+ }
12514
+ if (ctx.redirects.has(route)) {
12515
+ return null;
11784
12516
  }
11785
- return fragment ? checkAnchor(route, fragment, site, ctx) : null;
12517
+ return {
12518
+ ...site,
12519
+ code: "BLUME_BROKEN_LINK",
12520
+ message: `Broken link to ${target}: no page resolves to ${route}.`,
12521
+ severity: "error",
12522
+ suggestion: "Check the path, or create the target page."
12523
+ };
11786
12524
  };
11787
12525
  var request = async (url, method) => {
11788
12526
  const controller = new AbortController;
@@ -11891,6 +12629,7 @@ var validateLinks = async (graph, options) => {
11891
12629
  const ctx = {
11892
12630
  anchors: buildAnchorIndex(graph.pages),
11893
12631
  publicDir: options.publicDir,
12632
+ redirects: new Set((options.redirects ?? []).map((redirect) => toRoute(redirect.from))),
11894
12633
  routes: new Set(graph.routes.keys())
11895
12634
  };
11896
12635
  const diagnostics = [];
@@ -11941,10 +12680,11 @@ var validateCommand = defineCommand10({
11941
12680
  try {
11942
12681
  const project = await scanProject(root, { mode: "build" });
11943
12682
  diagnostics.push(...project.diagnostics);
11944
- const publicDir = join34(root, "public");
12683
+ const publicDir = join37(root, "public");
11945
12684
  diagnostics.push(...await validateLinks(project.graph, {
11946
12685
  checkExternal: Boolean(args.external),
11947
- publicDir: existsSync21(publicDir) ? publicDir : null
12686
+ publicDir: existsSync24(publicDir) ? publicDir : null,
12687
+ redirects: project.config.redirects
11948
12688
  }));
11949
12689
  } catch (error) {
11950
12690
  if (error instanceof BlumeError) {
@@ -11985,5 +12725,5 @@ var main = defineCommand11({
11985
12725
  });
11986
12726
  runMain(main);
11987
12727
 
11988
- //# debugId=6F4400D6CD52CE0064756E2164756E21
12728
+ //# debugId=C5E764B7F3B6157B64756E2164756E21
11989
12729
  //# sourceMappingURL=index.js.map