blume 0.3.0 → 0.5.0

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 (128) hide show
  1. package/dist/cli/index.js +1631 -940
  2. package/dist/cli/index.js.map +62 -50
  3. package/dist/types/core/data.d.ts +2 -0
  4. package/dist/types/core/project.d.ts +12 -2
  5. package/dist/types/core/schema.d.ts +442 -292
  6. package/dist/types/core/types.d.ts +7 -0
  7. package/dist/types/migrate/mintlify/assets.d.ts +8 -0
  8. package/docs/01-quickstart.mdx +5 -16
  9. package/docs/02-deployment.mdx +21 -54
  10. package/docs/advanced/api-reference.mdx +34 -51
  11. package/docs/advanced/blog.mdx +9 -25
  12. package/docs/advanced/bridge.mdx +74 -0
  13. package/docs/advanced/changelog.mdx +10 -33
  14. package/docs/advanced/custom-pages.mdx +21 -78
  15. package/docs/advanced/meta.ts +8 -1
  16. package/docs/advanced/migrate.mdx +119 -0
  17. package/docs/configuration/ai.mdx +42 -103
  18. package/docs/configuration/analytics.mdx +20 -38
  19. package/docs/configuration/customization.mdx +40 -73
  20. package/docs/configuration/export.mdx +9 -34
  21. package/docs/configuration/index.mdx +67 -87
  22. package/docs/configuration/search.mdx +17 -54
  23. package/docs/configuration/seo.mdx +17 -48
  24. package/docs/configuration/theming.mdx +20 -42
  25. package/docs/content/components.mdx +95 -101
  26. package/docs/content/i18n.mdx +21 -72
  27. package/docs/content/index.mdx +18 -48
  28. package/docs/content/islands.mdx +25 -52
  29. package/docs/content/meta.mdx +23 -50
  30. package/docs/content/navigation.mdx +23 -62
  31. package/docs/content/sources.mdx +20 -83
  32. package/docs/content/syntax.mdx +37 -105
  33. package/docs/index.mdx +12 -41
  34. package/docs/reference/cli.mdx +47 -30
  35. package/docs/reference/frontmatter.mdx +7 -5
  36. package/package.json +11 -1
  37. package/src/astro/generate.ts +18 -8
  38. package/src/astro/integration.ts +26 -3
  39. package/src/astro/islands.ts +6 -2
  40. package/src/astro/markdown-negotiation.ts +17 -3
  41. package/src/astro/pages.ts +6 -1
  42. package/src/astro/static-assets.ts +117 -0
  43. package/src/astro/templates.ts +76 -30
  44. package/src/cli/args.ts +23 -0
  45. package/src/cli/commands/build.ts +129 -62
  46. package/src/cli/commands/check.ts +20 -0
  47. package/src/cli/commands/dev.ts +11 -2
  48. package/src/cli/commands/doctor.ts +10 -1
  49. package/src/cli/commands/eject.ts +3 -1
  50. package/src/cli/commands/init.ts +21 -1
  51. package/src/cli/commands/preview.ts +2 -1
  52. package/src/cli/commands/validate.ts +12 -1
  53. package/src/cli/dev-lock.ts +92 -0
  54. package/src/cli/log.ts +11 -0
  55. package/src/cli/prepare.ts +3 -0
  56. package/src/components/BlumePage.astro +8 -0
  57. package/src/components/Icon.astro +13 -10
  58. package/src/components/content/ApiField.astro +75 -0
  59. package/src/components/content/ParamField.astro +39 -0
  60. package/src/components/content/RequestField.astro +23 -0
  61. package/src/components/content/ResponseField.astro +23 -0
  62. package/src/components/content/Step.astro +1 -1
  63. package/src/components/content/YouTube.astro +35 -0
  64. package/src/components/content/youtube.ts +46 -0
  65. package/src/components/islands/ask-ai.tsx +14 -14
  66. package/src/components/layout/Breadcrumbs.astro +7 -2
  67. package/src/components/layout/NavTree.astro +24 -8
  68. package/src/components/layout/RootLayout.astro +56 -34
  69. package/src/components/layout/Search.astro +1 -1
  70. package/src/components/openapi/ApiOverview.astro +84 -0
  71. package/src/components/openapi/MethodBadge.astro +28 -0
  72. package/src/components/openapi/Operation.astro +140 -0
  73. package/src/components/openapi/ParametersTable.astro +97 -0
  74. package/src/components/openapi/RequestBody.astro +58 -0
  75. package/src/components/openapi/RequestPanel.astro +169 -0
  76. package/src/components/openapi/Responses.astro +91 -0
  77. package/src/components/openapi/SchemaProperty.astro +118 -0
  78. package/src/components/openapi/SchemaTable.astro +86 -0
  79. package/src/components/openapi/helpers.ts +238 -0
  80. package/src/components/openapi/panel.ts +59 -0
  81. package/src/components/openapi/snippets.ts +201 -0
  82. package/src/components/props.ts +3 -0
  83. package/src/core/assets.ts +31 -0
  84. package/src/core/bridge.ts +10 -0
  85. package/src/core/builtin-tags.ts +6 -0
  86. package/src/core/data.ts +2 -0
  87. package/src/core/diagnostics.ts +6 -1
  88. package/src/core/gitignore.ts +30 -0
  89. package/src/core/links.ts +60 -19
  90. package/src/core/project-graph.ts +5 -1
  91. package/src/core/project.ts +25 -3
  92. package/src/core/schema.ts +54 -6
  93. package/src/core/sources/mdx-remote.ts +54 -8
  94. package/src/core/sources/mintlify.ts +1 -1
  95. package/src/core/sources/normalize.ts +6 -1
  96. package/src/core/sources/notion.ts +49 -5
  97. package/src/core/sources/resolve.ts +28 -6
  98. package/src/core/sources/sanity.ts +5 -1
  99. package/src/core/types.ts +7 -0
  100. package/src/deploy/rss.ts +1 -8
  101. package/src/deploy/sitemap.ts +20 -1
  102. package/src/deploy/xml.ts +8 -0
  103. package/src/markdown/directives.ts +15 -7
  104. package/src/markdown/package-commands.ts +26 -4
  105. package/src/migrate/fumadocs/content.ts +14 -1
  106. package/src/migrate/fumadocs/groups.ts +7 -0
  107. package/src/migrate/fumadocs/index.ts +5 -2
  108. package/src/migrate/mintlify/assets.ts +46 -0
  109. package/src/migrate/mintlify/config.ts +153 -1
  110. package/src/migrate/mintlify/content.ts +8 -2
  111. package/src/migrate/mintlify/index.ts +111 -46
  112. package/src/migrate/shared.ts +12 -27
  113. package/src/og/card.ts +14 -2
  114. package/src/openapi/model.ts +174 -0
  115. package/src/openapi/parse.ts +48 -0
  116. package/src/openapi/references.ts +164 -0
  117. package/src/openapi/render-mdx.ts +76 -0
  118. package/src/openapi/scalar.ts +15 -103
  119. package/src/openapi/source.ts +140 -0
  120. package/src/registry/eject.ts +28 -5
  121. package/src/registry/registry.ts +6 -0
  122. package/src/registry/rewrite-imports.ts +31 -19
  123. package/src/search/documents.ts +23 -5
  124. package/src/search/sync/algolia.ts +5 -1
  125. package/src/search/sync/typesense.ts +24 -16
  126. package/src/theme/chrome-icons.ts +22 -0
  127. package/src/theme/icons.ts +151 -161
  128. package/src/theme/palette.ts +26 -7
package/dist/cli/index.js CHANGED
@@ -209,6 +209,12 @@ var CONTENT_COMPONENTS = [
209
209
  file: "Prompt.astro",
210
210
  name: "prompt",
211
211
  tag: "Prompt"
212
+ },
213
+ {
214
+ description: "A responsive, privacy-friendly YouTube embed.",
215
+ file: "YouTube.astro",
216
+ name: "youtube",
217
+ tag: "YouTube"
212
218
  }
213
219
  ];
214
220
  var registry = [
@@ -254,18 +260,22 @@ var findItem = (name) => registry.find((item) => item.name === name);
254
260
 
255
261
  // src/registry/rewrite-imports.ts
256
262
  import { dirname as dirname2, relative, resolve } from "pathe";
257
- var RELATIVE_IMPORT = /(?<kw>\bfrom|\bimport)(?<gap>\s+)(?<quote>["'])(?<spec>\.[^"']*)\k<quote>/gu;
258
- var rewriteImports = (content, sourceFile, srcRoot) => content.replaceAll(RELATIVE_IMPORT, (match, kw, gap, quote, spec) => {
259
- const resolved = resolve(dirname2(sourceFile), spec);
260
- if (resolved === sourceFile) {
261
- return match;
262
- }
263
- const rel = relative(srcRoot, resolved);
264
- if (rel.startsWith("..")) {
265
- return match;
266
- }
267
- return `${kw}${gap}${quote}blume/${rel}${quote}`;
268
- });
263
+ var FROM_IMPORT = /(?<prefix>^[ \t]*(?:import|export)\b[^;]*?\bfrom[ \t]*)(?<quote>["'])(?<spec>\.[^"']*)\k<quote>/gmu;
264
+ var SIDE_EFFECT_IMPORT = /(?<prefix>^[ \t]*import[ \t]+)(?<quote>["'])(?<spec>\.[^"']*)\k<quote>/gmu;
265
+ var rewriteImports = (content, sourceFile, srcRoot) => {
266
+ const rewrite = (match, prefix, quote, spec) => {
267
+ const resolved = resolve(dirname2(sourceFile), spec);
268
+ if (resolved === sourceFile) {
269
+ return match;
270
+ }
271
+ const rel = relative(srcRoot, resolved);
272
+ if (rel.startsWith("..")) {
273
+ return match;
274
+ }
275
+ return `${prefix}${quote}blume/${rel}${quote}`;
276
+ };
277
+ return content.replaceAll(FROM_IMPORT, rewrite).replaceAll(SIDE_EFFECT_IMPORT, rewrite);
278
+ };
269
279
 
270
280
  // src/cli/log.ts
271
281
  import { consola } from "consola";
@@ -326,7 +336,7 @@ var locatePath = (source, path) => {
326
336
  if (typeof segment !== "string") {
327
337
  continue;
328
338
  }
329
- const matcher = new RegExp(`${escapeRegExp(segment)}\\s*[:=]`, "gu");
339
+ const matcher = new RegExp(`(?<![\\w$])${escapeRegExp(segment)}\\s*[:=]`, "gu");
330
340
  matcher.lastIndex = cursor;
331
341
  const match = matcher.exec(source);
332
342
  if (!match) {
@@ -408,6 +418,9 @@ var countBySeverity = (diagnostics) => {
408
418
 
409
419
  // src/cli/log.ts
410
420
  var logger = consola.withTag("blume");
421
+ var flushStdout = () => new Promise((resolve2) => {
422
+ process.stdout.write("", () => resolve2());
423
+ });
411
424
  var reportDiagnosticsJson = (diagnostics, root) => {
412
425
  const enriched = diagnostics.map((diagnostic) => {
413
426
  const withDocs = enrichDiagnostic(diagnostic);
@@ -500,11 +513,11 @@ Next steps:
500
513
  });
501
514
 
502
515
  // src/cli/commands/build.ts
503
- import { existsSync as existsSync15 } from "node:fs";
504
- import { readdir, stat, writeFile as writeFile6 } from "node:fs/promises";
516
+ import { existsSync as existsSync17 } from "node:fs";
517
+ import { readdir, stat, writeFile as writeFile7 } from "node:fs/promises";
505
518
  import { build } from "astro";
506
519
  import { defineCommand as defineCommand2 } from "citty";
507
- import { join as join24 } from "pathe";
520
+ import { join as join28 } from "pathe";
508
521
 
509
522
  // src/core/frontmatter.ts
510
523
  import baseMatter from "gray-matter";
@@ -595,6 +608,29 @@ var buildLlmsFiles = async (project) => ({
595
608
  index: buildIndex(project)
596
609
  });
597
610
 
611
+ // src/core/gitignore.ts
612
+ import { existsSync as existsSync3 } from "node:fs";
613
+ import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
614
+ import { join as join5 } from "pathe";
615
+ var gitignoreKey = (line) => line.trim().replace(/\/+$/u, "");
616
+ var ensureGitignore = async (root, entries) => {
617
+ const path = join5(root, ".gitignore");
618
+ const existing = existsSync3(path) ? await readFile3(path, "utf-8") : "";
619
+ const present = new Set(existing.split(`
620
+ `).map(gitignoreKey).filter(Boolean));
621
+ const added = entries.filter((entry) => !present.has(gitignoreKey(entry)));
622
+ if (added.length === 0) {
623
+ return [];
624
+ }
625
+ const gap = existing.length > 0 && !existing.endsWith(`
626
+ `) ? `
627
+ ` : "";
628
+ await writeFile2(path, `${existing}${gap}${added.join(`
629
+ `)}
630
+ `, "utf-8");
631
+ return added;
632
+ };
633
+
598
634
  // src/search/providers.ts
599
635
  var SEARCH_PROVIDERS = {
600
636
  algolia: {
@@ -699,14 +735,24 @@ var buildRobots = (project) => {
699
735
  `;
700
736
  };
701
737
 
738
+ // src/deploy/xml.ts
739
+ var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
740
+
702
741
  // src/deploy/sitemap.ts
742
+ var lastmodTag = (value) => {
743
+ if (!value) {
744
+ return "";
745
+ }
746
+ const date = new Date(value);
747
+ return Number.isNaN(date.getTime()) ? "" : `<lastmod>${date.toISOString().slice(0, 10)}</lastmod>`;
748
+ };
703
749
  var buildSitemap = (project) => {
704
750
  const { site } = project.config.deployment;
705
751
  if (!(site && project.config.seo.sitemap)) {
706
752
  return null;
707
753
  }
708
754
  const base = site.replace(/\/$/u, "");
709
- const urls = project.graph.pages.filter((page) => !(page.meta.draft || page.meta.sidebar.hidden || page.meta.seo.noindex)).map((page) => ` <url><loc>${base}${page.route}</loc></url>`).toSorted();
755
+ const urls = project.graph.pages.filter((page) => !(page.meta.draft || page.meta.sidebar.hidden || page.meta.seo.noindex)).map((page) => ` <url><loc>${escapeXml(encodeURI(`${base}${page.route}`))}</loc>${lastmodTag(page.lastModified)}</url>`).toSorted();
710
756
  return `<?xml version="1.0" encoding="UTF-8"?>
711
757
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
712
758
  ${urls.join(`
@@ -716,7 +762,7 @@ ${urls.join(`
716
762
  };
717
763
 
718
764
  // src/search/build.ts
719
- import { join as join5 } from "pathe";
765
+ import { join as join6 } from "pathe";
720
766
  var buildSearchIndex = async (outDir) => {
721
767
  const pagefind = await import("pagefind");
722
768
  const { index } = await pagefind.createIndex({});
@@ -724,7 +770,7 @@ var buildSearchIndex = async (outDir) => {
724
770
  throw new Error("Failed to create Pagefind index.");
725
771
  }
726
772
  const result = await index.addDirectory({ path: outDir });
727
- await index.writeFiles({ outputPath: join5(outDir, "pagefind") });
773
+ await index.writeFiles({ outputPath: join6(outDir, "pagefind") });
728
774
  await pagefind.close();
729
775
  return result.page_count;
730
776
  };
@@ -2580,7 +2626,19 @@ var LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
2580
2626
  var HEADING_MARK = /^#{1,6}\s+/gmu;
2581
2627
  var MARKDOWN_PUNCT = /[*_~>]+/gu;
2582
2628
  var WHITESPACE = /\s+/gu;
2583
- var toPlainText = (markdown) => markdown.replaceAll(CODE_FENCE, " ").replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>").replaceAll(HTML_OR_JSX, " ").replaceAll(INLINE_CODE, "$<code>").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT, " ").replaceAll(WHITESPACE, " ").trim();
2629
+ var toPlainText = (markdown) => {
2630
+ const withoutBlocks = markdown.replaceAll(CODE_FENCE, " ").replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>");
2631
+ const pieces = [];
2632
+ let cursor = 0;
2633
+ for (const match of withoutBlocks.matchAll(INLINE_CODE)) {
2634
+ const start = match.index ?? 0;
2635
+ pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "));
2636
+ pieces.push(match.groups?.code ?? "");
2637
+ cursor = start + match[0].length;
2638
+ }
2639
+ pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
2640
+ return pieces.join("").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT, " ").replaceAll(WHITESPACE, " ").trim();
2641
+ };
2584
2642
  var buildCrumbIndex = (sidebar) => {
2585
2643
  const index = new Map;
2586
2644
  const walk = (nodes, trail) => {
@@ -2654,7 +2712,7 @@ var syncAlgolia = async (records, config) => {
2654
2712
  }
2655
2713
  const { algoliasearch } = await import("algoliasearch");
2656
2714
  const client = algoliasearch(config.appId, adminKey);
2657
- await client.saveObjects({
2715
+ await client.replaceAllObjects({
2658
2716
  indexName: config.indexName,
2659
2717
  objects: records.map((record) => ({ ...record, objectID: record._id }))
2660
2718
  });
@@ -2706,20 +2764,21 @@ var syncTypesense = async (records, config) => {
2706
2764
  }
2707
2765
  ]
2708
2766
  });
2709
- try {
2710
- await client.collections(config.collection).retrieve();
2711
- } catch {
2712
- await client.collections().create({
2713
- fields: [
2714
- { name: "title", type: "string" },
2715
- { name: "description", optional: true, type: "string" },
2716
- { name: "content", type: "string" },
2717
- { name: "url", type: "string" },
2718
- { facet: true, name: "tag", optional: true, type: "string" }
2719
- ],
2720
- name: config.collection
2721
- });
2722
- }
2767
+ const collection = client.collections(config.collection);
2768
+ const exists = await collection.retrieve().then(() => true).catch(() => false);
2769
+ if (exists) {
2770
+ await collection.delete();
2771
+ }
2772
+ await client.collections().create({
2773
+ fields: [
2774
+ { name: "title", type: "string" },
2775
+ { name: "description", optional: true, type: "string" },
2776
+ { name: "content", type: "string" },
2777
+ { name: "url", type: "string" },
2778
+ { facet: true, name: "tag", optional: true, type: "string" }
2779
+ ],
2780
+ name: config.collection
2781
+ });
2723
2782
  const documents = records.map((record) => ({
2724
2783
  content: record.content,
2725
2784
  description: record.description,
@@ -2753,20 +2812,112 @@ var syncSearchProvider = async (project, reporter) => {
2753
2812
  }
2754
2813
  };
2755
2814
 
2815
+ // src/cli/dev-lock.ts
2816
+ import {
2817
+ existsSync as existsSync5,
2818
+ mkdirSync,
2819
+ readFileSync as readFileSync2,
2820
+ rmSync,
2821
+ writeFileSync
2822
+ } from "node:fs";
2823
+ import { join as join8 } from "pathe";
2824
+
2825
+ // src/core/project.ts
2826
+ import { existsSync as existsSync4 } from "node:fs";
2827
+ import { isAbsolute, join as join7, resolve as resolve2 } from "pathe";
2828
+ var CONFIG_FILENAMES = [
2829
+ "blume.config.ts",
2830
+ "blume.config.mjs",
2831
+ "blume.config.js"
2832
+ ];
2833
+ var THEME_FILENAMES = ["theme.css"];
2834
+ var COMPONENTS_FILENAMES = ["components.tsx", "components.ts"];
2835
+ var firstExisting = (root, names) => {
2836
+ for (const name of names) {
2837
+ const candidate = join7(root, name);
2838
+ if (existsSync4(candidate)) {
2839
+ return candidate;
2840
+ }
2841
+ }
2842
+ return null;
2843
+ };
2844
+ var findConfigFile = (root) => firstExisting(root, CONFIG_FILENAMES);
2845
+ var resolveRuntimeDir = (root, runtimeDir = ".blume") => isAbsolute(runtimeDir) ? runtimeDir : join7(resolve2(root), runtimeDir);
2846
+ var resolveProjectContext = (root, config, options) => {
2847
+ const absoluteRoot = resolve2(root);
2848
+ const contentRoot = isAbsolute(config.content.root) ? config.content.root : join7(absoluteRoot, config.content.root);
2849
+ const pagesPath = join7(absoluteRoot, config.content.pages);
2850
+ const pagesRoot = existsSync4(pagesPath) ? pagesPath : null;
2851
+ const outDir = resolveRuntimeDir(absoluteRoot, options?.runtimeDir);
2852
+ const distDir = options?.runtimeDir ? join7(outDir, "dist") : join7(absoluteRoot, "dist");
2853
+ return {
2854
+ componentsFile: firstExisting(absoluteRoot, COMPONENTS_FILENAMES),
2855
+ configFile: findConfigFile(absoluteRoot),
2856
+ contentRoot,
2857
+ distDir,
2858
+ outDir,
2859
+ pagesRoot,
2860
+ root: absoluteRoot,
2861
+ themeFile: firstExisting(absoluteRoot, THEME_FILENAMES)
2862
+ };
2863
+ };
2864
+
2865
+ // src/cli/dev-lock.ts
2866
+ var lockPath = (outDir) => join8(outDir, "dev.lock");
2867
+ var isDevLocked = (outDir) => {
2868
+ const path = lockPath(outDir);
2869
+ if (!existsSync5(path)) {
2870
+ return false;
2871
+ }
2872
+ const pid = Number.parseInt(readFileSync2(path, "utf-8").trim(), 10);
2873
+ if (!(Number.isInteger(pid) && pid > 0)) {
2874
+ return false;
2875
+ }
2876
+ try {
2877
+ process.kill(pid, 0);
2878
+ return true;
2879
+ } catch {
2880
+ return false;
2881
+ }
2882
+ };
2883
+ var acquireDevLock = (outDir) => {
2884
+ const path = lockPath(outDir);
2885
+ mkdirSync(outDir, { recursive: true });
2886
+ writeFileSync(path, String(process.pid));
2887
+ let released = false;
2888
+ return () => {
2889
+ if (released) {
2890
+ return;
2891
+ }
2892
+ released = true;
2893
+ try {
2894
+ if (existsSync5(path) && readFileSync2(path, "utf-8").trim() === String(process.pid)) {
2895
+ rmSync(path, { force: true });
2896
+ }
2897
+ } catch {}
2898
+ };
2899
+ };
2900
+ var refuseIfDevRunning = (root, action, runtimeDir) => {
2901
+ if (isDevLocked(resolveRuntimeDir(root, runtimeDir))) {
2902
+ logger.error(`A \`blume dev\` server is running against .blume; ${action} would corrupt it. Stop the dev server, or re-run with --isolated to build/verify against .blume-verify without touching it.`);
2903
+ process.exit(1);
2904
+ }
2905
+ };
2906
+
2756
2907
  // src/astro/generate.ts
2757
- import { existsSync as existsSync6, readFileSync as readFileSync5, realpathSync } from "node:fs";
2908
+ import { existsSync as existsSync9, readFileSync as readFileSync6, realpathSync } from "node:fs";
2758
2909
  import {
2759
2910
  lstat,
2760
- mkdir as mkdir2,
2761
- readFile as readFile7,
2911
+ mkdir as mkdir3,
2912
+ readFile as readFile10,
2762
2913
  rename,
2763
2914
  rm,
2764
2915
  symlink,
2765
- writeFile as writeFile2
2916
+ writeFile as writeFile4
2766
2917
  } from "node:fs/promises";
2767
- import { createRequire as createRequire4 } from "node:module";
2918
+ import { createRequire as createRequire5 } from "node:module";
2768
2919
  import { pathToFileURL as pathToFileURL2 } from "node:url";
2769
- import { basename as basename2, dirname as dirname7, join as join11, normalize, relative as relative6 } from "pathe";
2920
+ import { basename as basename2, dirname as dirname7, join as join17, normalize as normalize3, relative as relative6 } from "pathe";
2770
2921
  import { glob as glob4 } from "tinyglobby";
2771
2922
 
2772
2923
  // src/ai/ask-data.ts
@@ -2838,7 +2989,7 @@ var askBackendRuntimeDep = (ask) => {
2838
2989
  };
2839
2990
 
2840
2991
  // src/ai/markdown.ts
2841
- import { readFile as readFile3 } from "node:fs/promises";
2992
+ import { readFile as readFile4 } from "node:fs/promises";
2842
2993
  var buildRawMarkdown = async (project) => {
2843
2994
  const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
2844
2995
  const readRoute = async (route) => {
@@ -2846,7 +2997,7 @@ var buildRawMarkdown = async (project) => {
2846
2997
  if (page) {
2847
2998
  return await readEntryText(project, page);
2848
2999
  }
2849
- return route.sourcePath ? await readFile3(route.sourcePath, "utf-8") : "";
3000
+ return route.sourcePath ? await readFile4(route.sourcePath, "utf-8") : "";
2850
3001
  };
2851
3002
  const entries = await Promise.all(project.manifest.routes.map(async (route) => [route.path, await readRoute(route)]));
2852
3003
  return Object.fromEntries(entries);
@@ -2943,6 +3094,7 @@ var buildMcpServerCard = (input) => ({
2943
3094
  var BUILTIN_MDX_TAGS = new Set([
2944
3095
  "Accordion",
2945
3096
  "AccordionItem",
3097
+ "ApiOverview",
2946
3098
  "AutoTypeTable",
2947
3099
  "Badge",
2948
3100
  "Callout",
@@ -2961,8 +3113,12 @@ var BUILTIN_MDX_TAGS = new Set([
2961
3113
  "GithubInfo",
2962
3114
  "Icon",
2963
3115
  "Math",
3116
+ "Operation",
2964
3117
  "Panel",
3118
+ "ParamField",
2965
3119
  "Prompt",
3120
+ "RequestField",
3121
+ "ResponseField",
2966
3122
  "Step",
2967
3123
  "Steps",
2968
3124
  "Tab",
@@ -2971,7 +3127,8 @@ var BUILTIN_MDX_TAGS = new Set([
2971
3127
  "Tooltip",
2972
3128
  "Tree",
2973
3129
  "TypeTable",
2974
- "Visibility"
3130
+ "Visibility",
3131
+ "YouTube"
2975
3132
  ]);
2976
3133
 
2977
3134
  // src/core/component-diagnostics.ts
@@ -3000,8 +3157,8 @@ var validateUsedComponents = (pages, extraTags, registryNames) => {
3000
3157
  };
3001
3158
 
3002
3159
  // src/core/component-overrides.ts
3003
- import { existsSync as existsSync3 } from "node:fs";
3004
- import { dirname as dirname4, extname, isAbsolute, resolve as resolve2 } from "pathe";
3160
+ import { existsSync as existsSync6 } from "node:fs";
3161
+ import { dirname as dirname4, extname, isAbsolute as isAbsolute2, resolve as resolve3 } from "pathe";
3005
3162
  import ts from "typescript";
3006
3163
  var GROUPS = ["mdx", "layout", "islands"];
3007
3164
  var FRAMEWORK_BY_EXT = {
@@ -3089,18 +3246,18 @@ var findDefaultExportObject = (sourceFile) => {
3089
3246
  var probeExtension = (base) => {
3090
3247
  for (const extension of COMPONENT_EXTS) {
3091
3248
  const candidate = `${base}.${extension}`;
3092
- if (existsSync3(candidate)) {
3249
+ if (existsSync6(candidate)) {
3093
3250
  return candidate;
3094
3251
  }
3095
3252
  }
3096
3253
  return null;
3097
3254
  };
3098
3255
  var toImport = (specifier, imported, dir) => {
3099
- const relative4 = specifier.startsWith(".") || isAbsolute(specifier);
3256
+ const relative4 = specifier.startsWith(".") || isAbsolute2(specifier);
3100
3257
  let path = specifier;
3101
3258
  let extension = extname(specifier).slice(1).toLowerCase();
3102
3259
  if (relative4) {
3103
- const absolute = isAbsolute(specifier) ? specifier : resolve2(dir, specifier);
3260
+ const absolute = isAbsolute2(specifier) ? specifier : resolve3(dir, specifier);
3104
3261
  if (extension) {
3105
3262
  path = absolute;
3106
3263
  } else {
@@ -3348,147 +3505,104 @@ var resolveUIStrings = (locale, options) => {
3348
3505
  };
3349
3506
 
3350
3507
  // src/theme/icons.ts
3351
- var icons = {
3352
- "arrow-left": '<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>',
3353
- "arrow-right": '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
3354
- "arrow-up": '<path d="m5 12 7-7 7 7"/><path d="M12 19V5"/>',
3355
- "arrow-up-right": '<path d="M7 7h10v10"/><path d="M7 17 17 7"/>',
3356
- "badge-alert": '<path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.78 4.78 4 4 0 0 1-6.74 0 4 4 0 0 1-4.78-4.78 4 4 0 0 1 0-6.75Z"/><path d="M12 8v4"/><path d="M12 16h.01"/>',
3357
- ban: '<circle cx="12" cy="12" r="10"/><path d="m4.93 4.93 14.14 14.14"/>',
3358
- "book-open": '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>',
3359
- "book-open-cover": '<path d="M12 7v14"/><path d="M3 18a2 2 0 0 1 2-2h7V5H5a2 2 0 0 0-2 2Z"/><path d="M21 18a2 2 0 0 0-2-2h-7V5h7a2 2 0 0 1 2 2Z"/>',
3360
- "brand-x": '<path d="m4 4 11.7 16H20L8.3 4Z"/><path d="M4 20 20 4"/>',
3361
- check: '<path d="M20 6 9 17l-5-5"/>',
3362
- "chevron-down": '<path d="m6 9 6 6 6-6"/>',
3363
- "chevron-right": '<path d="m9 18 6-6-6-6"/>',
3364
- "circle-check": '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>',
3365
- "circle-x": '<circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/>',
3366
- clock: '<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>',
3367
- copy: '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
3368
- download: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>',
3369
- "external-link": '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
3370
- file: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
3371
- flag: '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><path d="M4 22V15"/>',
3372
- folder: '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
3373
- gear: '<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/>',
3374
- github: '<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.3-.8 2.1-.36.16-.78.24-1.2.24-1.4 0-2.4-.7-3-2-.3-.6-.8-.9-1.2-.9-.4 0-.8.2-.8.5 0 .5.7.8 1 1.2.7 1.5 2 2.4 4 2.4.43 0 .84-.04 1.2-.13V22"/>',
3375
- globe: '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
3376
- info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
3377
- js: '<path d="M8 8v7a2 2 0 1 1-4 0"/><path d="M16 15a2 2 0 1 0 2-2 2 2 0 1 1 2-2"/><path d="M20 8v.01"/>',
3378
- key: '<path d="M21 2 11.4 11.6"/><circle cx="7.5" cy="16.5" r="5.5"/><path d="m15 7 2 2"/><path d="m12 10 2 2"/>',
3379
- leaf: '<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"/><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"/>',
3380
- lightbulb: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
3381
- link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
3382
- linkedin: '<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-4 0v7h-4v-7a6 6 0 0 1 6-6z"/><rect width="4" height="12" x="2" y="9"/><circle cx="4" cy="4" r="2"/>',
3383
- lock: '<rect width="18" height="11" x="3" y="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
3384
- menu: '<line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/>',
3385
- "message-circle": '<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/>',
3386
- moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
3387
- "panel-left": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/>',
3388
- "panel-left-close": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m16 15-3-3 3-3"/>',
3389
- "panel-right": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/>',
3390
- "panel-right-close": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m8 9 3 3-3 3"/>',
3391
- paperclip: '<path d="m16 6-8.41 8.41a2 2 0 0 0 2.83 2.83L18.83 8.83a4 4 0 0 0-5.66-5.66L4.76 11.59a6 6 0 0 0 8.49 8.49L21.66 11.66"/>',
3392
- "puzzle-piece": '<path d="M15.39 4.39a2.1 2.1 0 0 0-2.97 0L12 4.82l-.42-.43a2.1 2.1 0 1 0-2.97 2.97l.43.42L7.6 9.22H4.75A1.75 1.75 0 0 0 3 10.97v8.28C3 20.22 3.78 21 4.75 21h8.28c.97 0 1.75-.78 1.75-1.75V16.4l1.44-1.44.42.43a2.1 2.1 0 1 0 2.97-2.97l-.43-.42.43-.42a2.1 2.1 0 1 0-2.97-2.97l-.42.43-1.44-1.44.61-.61a2.1 2.1 0 0 0 0-2.97Z"/>',
3393
- python: '<path d="M12 2h4a4 4 0 0 1 4 4v3H8a4 4 0 0 0-4 4v1"/><path d="M12 22H8a4 4 0 0 1-4-4v-3h12a4 4 0 0 0 4-4v-1"/><path d="M9 6h.01"/><path d="M15 18h.01"/>',
3394
- rocket: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
3395
- rss: '<path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/>',
3396
- search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
3397
- sparkles: '<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3Z"/><path d="M5 3v4"/><path d="M3 5h4"/><path d="M19 17v4"/><path d="M17 19h4"/>',
3398
- star: '<path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01Z"/>',
3399
- sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
3400
- "text-align-start": '<path d="M4 6h16"/><path d="M4 10h10"/><path d="M4 14h16"/><path d="M4 18h10"/>',
3401
- "thumbs-down": '<path d="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"/>',
3402
- "thumbs-up": '<path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/>',
3403
- "triangle-alert": '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
3404
- x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>'
3405
- };
3406
- var iconAliases = {
3407
- "alien-8bit": "sparkles",
3408
- "arrow-up-right-from-square": "external-link",
3409
- "book-open-reader": "book-open",
3410
- "circle-info": "info",
3411
- close: "x",
3412
- "external-link-alt": "external-link",
3413
- "fa-github": "github",
3414
- "fa-linkedin": "linkedin",
3415
- "fa-x-twitter": "brand-x",
3416
- "file-lines": "file",
3417
- javascript: "js",
3418
- "panel-left-open": "panel-left",
3419
- "panel-right-open": "panel-right",
3420
- times: "x",
3421
- "x-twitter": "brand-x"
3422
- };
3423
- var libraryPrefixes = [
3424
- "fa-brands",
3425
- "fa-duotone",
3426
- "fa-light",
3427
- "fa-regular",
3428
- "fa-sharp-solid",
3429
- "fa-solid",
3430
- "fa-thin",
3431
- "fa",
3432
- "fab",
3433
- "fad",
3434
- "fal",
3435
- "far",
3436
- "fas",
3437
- "fat",
3438
- "lucide",
3439
- "tabler",
3440
- "ti"
3441
- ];
3442
- var normalizedIconName = (name) => name.trim().toLowerCase().replaceAll(/[\s_]+/gu, "-");
3443
- var isString = (value) => typeof value === "string";
3444
- var withoutLibraryPrefix = (name) => {
3445
- let normalized = normalizedIconName(name).replaceAll(/^icon-/gu, "");
3446
- let changed = true;
3447
- while (changed) {
3448
- changed = false;
3449
- for (const prefix of libraryPrefixes) {
3450
- if (normalized.startsWith(`${prefix}-`)) {
3451
- normalized = normalized.slice(prefix.length + 1);
3452
- changed = true;
3453
- }
3454
- if (normalized.startsWith(`${prefix}:`)) {
3455
- normalized = normalized.slice(prefix.length + 1);
3456
- changed = true;
3457
- }
3458
- }
3508
+ import { createRequire as createRequire2 } from "node:module";
3509
+ import { getIconData, iconToSVG } from "@iconify/utils";
3510
+ var requireJson = createRequire2(import.meta.url);
3511
+ var loadSet = (pkg) => requireJson(pkg);
3512
+ var SETS = {
3513
+ "fa6-brands": loadSet("@iconify-json/fa6-brands/icons.json"),
3514
+ "fa6-regular": loadSet("@iconify-json/fa6-regular/icons.json"),
3515
+ "fa6-solid": loadSet("@iconify-json/fa6-solid/icons.json"),
3516
+ lucide: loadSet("@iconify-json/lucide/icons.json"),
3517
+ tabler: loadSet("@iconify-json/tabler/icons.json")
3518
+ };
3519
+ var DEFAULT_SET = "lucide";
3520
+ var LIBRARY_SETS = {
3521
+ fa: "fa6-solid",
3522
+ "font-awesome": "fa6-solid",
3523
+ fontawesome: "fa6-solid",
3524
+ lucide: "lucide",
3525
+ tabler: "tabler"
3526
+ };
3527
+ var ICON_TYPE_SETS = {
3528
+ brands: "fa6-brands",
3529
+ duotone: "fa6-solid",
3530
+ light: "fa6-solid",
3531
+ regular: "fa6-regular",
3532
+ "sharp-solid": "fa6-solid",
3533
+ solid: "fa6-solid",
3534
+ thin: "fa6-solid"
3535
+ };
3536
+ var PREFIX_SETS = {
3537
+ ...LIBRARY_SETS,
3538
+ fa: "fa6-solid",
3539
+ "fa-brands": "fa6-brands",
3540
+ "fa-regular": "fa6-regular",
3541
+ "fa-solid": "fa6-solid",
3542
+ "fa6-brands": "fa6-brands",
3543
+ "fa6-regular": "fa6-regular",
3544
+ "fa6-solid": "fa6-solid",
3545
+ fab: "fa6-brands",
3546
+ far: "fa6-regular",
3547
+ fas: "fa6-solid",
3548
+ ti: "tabler"
3549
+ };
3550
+ var normalize = (name) => name.trim().toLowerCase().replaceAll(/[\s_]+/gu, "-");
3551
+ var setFor = (options) => {
3552
+ if (options.iconType) {
3553
+ const set = ICON_TYPE_SETS[normalize(options.iconType)];
3554
+ if (set) {
3555
+ return set;
3556
+ }
3557
+ }
3558
+ if (options.library) {
3559
+ const set = LIBRARY_SETS[normalize(options.library)];
3560
+ if (set) {
3561
+ return set;
3562
+ }
3563
+ }
3564
+ return DEFAULT_SET;
3565
+ };
3566
+ var fromSet = (setName, iconName) => {
3567
+ const set = SETS[setName];
3568
+ const data = set && getIconData(set, iconName);
3569
+ if (!data) {
3570
+ return null;
3459
3571
  }
3460
- return normalized;
3572
+ const { attributes, body } = iconToSVG(data, { height: "auto" });
3573
+ return { body, name: iconName, viewBox: attributes.viewBox };
3461
3574
  };
3462
- var resolveIcon = (name, iconType) => {
3463
- const normalized = normalizedIconName(name);
3464
- const stripped = withoutLibraryPrefix(name);
3465
- const type = iconType ? normalizedIconName(iconType) : null;
3466
- const candidates = [
3467
- normalized,
3468
- stripped,
3469
- type ? `${type}-${stripped}` : null,
3470
- iconAliases[normalized],
3471
- iconAliases[stripped]
3472
- ].filter(isString);
3473
- for (const candidate of candidates) {
3474
- const markup = icons[candidate];
3475
- if (markup) {
3476
- return { markup, name: candidate };
3575
+ var fromFaSet = (setName, name) => fromSet(setName, name) ?? fromSet("fa6-brands", name);
3576
+ var resolveInSet = (setName, name) => setName.startsWith("fa6-") ? fromFaSet(setName, name) : fromSet(setName, name);
3577
+ var resolveIcon = (name, options = {}) => {
3578
+ const normalized = normalize(name);
3579
+ const colon = normalized.indexOf(":");
3580
+ if (colon > 0) {
3581
+ const setName = PREFIX_SETS[normalized.slice(0, colon)];
3582
+ if (setName) {
3583
+ return resolveInSet(setName, normalized.slice(colon + 1));
3477
3584
  }
3478
3585
  }
3479
- return null;
3586
+ return resolveInSet(setFor(options), normalized);
3587
+ };
3588
+ var hasIcon = (name, options = {}) => {
3589
+ if (resolveIcon(name, options)) {
3590
+ return true;
3591
+ }
3592
+ const normalized = normalize(name);
3593
+ const bare = normalized.includes(":") ? normalized.slice(normalized.indexOf(":") + 1) : normalized;
3594
+ return Object.values(SETS).some((set) => getIconData(set, bare) !== null);
3480
3595
  };
3481
- var hasIcon = (name, iconType) => resolveIcon(name, iconType) !== null;
3482
3596
 
3483
3597
  // src/core/nav-diagnostics.ts
3484
3598
  var IMAGE_ICON = /^(?:https?:\/\/|data:image\/|\/|\.{1,2}\/)|\.(?:avif|gif|jpe?g|png|svg|webp)$/iu;
3485
3599
  var isAssetIcon = (value) => value.startsWith("<") || IMAGE_ICON.test(value);
3486
3600
  var flattenNodes = (nodes) => nodes.flatMap((node) => node.kind === "group" ? [node, ...flattenNodes(node.children)] : [node]);
3487
3601
  var collectIcons = (navigation) => {
3488
- const icons2 = [];
3602
+ const icons = [];
3489
3603
  const push = (icon, where) => {
3490
3604
  if (icon) {
3491
- icons2.push({ icon, where });
3605
+ icons.push({ icon, where });
3492
3606
  }
3493
3607
  };
3494
3608
  for (const tab of navigation.tabs) {
@@ -3511,7 +3625,7 @@ var collectIcons = (navigation) => {
3511
3625
  push(node.icon, `"${node.label}"`);
3512
3626
  }
3513
3627
  }
3514
- return icons2;
3628
+ return icons;
3515
3629
  };
3516
3630
  var validateNavIcons = (navigation) => {
3517
3631
  const seen = new Set;
@@ -3621,10 +3735,10 @@ var validateNavStructure = (navigation, pages) => [
3621
3735
  ];
3622
3736
 
3623
3737
  // src/core/tsconfig-aliases.ts
3624
- import { existsSync as existsSync4, readFileSync as readFileSync2, statSync } from "node:fs";
3625
- import { createRequire as createRequire2 } from "node:module";
3738
+ import { existsSync as existsSync7, readFileSync as readFileSync3, statSync } from "node:fs";
3739
+ import { createRequire as createRequire3 } from "node:module";
3626
3740
  import { pathToFileURL } from "node:url";
3627
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join6, resolve as resolve3 } from "pathe";
3741
+ import { dirname as dirname5, isAbsolute as isAbsolute3, join as join9, resolve as resolve4 } from "pathe";
3628
3742
  var stripJsonComments = (text) => {
3629
3743
  let out = "";
3630
3744
  let inString = false;
@@ -3678,16 +3792,16 @@ var isFile = (path) => {
3678
3792
  }
3679
3793
  };
3680
3794
  var resolveExtends = (spec, fromDir) => {
3681
- if (spec.startsWith(".") || isAbsolute2(spec)) {
3682
- const candidates = spec.endsWith(".json") ? [resolve3(fromDir, spec)] : [
3683
- resolve3(fromDir, `${spec}.json`),
3684
- resolve3(fromDir, spec, "tsconfig.json"),
3685
- resolve3(fromDir, spec)
3795
+ if (spec.startsWith(".") || isAbsolute3(spec)) {
3796
+ const candidates = spec.endsWith(".json") ? [resolve4(fromDir, spec)] : [
3797
+ resolve4(fromDir, `${spec}.json`),
3798
+ resolve4(fromDir, spec, "tsconfig.json"),
3799
+ resolve4(fromDir, spec)
3686
3800
  ];
3687
3801
  return candidates.find(isFile) ?? null;
3688
3802
  }
3689
3803
  try {
3690
- const require_ = createRequire2(pathToFileURL(join6(fromDir, "_.js")).href);
3804
+ const require_ = createRequire3(pathToFileURL(join9(fromDir, "_.js")).href);
3691
3805
  for (const sub of [`${spec}/tsconfig.json`, spec]) {
3692
3806
  try {
3693
3807
  return require_.resolve(sub);
@@ -3697,11 +3811,11 @@ var resolveExtends = (spec, fromDir) => {
3697
3811
  return null;
3698
3812
  };
3699
3813
  var loadPaths = (file, seen) => {
3700
- if (seen.has(file) || !existsSync4(file)) {
3814
+ if (seen.has(file) || !existsSync7(file)) {
3701
3815
  return null;
3702
3816
  }
3703
3817
  seen.add(file);
3704
- const json = parseJsonc(readFileSync2(file, "utf-8"));
3818
+ const json = parseJsonc(readFileSync3(file, "utf-8"));
3705
3819
  if (!json) {
3706
3820
  return null;
3707
3821
  }
@@ -3709,7 +3823,7 @@ var loadPaths = (file, seen) => {
3709
3823
  if (options.paths && typeof options.paths === "object") {
3710
3824
  const baseUrl = typeof options.baseUrl === "string" ? options.baseUrl : ".";
3711
3825
  return {
3712
- baseDir: resolve3(dirname5(file), baseUrl),
3826
+ baseDir: resolve4(dirname5(file), baseUrl),
3713
3827
  paths: options.paths
3714
3828
  };
3715
3829
  }
@@ -3736,10 +3850,10 @@ var toAlias = (key, value, baseDir) => {
3736
3850
  if (find === "" || find === "*") {
3737
3851
  return null;
3738
3852
  }
3739
- return { find, replacement: resolve3(baseDir, target) };
3853
+ return { find, replacement: resolve4(baseDir, target) };
3740
3854
  };
3741
3855
  var resolveTsconfigAliases = (root) => {
3742
- const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join6(root, name)).find((file) => existsSync4(file));
3856
+ const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join9(root, name)).find((file) => existsSync7(file));
3743
3857
  if (!entry) {
3744
3858
  return {};
3745
3859
  }
@@ -3798,7 +3912,6 @@ var buildRssFeeds = (project) => {
3798
3912
  }
3799
3913
  return feeds;
3800
3914
  };
3801
- var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
3802
3915
  var renderItem = (item) => {
3803
3916
  const parts = [
3804
3917
  ` <title>${escapeXml(item.title)}</title>`,
@@ -3836,13 +3949,98 @@ ${items}
3836
3949
  `;
3837
3950
  };
3838
3951
 
3952
+ // src/openapi/references.ts
3953
+ var NON_SLUG = /[^a-z0-9]+/gu;
3954
+ var SLUG_EDGES = /^-+|-+$/gu;
3955
+ var ROUTE_EDGES = /^\/+|\/+$/gu;
3956
+ var TRAILING_SLASH = /\/+$/u;
3957
+ var slugify = (text) => text.toLowerCase().replace(NON_SLUG, "-").replace(SLUG_EDGES, "");
3958
+ var normalizeRoute = (route) => {
3959
+ const trimmed = route.trim();
3960
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
3961
+ const noTrailing = withSlash.replace(TRAILING_SLASH, "");
3962
+ return noTrailing === "" ? "/" : noTrailing;
3963
+ };
3964
+ var routeSlug = (route) => slugify(route.replace(ROUTE_EDGES, "")) || "reference";
3965
+ var sourcesOf = (block) => {
3966
+ const sources = [...block.sources];
3967
+ if (block.spec) {
3968
+ sources.unshift({ spec: block.spec });
3969
+ }
3970
+ return sources;
3971
+ };
3972
+ var referencesFor = (kind, block, defaultLabel, renderer, display) => {
3973
+ if (!block.enabled) {
3974
+ return [];
3975
+ }
3976
+ const sources = sourcesOf(block);
3977
+ const base = normalizeRoute(block.route);
3978
+ return sources.map((source, index) => {
3979
+ const label = source.label ?? (sources.length > 1 ? `${defaultLabel} ${index + 1}` : defaultLabel);
3980
+ let route;
3981
+ if (source.route) {
3982
+ route = normalizeRoute(source.route);
3983
+ } else if (sources.length === 1) {
3984
+ route = base;
3985
+ } else {
3986
+ const suffix = source.label ? slugify(source.label) : "";
3987
+ route = normalizeRoute(`${base}/${suffix || index + 1}`);
3988
+ }
3989
+ return {
3990
+ display,
3991
+ kind,
3992
+ label,
3993
+ renderer,
3994
+ route,
3995
+ slug: routeSlug(route),
3996
+ spec: source.spec,
3997
+ theme: block.theme
3998
+ };
3999
+ });
4000
+ };
4001
+ var NO_DISPLAY = { codeSamples: [], expandSchemas: false };
4002
+ var resolveReferences = (config) => [
4003
+ ...referencesFor("openapi", config.openapi, "API Reference", config.openapi.renderer, {
4004
+ codeSamples: config.openapi.codeSamples,
4005
+ expandSchemas: config.openapi.expandSchemas
4006
+ }),
4007
+ ...referencesFor("asyncapi", config.asyncapi, "Events", "scalar", NO_DISPLAY)
4008
+ ];
4009
+ var referenceTabs = (config) => resolveReferences(config).map((ref) => ({
4010
+ label: ref.label,
4011
+ path: ref.route
4012
+ }));
4013
+ var blumeReferences = (config) => {
4014
+ const seen = new Set;
4015
+ const result = [];
4016
+ for (const ref of resolveReferences(config)) {
4017
+ if (ref.kind !== "openapi" || ref.renderer !== "blume") {
4018
+ continue;
4019
+ }
4020
+ if (seen.has(ref.route)) {
4021
+ continue;
4022
+ }
4023
+ seen.add(ref.route);
4024
+ result.push(ref);
4025
+ }
4026
+ return result;
4027
+ };
4028
+ var hasScalarReferences = (config) => resolveReferences(config).some((ref) => ref.renderer === "scalar");
4029
+
3839
4030
  // src/openapi/scalar.ts
3840
- import { readFile as readFile4 } from "node:fs/promises";
3841
- import { isAbsolute as isAbsolute3, join as join8 } from "pathe";
4031
+ import { readFile as readFile5 } from "node:fs/promises";
4032
+ import { isAbsolute as isAbsolute4, join as join12 } from "pathe";
3842
4033
 
3843
4034
  // src/astro/templates.ts
3844
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
3845
- import { dirname as dirname6, join as join7 } from "pathe";
4035
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
4036
+ import { dirname as dirname6, join as join11 } from "pathe";
4037
+
4038
+ // src/core/assets.ts
4039
+ import { join as join10 } from "pathe";
4040
+ var resolveAssetMounts = (root, assets) => assets.map((entry) => {
4041
+ const rel = entry.replace(/^[./]+/u, "").replaceAll(/\.\.\/?/gu, "").replace(/\/+$/u, "");
4042
+ return { dir: join10(root, rel), url: `/${rel}` };
4043
+ });
3846
4044
 
3847
4045
  // src/theme/fonts.ts
3848
4046
  var FALLBACKS = {
@@ -4000,17 +4198,17 @@ var WORKSPACE_MARKERS = [
4000
4198
  "yarn.lock"
4001
4199
  ];
4002
4200
  var hasWorkspacesField = (pkgPath) => {
4003
- if (!existsSync5(pkgPath)) {
4201
+ if (!existsSync8(pkgPath)) {
4004
4202
  return false;
4005
4203
  }
4006
4204
  try {
4007
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
4205
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
4008
4206
  return pkg.workspaces !== undefined;
4009
4207
  } catch {
4010
4208
  return false;
4011
4209
  }
4012
4210
  };
4013
- var hasWorkspaceMarker = (dir) => hasWorkspacesField(join7(dir, "package.json")) || WORKSPACE_MARKERS.some((marker) => existsSync5(join7(dir, marker)));
4211
+ var hasWorkspaceMarker = (dir) => hasWorkspacesField(join11(dir, "package.json")) || WORKSPACE_MARKERS.some((marker) => existsSync8(join11(dir, marker)));
4014
4212
  var findWorkspaceRoot = (start) => {
4015
4213
  let dir = start;
4016
4214
  for (;; ) {
@@ -4045,7 +4243,7 @@ var runtimeDependencies = (options) => {
4045
4243
  if (needsSvelte) {
4046
4244
  deps.push("@astrojs/svelte");
4047
4245
  }
4048
- if (config.openapi.enabled || config.asyncapi.enabled) {
4246
+ if (hasScalarReferences(config)) {
4049
4247
  deps.push("@scalar/astro");
4050
4248
  }
4051
4249
  deps.push(...searchProviderMeta(config.search.provider).runtimeDeps);
@@ -4078,6 +4276,7 @@ var RENDER_EXTERNAL_DEPS = [
4078
4276
  ];
4079
4277
  var renderUserAliases = (aliases) => Object.entries(aliases ?? {}).toSorted(([a], [b]) => b.length - a.length).map(([find, replacement]) => `
4080
4278
  ${JSON.stringify(find)}: ${JSON.stringify(replacement)},`).join("");
4279
+ var astroOutDir = (context) => context.distDir ?? `${context.root}/dist`;
4081
4280
  var astroConfigTemplate = (options) => {
4082
4281
  const { context, config, needsReact, pages, dataPath, themePath } = options;
4083
4282
  const {
@@ -4085,6 +4284,7 @@ var astroConfigTemplate = (options) => {
4085
4284
  examplesPath,
4086
4285
  needsSvelte,
4087
4286
  needsVue,
4287
+ openapiPath,
4088
4288
  searchClientPath
4089
4289
  } = options;
4090
4290
  const { deployment } = config;
@@ -4144,7 +4344,8 @@ var astroConfigTemplate = (options) => {
4144
4344
  if (needsSvelte) {
4145
4345
  integrations.push("svelte()");
4146
4346
  }
4147
- integrations.push(`blumeIntegration(${JSON.stringify({ contentRoutes, pages })})`);
4347
+ const assets = resolveAssetMounts(context.root, config.content.assets);
4348
+ integrations.push(`blumeIntegration(${JSON.stringify({ assets, base: deployment.base, contentRoutes, pages })})`);
4148
4349
  return `// Generated by Blume. Do not edit; this file is recreated on each run.
4149
4350
  ${defineConfigImport}
4150
4351
  import mdx from "@astrojs/mdx";
@@ -4154,7 +4355,7 @@ ${twoslashImport}${reactImport}${vueImport}${svelteImport}${blumeImport}${adapte
4154
4355
  export default defineConfig({
4155
4356
  root: ${JSON.stringify(context.outDir)},
4156
4357
  srcDir: ${JSON.stringify(`${context.outDir}/src`)},
4157
- outDir: ${JSON.stringify(`${context.root}/dist`)},
4358
+ outDir: ${JSON.stringify(astroOutDir(context))},
4158
4359
  publicDir: ${JSON.stringify(`${context.root}/public`)},
4159
4360
  output: ${JSON.stringify(deployment.output)},${adapterOption}${siteOption}${baseOption}${redirectsOption}${i18nOption}${fontsOption}
4160
4361
  integrations: [${integrations.join(", ")}],
@@ -4187,6 +4388,7 @@ export default defineConfig({
4187
4388
  alias: {
4188
4389
  "blume:data": ${JSON.stringify(dataPath)},
4189
4390
  "blume:examples": ${JSON.stringify(examplesPath)},
4391
+ "blume:openapi": ${JSON.stringify(openapiPath)},
4190
4392
  "blume:search-client": ${JSON.stringify(searchClientPath)},
4191
4393
  "blume:theme": ${JSON.stringify(themePath)},${userAliasLines}
4192
4394
  },
@@ -4200,7 +4402,7 @@ export default defineConfig({
4200
4402
  });
4201
4403
  `;
4202
4404
  };
4203
- var stagedContentDir = (outDir) => join7(outDir, "content");
4405
+ var stagedContentDir = (outDir) => join11(outDir, "content");
4204
4406
  var contentConfigTemplate = (options) => {
4205
4407
  const { context, config } = options;
4206
4408
  const stagedBase = options.stagedBase ?? stagedContentDir(context.outDir);
@@ -4257,31 +4459,44 @@ const provider = createOpenAICompatible({
4257
4459
  modelExpr = `provider(${JSON.stringify(backend.model)})`;
4258
4460
  }
4259
4461
  if (grounded) {
4260
- imports.push('import { createAskContext } from "blume/ai/ask-context.ts";', 'import askData from "../generated/ask-data.json";');
4462
+ imports.push('import { createAskContext } from "blume/ai/ask-context.ts";', 'import askData from "../../generated/ask-data.json";');
4261
4463
  setup += `
4262
4464
  const ground = createAskContext(askData);
4263
4465
  `;
4264
4466
  }
4265
- const handler = grounded ? `export const POST: APIRoute = async ({ request }) => {
4266
- const { messages, page } = await request.json();
4267
- const system =
4268
- (await ground(messages, page)) ??
4269
- "You are a helpful documentation assistant. Answer using the project's documentation.";
4270
- const result = streamText({
4271
- model: ${modelExpr},
4272
- system,
4273
- messages,
4274
- });
4275
- return result.toTextStreamResponse();
4276
- };` : `export const POST: APIRoute = async ({ request }) => {
4277
- const { messages } = await request.json();
4278
- const result = streamText({
4279
- model: ${modelExpr},
4280
- system:
4281
- "You are a helpful documentation assistant. Answer using the project's documentation.",
4282
- messages,
4283
- });
4284
- return result.toTextStreamResponse();
4467
+ const validate = ` const body = await request.json().catch(() => null);
4468
+ const messages = body?.messages;
4469
+ if (
4470
+ !Array.isArray(messages) ||
4471
+ messages.length === 0 ||
4472
+ messages.length > 40 ||
4473
+ JSON.stringify(messages).length > 24_000
4474
+ ) {
4475
+ return new Response("Invalid request: send 1-40 messages.", {
4476
+ status: 400,
4477
+ });
4478
+ }`;
4479
+ const stream = grounded ? ` const system =
4480
+ (await ground(messages, body.page)) ??
4481
+ "You are a helpful documentation assistant. Answer using the project's documentation.";
4482
+ const result = streamText({
4483
+ model: ${modelExpr},
4484
+ system,
4485
+ messages,
4486
+ });` : ` const result = streamText({
4487
+ model: ${modelExpr},
4488
+ system:
4489
+ "You are a helpful documentation assistant. Answer using the project's documentation.",
4490
+ messages,
4491
+ });`;
4492
+ const handler = `export const POST: APIRoute = async ({ request }) => {
4493
+ ${validate}
4494
+ try {
4495
+ ${stream}
4496
+ return result.toTextStreamResponse();
4497
+ } catch {
4498
+ return new Response("Failed to generate a response.", { status: 500 });
4499
+ }
4285
4500
  };`;
4286
4501
  return `// Generated by Blume. Do not edit.
4287
4502
  ${imports.join(`
@@ -4593,7 +4808,10 @@ import FileTree from "blume/components/content/FileTree.astro";
4593
4808
  import Frame from "blume/components/content/Frame.astro";
4594
4809
  import GithubInfo from "blume/components/content/GithubInfo.astro";
4595
4810
  import Panel from "blume/components/content/Panel.astro";
4811
+ import ParamField from "blume/components/content/ParamField.astro";
4596
4812
  import Prompt from "blume/components/content/Prompt.astro";
4813
+ import RequestField from "blume/components/content/RequestField.astro";
4814
+ import ResponseField from "blume/components/content/ResponseField.astro";
4597
4815
  import Step from "blume/components/content/Step.astro";
4598
4816
  import Steps from "blume/components/content/Steps.astro";
4599
4817
  import Tab from "blume/components/content/Tab.astro";
@@ -4605,7 +4823,10 @@ import TreeFile from "blume/components/content/TreeFile.astro";
4605
4823
  import TreeFolder from "blume/components/content/TreeFolder.astro";
4606
4824
  import TypeTable from "blume/components/content/TypeTable.astro";
4607
4825
  import Visibility from "blume/components/content/Visibility.astro";
4826
+ import YouTube from "blume/components/content/YouTube.astro";
4608
4827
  import Icon from "blume/components/Icon.astro";
4828
+ import ApiOverview from "blume/components/openapi/ApiOverview.astro";
4829
+ import Operation from "blume/components/openapi/Operation.astro";
4609
4830
  ${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../generated/components.ts";
4610
4831
  import { islandComponents } from "../generated/islands.ts";
4611
4832
  import data from "../generated/data.json";
@@ -4622,6 +4843,7 @@ export const prerender = true;
4622
4843
  const components = {
4623
4844
  Accordion,
4624
4845
  AccordionItem,
4846
+ ApiOverview,
4625
4847
  AutoTypeTable,
4626
4848
  Badge,
4627
4849
  Callout,
@@ -4639,8 +4861,12 @@ const components = {
4639
4861
  Frame,
4640
4862
  GithubInfo,
4641
4863
  Icon,
4864
+ Operation,
4642
4865
  Panel,
4866
+ ParamField,
4643
4867
  Prompt,
4868
+ RequestField,
4869
+ ResponseField,
4644
4870
  Step,
4645
4871
  Steps,
4646
4872
  Tab,
@@ -4650,6 +4876,7 @@ const components = {
4650
4876
  Tree,
4651
4877
  TypeTable,
4652
4878
  Visibility,
4879
+ YouTube,
4653
4880
  ${mathEntry}...islandComponents,
4654
4881
  ...userMdx,
4655
4882
  };
@@ -5039,6 +5266,11 @@ declare module "blume:data" {
5039
5266
  export default data;
5040
5267
  }
5041
5268
 
5269
+ declare module "blume:openapi" {
5270
+ const specs: import("blume/openapi/model.ts").OpenApiData;
5271
+ export default specs;
5272
+ }
5273
+
5042
5274
  declare module "blume:search-client" {
5043
5275
  export const createSearch: () =>
5044
5276
  | import("blume/components/layout/search/types.ts").SearchFn
@@ -5061,8 +5293,9 @@ var runtimeTsconfigTemplate = () => `${JSON.stringify({
5061
5293
  `;
5062
5294
 
5063
5295
  // src/theme/palette.ts
5296
+ var FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
5064
5297
  var ACCENTS = {
5065
- blue: "oklch(0.62 0.16 250)",
5298
+ blue: FALLBACK_ACCENT,
5066
5299
  green: "oklch(0.6 0.16 150)",
5067
5300
  orange: "oklch(0.68 0.17 50)",
5068
5301
  pink: "oklch(0.65 0.2 350)",
@@ -5070,6 +5303,9 @@ var ACCENTS = {
5070
5303
  red: "oklch(0.58 0.22 25)",
5071
5304
  teal: "oklch(0.6 0.12 195)"
5072
5305
  };
5306
+ var CSS_COLOR = /^[\w\s#%.,()/+-]+$/u;
5307
+ var safeColor = (value, fallback) => CSS_COLOR.test(value.trim()) ? value.trim() : fallback;
5308
+ var safeColorOrNull = (value) => value && CSS_COLOR.test(value.trim()) ? value.trim() : null;
5073
5309
  var RADII = {
5074
5310
  lg: "0.75rem",
5075
5311
  md: "0.5rem",
@@ -5104,7 +5340,7 @@ var themeRootCss = (theme, options) => [
5104
5340
  ` --blume-accent: ${options.accent};`,
5105
5341
  ...cssToken("--blume-action", options.action),
5106
5342
  ...cssToken("--blume-action-foreground", options.action ? "oklch(1 0 0)" : null),
5107
- ...cssToken("--blume-background", theme.background),
5343
+ ...cssToken("--blume-background", safeColorOrNull(theme.background)),
5108
5344
  ...cssToken("--blume-background-image", theme.backgroundImage ? backgroundImageCss(theme.backgroundImage) : null),
5109
5345
  options.backgroundDecoration.trimEnd(),
5110
5346
  ` --blume-radius: ${options.radius};`
@@ -5113,7 +5349,7 @@ var themeRootCss = (theme, options) => [
5113
5349
  var themeDarkCss = (theme, accentDark) => {
5114
5350
  const tokens = [
5115
5351
  ...cssToken("--blume-accent", accentDark),
5116
- ...cssToken("--blume-background", theme.backgroundDark),
5352
+ ...cssToken("--blume-background", safeColorOrNull(theme.backgroundDark)),
5117
5353
  ...cssToken("--blume-background-image", theme.backgroundImageDark ? backgroundImageCss(theme.backgroundImageDark) : null)
5118
5354
  ];
5119
5355
  if (tokens.length === 0) {
@@ -5125,12 +5361,12 @@ ${tokens.join(`
5125
5361
  }
5126
5362
  `;
5127
5363
  };
5128
- var resolveAccent = (theme) => ACCENTS[theme.accent] ?? theme.accent;
5364
+ var resolveAccent = (theme) => ACCENTS[theme.accent] ?? safeColor(theme.accent, FALLBACK_ACCENT);
5129
5365
  var resolveRadius = (theme) => RADII[theme.radius];
5130
5366
  var buildThemeCss = (theme) => {
5131
- const accent = ACCENTS[theme.accent] ?? theme.accent;
5132
- const accentDark = theme.accentDark ? ACCENTS[theme.accentDark] ?? theme.accentDark : null;
5133
- const action = theme.action ? ACCENTS[theme.action] ?? theme.action : null;
5367
+ const accent = ACCENTS[theme.accent] ?? safeColor(theme.accent, FALLBACK_ACCENT);
5368
+ const accentDark = theme.accentDark ? ACCENTS[theme.accentDark] ?? safeColor(theme.accentDark, FALLBACK_ACCENT) : null;
5369
+ const action = theme.action ? ACCENTS[theme.action] ?? safeColor(theme.action, FALLBACK_ACCENT) : null;
5134
5370
  const backgroundDecoration = backgroundDecorationCss(theme.backgroundDecoration);
5135
5371
  const radius = RADII[theme.radius];
5136
5372
  const root = themeRootCss(theme, {
@@ -5149,57 +5385,11 @@ ${dark}`;
5149
5385
 
5150
5386
  // src/openapi/scalar.ts
5151
5387
  var URL_SPEC = /^https?:\/\//u;
5152
- var NON_SLUG = /[^a-z0-9]+/gu;
5153
- var SLUG_EDGES = /^-+|-+$/gu;
5154
- var ROUTE_EDGES = /^\/+|\/+$/gu;
5155
- var TRAILING_SLASH = /\/+$/u;
5156
- var slugify = (text) => text.toLowerCase().replace(NON_SLUG, "-").replace(SLUG_EDGES, "");
5157
- var normalizeRoute = (route) => {
5158
- const trimmed = route.trim();
5159
- const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
5160
- const noTrailing = withSlash.replace(TRAILING_SLASH, "");
5161
- return noTrailing === "" ? "/" : noTrailing;
5162
- };
5388
+ var ROUTE_EDGES2 = /^\/+|\/+$/gu;
5163
5389
  var referencePagePath = (route) => {
5164
- const segments = route.replace(ROUTE_EDGES, "");
5390
+ const segments = route.replace(ROUTE_EDGES2, "");
5165
5391
  return `${segments === "" ? "index" : segments}.astro`;
5166
5392
  };
5167
- var sourcesOf = (block) => {
5168
- const sources = [...block.sources];
5169
- if (block.spec) {
5170
- sources.unshift({ spec: block.spec });
5171
- }
5172
- return sources;
5173
- };
5174
- var referencesFor = (kind, block, defaultLabel) => {
5175
- if (!block.enabled) {
5176
- return [];
5177
- }
5178
- const sources = sourcesOf(block);
5179
- const base = normalizeRoute(block.route);
5180
- return sources.map((source, index) => {
5181
- const label = source.label ?? (sources.length > 1 ? `${defaultLabel} ${index + 1}` : defaultLabel);
5182
- let route;
5183
- if (source.route) {
5184
- route = normalizeRoute(source.route);
5185
- } else if (sources.length === 1) {
5186
- route = base;
5187
- } else {
5188
- const suffix = source.label ? slugify(source.label) : "";
5189
- route = normalizeRoute(`${base}/${suffix || index + 1}`);
5190
- }
5191
- return { kind, label, route, spec: source.spec, theme: block.theme };
5192
- });
5193
- };
5194
- var resolveReferences = (config) => [
5195
- ...referencesFor("openapi", config.openapi, "API Reference"),
5196
- ...referencesFor("asyncapi", config.asyncapi, "Events")
5197
- ];
5198
- var referenceTabs = (config) => resolveReferences(config).map((ref) => ({
5199
- label: ref.label,
5200
- path: ref.route
5201
- }));
5202
- var hasReferences = (config) => config.openapi.enabled || config.asyncapi.enabled;
5203
5393
  var darkModeConfig = (mode) => {
5204
5394
  if (mode === "dark") {
5205
5395
  return { darkMode: true };
@@ -5224,9 +5414,9 @@ var specConfiguration = async (spec, root) => {
5224
5414
  if (URL_SPEC.test(spec)) {
5225
5415
  return { config: { url: spec } };
5226
5416
  }
5227
- const absolute = isAbsolute3(spec) ? spec : join8(root, spec);
5417
+ const absolute = isAbsolute4(spec) ? spec : join12(root, spec);
5228
5418
  try {
5229
- return { config: { content: await readFile4(absolute, "utf-8") } };
5419
+ return { config: { content: await readFile5(absolute, "utf-8") } };
5230
5420
  } catch {
5231
5421
  return {
5232
5422
  config: { url: spec },
@@ -5240,6 +5430,9 @@ var buildReferenceFiles = async (options) => {
5240
5430
  const seen = new Set;
5241
5431
  const accepted = [];
5242
5432
  for (const ref of resolveReferences(config)) {
5433
+ if (ref.renderer !== "scalar") {
5434
+ continue;
5435
+ }
5243
5436
  if (seen.has(ref.route)) {
5244
5437
  warnings.push(`Two API reference sources resolve to ${ref.route}; keeping the first.`);
5245
5438
  continue;
@@ -5278,37 +5471,315 @@ var buildReferenceFiles = async (options) => {
5278
5471
  return { files, warnings };
5279
5472
  };
5280
5473
 
5281
- // src/theme/entry.ts
5282
- var tailwindEntryTemplate = (options) => `/* Generated by Blume. Do not edit. */
5283
- @import "tailwindcss";
5284
- @plugin "@tailwindcss/typography";
5285
-
5286
- /* Scan Blume's components and the user's project for utility classes. */
5287
- ${options.sources.map((source) => `@source "${source}";`).join(`
5288
- `)}
5289
-
5290
- /* Dark mode is driven by data-theme on the <html> element. */
5291
- @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
5292
-
5293
- :root {
5294
- --blume-background: oklch(1 0 0);
5295
- --blume-background-decoration: none;
5296
- --blume-background-decoration-repeat: no-repeat;
5297
- --blume-background-decoration-size: auto;
5298
- --blume-background-image: none;
5299
- --blume-background-image-repeat: no-repeat;
5300
- --blume-background-image-size: cover;
5301
- --blume-foreground: oklch(0.145 0 0);
5302
- --blume-muted: oklch(0.965 0 0);
5303
- --blume-muted-foreground: oklch(0.54 0 0);
5304
- --blume-border: oklch(0.88 0.006 260 / 0.72);
5305
- --blume-accent: oklch(0.145 0 0);
5306
- --blume-accent-foreground: oklch(1 0 0);
5307
- --blume-action: var(--blume-accent);
5308
- --blume-action-foreground: var(--blume-accent-foreground);
5309
- --blume-code-background: oklch(0.99 0 0);
5310
- /* Shiki notation transformers: line/word highlight, diff add/remove. */
5311
- --blume-code-highlight: oklch(0.55 0.16 255 / 0.1);
5474
+ // src/core/sources/cache.ts
5475
+ import { mkdir as mkdir2, readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
5476
+ import { join as join13 } from "pathe";
5477
+ var hashText = (text) => {
5478
+ let hash = 5381;
5479
+ for (let i = 0;i < text.length; i += 1) {
5480
+ hash = (hash * 33 + (text.codePointAt(i) ?? 0)) % 2147483647;
5481
+ }
5482
+ return hash.toString(36);
5483
+ };
5484
+ var entriesDigest = (entries) => hashText(entries.map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`).join("|"));
5485
+ var pollingWatch = (load2, intervalSeconds) => (onChange) => {
5486
+ let last = "";
5487
+ const tick = async () => {
5488
+ try {
5489
+ const { entries } = await load2();
5490
+ const next = entriesDigest(entries);
5491
+ if (last && next !== last) {
5492
+ onChange();
5493
+ }
5494
+ last = next;
5495
+ } catch {}
5496
+ };
5497
+ const timer = setInterval(() => {
5498
+ tick();
5499
+ }, intervalSeconds * 1000);
5500
+ return () => clearInterval(timer);
5501
+ };
5502
+ var snapshotCache = (cacheDir) => {
5503
+ const file = join13(cacheDir, "entries.json");
5504
+ return {
5505
+ read: async () => {
5506
+ try {
5507
+ return JSON.parse(await readFile6(file, "utf-8"));
5508
+ } catch {
5509
+ return [];
5510
+ }
5511
+ },
5512
+ write: async (entries) => {
5513
+ try {
5514
+ await mkdir2(cacheDir, { recursive: true });
5515
+ await writeFile3(file, `${JSON.stringify(entries)}
5516
+ `, "utf-8");
5517
+ } catch {}
5518
+ }
5519
+ };
5520
+ };
5521
+ var loadWithCache = async (name, cache, fetchEntries, refresh = true) => {
5522
+ if (!refresh) {
5523
+ const cached3 = await cache.read();
5524
+ if (cached3.length > 0) {
5525
+ return { diagnostics: [], entries: cached3 };
5526
+ }
5527
+ }
5528
+ try {
5529
+ const entries = await fetchEntries();
5530
+ await cache.write(entries);
5531
+ return { diagnostics: [], entries };
5532
+ } catch (error) {
5533
+ const fallback = await cache.read();
5534
+ if (fallback.length > 0) {
5535
+ const diagnostic = {
5536
+ code: "BLUME_SOURCE_OFFLINE",
5537
+ message: `Source "${name}" could not be fetched (${error.message}); served ${fallback.length} cached entries.`,
5538
+ severity: "warning"
5539
+ };
5540
+ return { diagnostics: [diagnostic], entries: fallback };
5541
+ }
5542
+ throw new BlumeError({
5543
+ code: "BLUME_SOURCE_FETCH_FAILED",
5544
+ message: `Source "${name}" failed to load and no cache is available: ${error.message}`,
5545
+ severity: "error"
5546
+ });
5547
+ }
5548
+ };
5549
+
5550
+ // src/openapi/model.ts
5551
+ var NON_SLUG2 = /[^a-z0-9]+/gu;
5552
+ var SLUG_EDGES2 = /^-+|-+$/gu;
5553
+ var slugify2 = (text) => text.toLowerCase().replace(NON_SLUG2, "-").replace(SLUG_EDGES2, "");
5554
+ var HTTP_METHODS = [
5555
+ "get",
5556
+ "put",
5557
+ "post",
5558
+ "delete",
5559
+ "options",
5560
+ "head",
5561
+ "patch",
5562
+ "trace"
5563
+ ];
5564
+ var UNTAGGED = "Operations";
5565
+ var operationKey = (method, path, operationId) => {
5566
+ const fromId = operationId ? slugify2(operationId) : "";
5567
+ return fromId || slugify2(`${method}-${path}`);
5568
+ };
5569
+ var isOperation = (value) => typeof value === "object" && value !== null;
5570
+ var extractOperations = (document, baseRoute) => {
5571
+ const operations = [];
5572
+ const tagOrder = [];
5573
+ const tagMeta = new Map((document.tags ?? []).map((tag) => [tag.name, tag.description ?? ""]));
5574
+ const seen = new Set;
5575
+ for (const [path, rawItem] of Object.entries(document.paths ?? {})) {
5576
+ const item = rawItem;
5577
+ if (!item || "$ref" in item) {
5578
+ continue;
5579
+ }
5580
+ for (const method of HTTP_METHODS) {
5581
+ const operation = item[method];
5582
+ if (!isOperation(operation)) {
5583
+ continue;
5584
+ }
5585
+ const tag = operation.tags?.[0] ?? UNTAGGED;
5586
+ const tagSlug = slugify2(tag) || "operations";
5587
+ if (!tagOrder.includes(tag)) {
5588
+ tagOrder.push(tag);
5589
+ }
5590
+ let key = operationKey(method, path, operation.operationId);
5591
+ while (seen.has(key)) {
5592
+ key = `${key}-${method}`;
5593
+ }
5594
+ seen.add(key);
5595
+ operations.push({
5596
+ deprecated: operation.deprecated ?? false,
5597
+ description: operation.description ?? "",
5598
+ key,
5599
+ method,
5600
+ operationId: operation.operationId,
5601
+ path,
5602
+ route: `${baseRoute}/${tagSlug}/${key}`,
5603
+ summary: operation.summary ?? "",
5604
+ tag,
5605
+ tagSlug
5606
+ });
5607
+ }
5608
+ }
5609
+ const tags = tagOrder.map((name) => ({
5610
+ description: tagMeta.get(name) ?? "",
5611
+ name,
5612
+ slug: slugify2(name) || "operations"
5613
+ }));
5614
+ return { operations, tags };
5615
+ };
5616
+
5617
+ // src/openapi/parse.ts
5618
+ import { readFile as readFile7 } from "node:fs/promises";
5619
+ import { normalize as normalize2, upgrade } from "@scalar/openapi-parser";
5620
+ import { isAbsolute as isAbsolute5, join as join14 } from "pathe";
5621
+ var URL_SPEC2 = /^https?:\/\//u;
5622
+ var readSpecText = async (spec, root) => {
5623
+ if (URL_SPEC2.test(spec)) {
5624
+ const response = await fetch(spec);
5625
+ if (!response.ok) {
5626
+ throw new Error(`${spec} -> ${response.status} ${response.statusText}`);
5627
+ }
5628
+ return await response.text();
5629
+ }
5630
+ const absolute = isAbsolute5(spec) ? spec : join14(root, spec);
5631
+ return await readFile7(absolute, "utf-8");
5632
+ };
5633
+ var parseSpec = async (spec, root) => {
5634
+ const text = await readSpecText(spec, root);
5635
+ const normalized = normalize2(text);
5636
+ const { specification } = upgrade(normalized);
5637
+ return { document: specification, warnings: [] };
5638
+ };
5639
+
5640
+ // src/openapi/render-mdx.ts
5641
+ var MDX_UNSAFE = /[<>{}]/gu;
5642
+ var ENTITIES = {
5643
+ "<": "&lt;",
5644
+ ">": "&gt;",
5645
+ "{": "&#123;",
5646
+ "}": "&#125;"
5647
+ };
5648
+ var mdxSafe = (text) => text.replace(MDX_UNSAFE, (char) => ENTITIES[char] ?? char);
5649
+ var withDescription = (description, component) => description.trim() ? `${mdxSafe(description.trim())}
5650
+
5651
+ ${component}` : component;
5652
+ var operationMdx = (spec, operation) => {
5653
+ const method = operation.method.toUpperCase();
5654
+ const title = operation.summary || `${method} ${operation.path}`;
5655
+ const description = operation.description.trim() === operation.summary.trim() ? "" : operation.description;
5656
+ return {
5657
+ body: withDescription(description, `<Operation source="${spec.slug}" id="${operation.key}" />`),
5658
+ data: {
5659
+ ...operation.deprecated ? { deprecated: true } : {},
5660
+ search: { tags: [operation.tag, method] },
5661
+ sidebar: { badge: method, label: operation.summary || operation.path },
5662
+ title,
5663
+ type: "openapi-operation"
5664
+ }
5665
+ };
5666
+ };
5667
+ var overviewMdx = (spec) => ({
5668
+ body: withDescription(spec.description, `<ApiOverview source="${spec.slug}" />`),
5669
+ data: {
5670
+ sidebar: { label: "Overview" },
5671
+ title: spec.title || spec.label
5672
+ }
5673
+ });
5674
+
5675
+ // src/openapi/source.ts
5676
+ var isOpenApiSource = (source) => source.kind === "openapi-source";
5677
+ var routeToRef = (route) => route.replace(/^\/+/u, "");
5678
+ var toEntry = (rendered, ref) => {
5679
+ const raw = frontmatter_default.stringify(`${rendered.body}
5680
+ `, rendered.data);
5681
+ return {
5682
+ body: { format: "mdx", text: rendered.body },
5683
+ data: rendered.data,
5684
+ hash: hashText(raw),
5685
+ raw,
5686
+ ref
5687
+ };
5688
+ };
5689
+ var specEntries = (spec, operations) => {
5690
+ const entries = operations.map((operation) => toEntry(operationMdx(spec, operation), `${routeToRef(operation.route)}.mdx`));
5691
+ entries.push(toEntry(overviewMdx(spec), `${routeToRef(spec.route)}/index.mdx`));
5692
+ return entries;
5693
+ };
5694
+ var openApiSource = (references, ctx) => {
5695
+ let parsed = {};
5696
+ const loadReference = async (reference) => {
5697
+ try {
5698
+ const { document } = await parseSpec(reference.spec, ctx.projectRoot);
5699
+ const { operations, tags } = extractOperations(document, reference.route);
5700
+ const info = document.info ?? { title: reference.label, version: "" };
5701
+ const spec = {
5702
+ codeSamples: reference.display.codeSamples,
5703
+ description: info.description ?? "",
5704
+ document,
5705
+ expandSchemas: reference.display.expandSchemas,
5706
+ label: reference.label,
5707
+ operations: Object.fromEntries(operations.map((operation) => [operation.key, operation])),
5708
+ route: reference.route,
5709
+ slug: reference.slug,
5710
+ tags,
5711
+ title: info.title ?? reference.label,
5712
+ version: info.version ?? ""
5713
+ };
5714
+ return {
5715
+ entries: specEntries(spec, operations),
5716
+ slug: reference.slug,
5717
+ spec
5718
+ };
5719
+ } catch (error) {
5720
+ return {
5721
+ code: "BLUME_OPENAPI_UNAVAILABLE",
5722
+ message: `Could not load OpenAPI spec "${reference.spec}" for ${reference.route} (${error.message}); its reference pages were skipped.`,
5723
+ severity: "warning"
5724
+ };
5725
+ }
5726
+ };
5727
+ const load2 = async () => {
5728
+ const results = await Promise.all(references.map(loadReference));
5729
+ const entries = [];
5730
+ const diagnostics = [];
5731
+ const data = {};
5732
+ for (const result of results) {
5733
+ if ("severity" in result) {
5734
+ diagnostics.push(result);
5735
+ continue;
5736
+ }
5737
+ data[result.slug] = result.spec;
5738
+ entries.push(...result.entries);
5739
+ }
5740
+ parsed = data;
5741
+ return { diagnostics, entries };
5742
+ };
5743
+ return {
5744
+ kind: "openapi-source",
5745
+ load: load2,
5746
+ name: "openapi",
5747
+ openApiData: () => parsed,
5748
+ staged: true
5749
+ };
5750
+ };
5751
+
5752
+ // src/theme/entry.ts
5753
+ var tailwindEntryTemplate = (options) => `/* Generated by Blume. Do not edit. */
5754
+ @import "tailwindcss";
5755
+ @plugin "@tailwindcss/typography";
5756
+
5757
+ /* Scan Blume's components and the user's project for utility classes. */
5758
+ ${options.sources.map((source) => `@source "${source}";`).join(`
5759
+ `)}
5760
+
5761
+ /* Dark mode is driven by data-theme on the <html> element. */
5762
+ @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
5763
+
5764
+ :root {
5765
+ --blume-background: oklch(1 0 0);
5766
+ --blume-background-decoration: none;
5767
+ --blume-background-decoration-repeat: no-repeat;
5768
+ --blume-background-decoration-size: auto;
5769
+ --blume-background-image: none;
5770
+ --blume-background-image-repeat: no-repeat;
5771
+ --blume-background-image-size: cover;
5772
+ --blume-foreground: oklch(0.145 0 0);
5773
+ --blume-muted: oklch(0.965 0 0);
5774
+ --blume-muted-foreground: oklch(0.54 0 0);
5775
+ --blume-border: oklch(0.88 0.006 260 / 0.72);
5776
+ --blume-accent: oklch(0.145 0 0);
5777
+ --blume-accent-foreground: oklch(1 0 0);
5778
+ --blume-action: var(--blume-accent);
5779
+ --blume-action-foreground: var(--blume-accent-foreground);
5780
+ --blume-code-background: oklch(0.99 0 0);
5781
+ /* Shiki notation transformers: line/word highlight, diff add/remove. */
5782
+ --blume-code-highlight: oklch(0.55 0.16 255 / 0.1);
5312
5783
  --blume-code-highlight-border: oklch(0.55 0.16 255 / 0.55);
5313
5784
  --blume-code-add: oklch(0.72 0.16 150 / 0.16);
5314
5785
  --blume-code-add-border: oklch(0.52 0.15 150 / 0.7);
@@ -5897,9 +6368,9 @@ ${options.userTheme}
5897
6368
  `;
5898
6369
 
5899
6370
  // src/theme/twoslash.ts
5900
- import { readFileSync as readFileSync4 } from "node:fs";
5901
- import { createRequire as createRequire3 } from "node:module";
5902
- var require2 = createRequire3(import.meta.url);
6371
+ import { readFileSync as readFileSync5 } from "node:fs";
6372
+ import { createRequire as createRequire4 } from "node:module";
6373
+ var require2 = createRequire4(import.meta.url);
5903
6374
  var OVERRIDES = `
5904
6375
  /* Twoslash: theme the rich renderer with Blume tokens. */
5905
6376
  :root {
@@ -5959,7 +6430,7 @@ var OVERRIDES = `
5959
6430
  `;
5960
6431
  var twoslashCss = () => {
5961
6432
  const file = require2.resolve("@shikijs/twoslash/style-rich.css");
5962
- return `${readFileSync4(file, "utf-8")}
6433
+ return `${readFileSync5(file, "utf-8")}
5963
6434
  ${OVERRIDES}`;
5964
6435
  };
5965
6436
 
@@ -6061,13 +6532,13 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
6061
6532
  };
6062
6533
 
6063
6534
  // src/astro/examples.ts
6064
- import { readFile as readFile6 } from "node:fs/promises";
6065
- import { join as join10, relative as relative4 } from "pathe";
6535
+ import { readFile as readFile9 } from "node:fs/promises";
6536
+ import { join as join16, relative as relative4 } from "pathe";
6066
6537
  import { glob as glob2 } from "tinyglobby";
6067
6538
 
6068
6539
  // src/astro/islands.ts
6069
- import { readFile as readFile5 } from "node:fs/promises";
6070
- import { basename, join as join9 } from "pathe";
6540
+ import { readFile as readFile8 } from "node:fs/promises";
6541
+ import { basename, join as join15 } from "pathe";
6071
6542
  import { glob } from "tinyglobby";
6072
6543
  var DEFAULT_CLIENT = "visible";
6073
6544
  var VALID_MODES = new Set([
@@ -6096,14 +6567,14 @@ var readClientMode = (source, file, warnings) => {
6096
6567
  return mode;
6097
6568
  };
6098
6569
  var discoverIslands = async (root) => {
6099
- const dir = join9(root, "islands");
6570
+ const dir = join15(root, "islands");
6100
6571
  const matches = await glob(["**/*.{jsx,svelte,tsx,vue}"], {
6101
6572
  absolute: true,
6102
6573
  cwd: dir,
6103
6574
  onlyFiles: true
6104
6575
  });
6105
6576
  const files = matches.toSorted();
6106
- const sources = await Promise.all(files.map((file) => readFile5(file, "utf-8")));
6577
+ const sources = await Promise.all(files.map((file) => readFile8(file, "utf-8")));
6107
6578
  const islands = [];
6108
6579
  const warnings = [];
6109
6580
  const seen = new Map;
@@ -6115,8 +6586,8 @@ var discoverIslands = async (root) => {
6115
6586
  continue;
6116
6587
  }
6117
6588
  const name = base.replace(ISLAND_FILE, "");
6118
- if (!/^[A-Z]/u.test(name)) {
6119
- warnings.push(`Island "${file}" must have a PascalCase filename to be used in MDX (e.g. Counter.tsx → <Counter />); skipping it.`);
6589
+ if (!/^[A-Z][A-Za-z0-9_]*$/u.test(name)) {
6590
+ warnings.push(`Island "${file}" must have a PascalCase identifier filename to be used in MDX (letters, digits, and underscores only, e.g. Counter.tsx → <Counter />); skipping it.`);
6120
6591
  continue;
6121
6592
  }
6122
6593
  const existing = seen.get(name);
@@ -6159,14 +6630,14 @@ var splitGlobBase = (pattern) => {
6159
6630
  };
6160
6631
  var discoverExamples = async (root, pattern = "examples") => {
6161
6632
  const { base, rest } = GLOB_MAGIC.test(pattern) ? splitGlobBase(pattern) : { base: pattern, rest: DEFAULT_EXAMPLE_GLOB };
6162
- const dir = join10(root, base);
6633
+ const dir = join16(root, base);
6163
6634
  const matches = await glob2([rest], {
6164
6635
  absolute: true,
6165
6636
  cwd: dir,
6166
6637
  onlyFiles: true
6167
6638
  });
6168
6639
  const files = matches.toSorted();
6169
- const sources = await Promise.all(files.map((file) => readFile6(file, "utf-8")));
6640
+ const sources = await Promise.all(files.map((file) => readFile9(file, "utf-8")));
6170
6641
  const examples = [];
6171
6642
  const warnings = [];
6172
6643
  const seen = new Map;
@@ -6209,7 +6680,10 @@ var discoverPages = async (pagesRoot) => {
6209
6680
  return files.map((file) => {
6210
6681
  const rel = relative5(pagesRoot, file);
6211
6682
  const withoutExt = rel.slice(0, rel.length - extname2(rel).length);
6212
- const parts = withoutExt.split("/").filter((part) => part !== "index");
6683
+ const parts = withoutExt.split("/");
6684
+ if (parts.at(-1) === "index") {
6685
+ parts.pop();
6686
+ }
6213
6687
  const pattern = parts.length === 0 ? "/" : `/${parts.join("/")}`;
6214
6688
  return { entrypoint: file, pattern };
6215
6689
  });
@@ -6237,10 +6711,10 @@ var customOgRoutes = (pages, siteTitle) => {
6237
6711
  };
6238
6712
 
6239
6713
  // src/astro/generate.ts
6240
- var BLUME_SRC = join11(packageRoot(), "src");
6714
+ var BLUME_SRC = join17(packageRoot(), "src");
6241
6715
  var canResolveFrom = (fromDir, spec) => {
6242
6716
  try {
6243
- createRequire4(pathToFileURL2(join11(fromDir, "_.js")).href).resolve(spec);
6717
+ createRequire5(pathToFileURL2(join17(fromDir, "_.js")).href).resolve(spec);
6244
6718
  return true;
6245
6719
  } catch {
6246
6720
  return false;
@@ -6248,15 +6722,15 @@ var canResolveFrom = (fromDir, spec) => {
6248
6722
  };
6249
6723
  var resolvedAstroPath = (fromDir) => {
6250
6724
  try {
6251
- const pkg = createRequire4(pathToFileURL2(join11(fromDir, "_.js")).href).resolve("astro/package.json");
6725
+ const pkg = createRequire5(pathToFileURL2(join17(fromDir, "_.js")).href).resolve("astro/package.json");
6252
6726
  return realpathSync(pkg);
6253
6727
  } catch {
6254
6728
  return null;
6255
6729
  }
6256
6730
  };
6257
6731
  var blumeDepsDir = (pkgDir = packageRoot()) => {
6258
- const candidates = [join11(pkgDir, "node_modules"), dirname7(pkgDir)];
6259
- return candidates.find((dir) => existsSync6(join11(dir, "astro"))) ?? null;
6732
+ const candidates = [join17(pkgDir, "node_modules"), dirname7(pkgDir)];
6733
+ return candidates.find((dir) => existsSync9(join17(dir, "astro"))) ?? null;
6260
6734
  };
6261
6735
  var linkDepsJunction = async (link, depsDir) => {
6262
6736
  const existing = await lstat(link).catch(() => null);
@@ -6266,7 +6740,7 @@ var linkDepsJunction = async (link, depsDir) => {
6266
6740
  }
6267
6741
  await rm(link, { force: true });
6268
6742
  }
6269
- await mkdir2(dirname7(link), { recursive: true });
6743
+ await mkdir3(dirname7(link), { recursive: true });
6270
6744
  await symlink(depsDir, link, "junction");
6271
6745
  };
6272
6746
  var readPkgVersion = (pkgJsonPath) => {
@@ -6274,7 +6748,7 @@ var readPkgVersion = (pkgJsonPath) => {
6274
6748
  return null;
6275
6749
  }
6276
6750
  try {
6277
- return JSON.parse(readFileSync5(pkgJsonPath, "utf-8")).version ?? null;
6751
+ return JSON.parse(readFileSync6(pkgJsonPath, "utf-8")).version ?? null;
6278
6752
  } catch {
6279
6753
  return null;
6280
6754
  }
@@ -6296,8 +6770,8 @@ var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
6296
6770
  if (blumeAstro && outDirAstro === blumeAstro) {
6297
6771
  return null;
6298
6772
  }
6299
- if (existsSync6(join11(depsDir, "@astrojs", "mdx"))) {
6300
- await linkDepsJunction(join11(outDir, "node_modules"), depsDir);
6773
+ if (existsSync9(join17(depsDir, "@astrojs", "mdx"))) {
6774
+ await linkDepsJunction(join17(outDir, "node_modules"), depsDir);
6301
6775
  return null;
6302
6776
  }
6303
6777
  return astroConflictWarning(blumeAstro, outDirAstro);
@@ -6321,7 +6795,7 @@ var readOptional = async (path) => {
6321
6795
  return "";
6322
6796
  }
6323
6797
  try {
6324
- return await readFile7(path, "utf-8");
6798
+ return await readFile10(path, "utf-8");
6325
6799
  } catch {
6326
6800
  return "";
6327
6801
  }
@@ -6337,16 +6811,16 @@ var detectNeedsReact = async (root) => {
6337
6811
  var writeIfChanged = async (path, content) => {
6338
6812
  let existing = null;
6339
6813
  try {
6340
- existing = await readFile7(path, "utf-8");
6814
+ existing = await readFile10(path, "utf-8");
6341
6815
  } catch {
6342
6816
  existing = null;
6343
6817
  }
6344
6818
  if (existing === content) {
6345
6819
  return false;
6346
6820
  }
6347
- await mkdir2(dirname7(path), { recursive: true });
6821
+ await mkdir3(dirname7(path), { recursive: true });
6348
6822
  const tmp = `${path}.${process.pid}.tmp`;
6349
- await writeFile2(tmp, content, "utf-8");
6823
+ await writeFile4(tmp, content, "utf-8");
6350
6824
  try {
6351
6825
  await rename(tmp, path);
6352
6826
  } catch (error) {
@@ -6361,7 +6835,7 @@ var pruneOrphans = async (srcDir, written) => {
6361
6835
  cwd: srcDir,
6362
6836
  onlyFiles: true
6363
6837
  });
6364
- await Promise.all(existing.map((path) => normalize(path)).filter((path) => !written.has(path)).map((path) => rm(path, { force: true })));
6838
+ await Promise.all(existing.map((path) => normalize3(path)).filter((path) => !written.has(path)).map((path) => rm(path, { force: true })));
6365
6839
  };
6366
6840
  var collectStaged = (project) => {
6367
6841
  const staged = new Map;
@@ -6376,11 +6850,11 @@ var writeStagedContent = async (out, staged) => {
6376
6850
  const contentDir = stagedContentDir(out);
6377
6851
  const written = new Set;
6378
6852
  await Promise.all([...staged].map(async ([entryId, text]) => {
6379
- const path = join11(contentDir, entryId);
6380
- written.add(normalize(path));
6853
+ const path = join17(contentDir, entryId);
6854
+ written.add(normalize3(path));
6381
6855
  await writeIfChanged(path, text);
6382
6856
  }));
6383
- if (existsSync6(contentDir)) {
6857
+ if (existsSync9(contentDir)) {
6384
6858
  await pruneOrphans(contentDir, written);
6385
6859
  }
6386
6860
  };
@@ -6397,11 +6871,11 @@ var resolveLogo = (project) => {
6397
6871
  if (light && light === dark && light.toLowerCase().endsWith(".svg")) {
6398
6872
  const rel = light.replace(/^\//u, "");
6399
6873
  const file = [
6400
- join11(project.context.root, "public", rel),
6401
- join11(project.context.root, rel)
6402
- ].find((path) => existsSync6(path));
6874
+ join17(project.context.root, "public", rel),
6875
+ join17(project.context.root, rel)
6876
+ ].find((path) => existsSync9(path));
6403
6877
  if (file) {
6404
- return { alt, href, svg: readFileSync5(file, "utf-8") };
6878
+ return { alt, href, svg: readFileSync6(file, "utf-8") };
6405
6879
  }
6406
6880
  }
6407
6881
  return { alt, dark, href, light };
@@ -6425,9 +6899,9 @@ var faviconType = (name) => {
6425
6899
  const ext = name.split(".").pop()?.toLowerCase();
6426
6900
  return ext ? FAVICON_TYPES[ext] : undefined;
6427
6901
  };
6428
- var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync5(file).toString("base64")}`;
6902
+ var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync6(file).toString("base64")}`;
6429
6903
  var defaultFavicon = () => ({
6430
- href: inlineDataUri(join11(BLUME_SRC, "assets", "icon.png"), "image/png"),
6904
+ href: inlineDataUri(join17(BLUME_SRC, "assets", "icon.png"), "image/png"),
6431
6905
  type: "image/png"
6432
6906
  });
6433
6907
  var APPLE_ICON_CANDIDATES = [
@@ -6439,13 +6913,13 @@ var APPLE_ICON_CANDIDATES = [
6439
6913
  var resolveIconFile = (project, candidates) => {
6440
6914
  const { root } = project.context;
6441
6915
  for (const name of candidates) {
6442
- if (existsSync6(join11(root, "public", name))) {
6916
+ if (existsSync9(join17(root, "public", name))) {
6443
6917
  return { href: `/${name}`, type: faviconType(name) };
6444
6918
  }
6445
6919
  }
6446
6920
  for (const name of candidates) {
6447
- const file = join11(root, name);
6448
- if (existsSync6(file)) {
6921
+ const file = join17(root, name);
6922
+ if (existsSync9(file)) {
6449
6923
  const type = faviconType(name);
6450
6924
  return { href: inlineDataUri(file, type ?? "image/x-icon"), type };
6451
6925
  }
@@ -6527,6 +7001,7 @@ var buildRuntimeData = (project) => {
6527
7001
  label
6528
7002
  }))
6529
7003
  } : null,
7004
+ icons: config.icons,
6530
7005
  imageZoom: config.markdown.imageZoom,
6531
7006
  logo: resolveLogo(project),
6532
7007
  mcp: config.mcp.enabled ? { name: config.mcp.name ?? config.title, route: config.mcp.route } : null,
@@ -6573,7 +7048,7 @@ var buildRuntimeData = (project) => {
6573
7048
  var planMcp = (project, srcDir) => {
6574
7049
  const { config } = project;
6575
7050
  const { route } = config.mcp;
6576
- const dir = join11(srcDir, "blume-mcp");
7051
+ const dir = join17(srcDir, "blume-mcp");
6577
7052
  const base = {
6578
7053
  dir,
6579
7054
  discoveryPages: [],
@@ -6597,11 +7072,11 @@ var planMcp = (project, srcDir) => {
6597
7072
  ...base,
6598
7073
  discoveryPages: [
6599
7074
  {
6600
- entrypoint: join11(dir, "discovery.ts"),
7075
+ entrypoint: join17(dir, "discovery.ts"),
6601
7076
  pattern: "/.well-known/mcp.json"
6602
7077
  },
6603
7078
  {
6604
- entrypoint: join11(dir, "server-card.ts"),
7079
+ entrypoint: join17(dir, "server-card.ts"),
6605
7080
  pattern: "/.well-known/mcp/server-card.json"
6606
7081
  }
6607
7082
  ],
@@ -6620,11 +7095,11 @@ var writeMcpFiles = async (project, plan, write) => {
6620
7095
  version: data.version
6621
7096
  };
6622
7097
  await Promise.all([
6623
- write(join11(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
7098
+ write(join17(plan.srcDir, "generated", "mcp-data.json"), `${JSON.stringify(data)}
6624
7099
  `),
6625
- write(join11(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
6626
- write(join11(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
6627
- write(join11(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
7100
+ write(join17(plan.srcDir, "pages", mcpPageFile(plan.route)), mcpEndpointTemplate(plan.route)),
7101
+ write(join17(plan.dir, "discovery.ts"), staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput))),
7102
+ write(join17(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
6628
7103
  ]);
6629
7104
  };
6630
7105
  var writeAskFiles = async (project, srcDir, write) => {
@@ -6634,16 +7109,16 @@ var writeAskFiles = async (project, srcDir, write) => {
6634
7109
  }
6635
7110
  const grounded = ask.provider !== "inkeep";
6636
7111
  if (grounded) {
6637
- await write(join11(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
7112
+ await write(join17(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
6638
7113
  `);
6639
7114
  }
6640
- await write(join11(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded));
7115
+ await write(join17(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded));
6641
7116
  };
6642
7117
  var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
6643
7118
  if (routeIsTaken(pages, contentPages, "/404")) {
6644
7119
  return;
6645
7120
  }
6646
- await write(join11(srcDir, "pages", "404.astro"), notFoundPageTemplate());
7121
+ await write(join17(srcDir, "pages", "404.astro"), notFoundPageTemplate());
6647
7122
  };
6648
7123
  var shouldGenerateChangelog = (project) => {
6649
7124
  const hasChangelog = project.graph.pages.some((page) => page.contentType === "changelog" && !(page.meta.draft || page.meta.sidebar.hidden));
@@ -6652,7 +7127,7 @@ var shouldGenerateChangelog = (project) => {
6652
7127
  return (hasChangelog || hasChangelogSource) && !changelogRouteTaken;
6653
7128
  };
6654
7129
  var buildComponentSlots = async (componentsFile) => {
6655
- const analysis = componentsFile ? analyzeComponentOverrides(await readFile7(componentsFile, "utf-8"), componentsFile) : null;
7130
+ const analysis = componentsFile ? analyzeComponentOverrides(await readFile10(componentsFile, "utf-8"), componentsFile) : null;
6656
7131
  return {
6657
7132
  plan: planComponentSlots(componentsFile, analysis),
6658
7133
  tags: analysis ? [...analysis.mdx, ...analysis.islands].map((entry) => entry.key) : [],
@@ -6662,14 +7137,15 @@ var buildComponentSlots = async (componentsFile) => {
6662
7137
  var generateRuntime = async (project) => {
6663
7138
  const { context, config } = project;
6664
7139
  const out = context.outDir;
6665
- const srcDir = join11(out, "src");
6666
- const dataPath = join11(srcDir, "generated", "data.json");
6667
- const themePath = join11(srcDir, "generated", "app.css");
6668
- const searchClientPath = join11(srcDir, "generated", "search-client.ts");
6669
- const examplesPath = join11(srcDir, "generated", "examples.ts");
7140
+ const srcDir = join17(out, "src");
7141
+ const dataPath = join17(srcDir, "generated", "data.json");
7142
+ const themePath = join17(srcDir, "generated", "app.css");
7143
+ const searchClientPath = join17(srcDir, "generated", "search-client.ts");
7144
+ const examplesPath = join17(srcDir, "generated", "examples.ts");
7145
+ const openapiPath = join17(srcDir, "generated", "openapi.json");
6670
7146
  const written = new Set;
6671
7147
  const write = (path, content) => {
6672
- written.add(normalize(path));
7148
+ written.add(normalize3(path));
6673
7149
  return writeIfChanged(path, content);
6674
7150
  };
6675
7151
  const depsLinkWarning = await ensureDepsLink(out);
@@ -6702,7 +7178,7 @@ var generateRuntime = async (project) => {
6702
7178
  const staged = collectStaged(project);
6703
7179
  const hasStaged = staged.size > 0;
6704
7180
  const structural = await Promise.all([
6705
- write(join11(out, "astro.config.mjs"), astroConfigTemplate({
7181
+ write(join17(out, "astro.config.mjs"), astroConfigTemplate({
6706
7182
  aliases: resolveTsconfigAliases(context.root),
6707
7183
  config,
6708
7184
  contentRoutes: project.manifest.routes.map((route) => route.path),
@@ -6712,24 +7188,25 @@ var generateRuntime = async (project) => {
6712
7188
  needsReact,
6713
7189
  needsSvelte,
6714
7190
  needsVue,
7191
+ openapiPath,
6715
7192
  pages,
6716
7193
  searchClientPath,
6717
7194
  themePath
6718
7195
  })),
6719
- write(join11(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
6720
- write(join11(out, "tsconfig.json"), runtimeTsconfigTemplate()),
6721
- write(join11(srcDir, "env.d.ts"), envTemplate()),
6722
- write(join11(srcDir, "content.config.ts"), contentConfigTemplate({ config, context, staged: hasStaged })),
6723
- write(join11(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
7196
+ write(join17(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
7197
+ write(join17(out, "tsconfig.json"), runtimeTsconfigTemplate()),
7198
+ write(join17(srcDir, "env.d.ts"), envTemplate()),
7199
+ write(join17(srcDir, "content.config.ts"), contentConfigTemplate({ config, context, staged: hasStaged })),
7200
+ write(join17(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
6724
7201
  askEnabled,
6725
7202
  exportEpub,
6726
7203
  exportPdf,
6727
7204
  mathEnabled: config.markdown.math,
6728
7205
  needsReact
6729
7206
  })),
6730
- write(join11(srcDir, "generated", "components.ts"), slotPlan.module),
6731
- write(join11(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
6732
- write(join11(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples)),
7207
+ write(join17(srcDir, "generated", "components.ts"), slotPlan.module),
7208
+ write(join17(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
7209
+ write(join17(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples)),
6733
7210
  write(themePath, tailwindEntryTemplate({
6734
7211
  configTokens: `${buildThemeCss(config.theme)}${buildFontsCss(config.theme.fonts)}`,
6735
7212
  sources: [
@@ -6740,16 +7217,16 @@ var generateRuntime = async (project) => {
6740
7217
  userTheme
6741
7218
  }))
6742
7219
  ]);
6743
- await Promise.all(islandDiscovery.islands.map((island) => write(join11(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island))));
6744
- await Promise.all(slotPlan.wrappers.map((wrapper) => write(join11(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content)));
6745
- await Promise.all(exampleDiscovery.examples.map((example) => write(join11(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example))));
7220
+ await Promise.all(islandDiscovery.islands.map((island) => write(join17(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island))));
7221
+ await Promise.all(slotPlan.wrappers.map((wrapper) => write(join17(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content)));
7222
+ await Promise.all(exampleDiscovery.examples.map((example) => write(join17(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example))));
6746
7223
  await writeAskFiles(project, srcDir, write);
6747
7224
  await writeMcpFiles(project, mcp, write);
6748
7225
  if (config.seo.og.enabled) {
6749
- await write(join11(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes));
7226
+ await write(join17(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes));
6750
7227
  }
6751
7228
  if (shouldGenerateChangelog(project)) {
6752
- await write(join11(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
7229
+ await write(join17(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
6753
7230
  askEnabled,
6754
7231
  exportEpub,
6755
7232
  exportPdf,
@@ -6761,27 +7238,27 @@ var generateRuntime = async (project) => {
6761
7238
  await write(searchClientPath, searchClientTemplate(config));
6762
7239
  if (servesStaticIndex(config.search.provider)) {
6763
7240
  const documents = await buildSearchDocuments(project);
6764
- await write(join11(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
7241
+ await write(join17(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
6765
7242
  `);
6766
- await write(join11(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
7243
+ await write(join17(srcDir, "pages", "blume-search.json.ts"), searchEndpointTemplate());
6767
7244
  }
6768
7245
  if (config.search.provider === "mixedbread") {
6769
- await write(join11(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
7246
+ await write(join17(srcDir, "pages", "api", "search.ts"), mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""));
6770
7247
  }
6771
7248
  const rawMarkdown = await buildRawMarkdown(project);
6772
7249
  await Promise.all([
6773
- write(join11(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
7250
+ write(join17(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
6774
7251
  `),
6775
- write(join11(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate()),
6776
- write(join11(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate())
7252
+ write(join17(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate()),
7253
+ write(join17(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate())
6777
7254
  ]);
6778
7255
  const feeds = buildRssFeeds(project);
6779
7256
  if (feeds.length > 0) {
6780
7257
  const feedXml = Object.fromEntries(feeds.map((feed) => [feed.type, renderRssFeed(feed)]));
6781
7258
  await Promise.all([
6782
- write(join11(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
7259
+ write(join17(srcDir, "generated", "rss.json"), `${JSON.stringify(feedXml)}
6783
7260
  `),
6784
- write(join11(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
7261
+ write(join17(srcDir, "pages", "[section]", "rss.xml.ts"), rssEndpointTemplate())
6785
7262
  ]);
6786
7263
  }
6787
7264
  const warnings = [
@@ -6811,17 +7288,20 @@ var generateRuntime = async (project) => {
6811
7288
  }
6812
7289
  }
6813
7290
  warnings.push(...islandFrameworkWarnings(frameworks, context.root));
6814
- if (hasReferences(config)) {
7291
+ if (hasScalarReferences(config)) {
6815
7292
  const references = await buildReferenceFiles({
6816
7293
  config,
6817
7294
  contentRoutes: new Set(project.graph.pages.map((page) => page.route)),
6818
7295
  root: context.root
6819
7296
  });
6820
7297
  warnings.push(...references.warnings);
6821
- await Promise.all(references.files.map((file) => write(join11(srcDir, "pages", file.pagePath), file.content)));
7298
+ await Promise.all(references.files.map((file) => write(join17(srcDir, "pages", file.pagePath), file.content)));
6822
7299
  }
6823
- await write(join11(srcDir, "generated", "data.json"), buildRuntimeData(project));
6824
- await write(join11(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
7300
+ await write(join17(srcDir, "generated", "data.json"), buildRuntimeData(project));
7301
+ const openApiSource2 = project.sources.find(isOpenApiSource);
7302
+ await write(openapiPath, `${JSON.stringify(openApiSource2 ? openApiSource2.openApiData() : {})}
7303
+ `);
7304
+ await write(join17(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
6825
7305
  `);
6826
7306
  await writeStagedContent(out, staged);
6827
7307
  await pruneOrphans(srcDir, written);
@@ -6829,16 +7309,48 @@ var generateRuntime = async (project) => {
6829
7309
  };
6830
7310
 
6831
7311
  // src/core/config.ts
6832
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
7312
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
6833
7313
 
6834
7314
  // src/core/bridge.ts
6835
- import { existsSync as existsSync7 } from "node:fs";
6836
- import { readFile as readFile9 } from "node:fs/promises";
6837
- import { join as join12 } from "pathe";
7315
+ import { existsSync as existsSync10 } from "node:fs";
7316
+ import { readFile as readFile12 } from "node:fs/promises";
7317
+ import { join as join18 } from "pathe";
7318
+
7319
+ // src/migrate/mintlify/assets.ts
7320
+ var assetRefs = (config) => {
7321
+ const refs = ["/images"];
7322
+ const logo = config.logo;
7323
+ if (typeof logo === "string") {
7324
+ refs.push(logo);
7325
+ } else if (logo) {
7326
+ refs.push(logo.light, logo.dark);
7327
+ }
7328
+ const favicon = config.favicon;
7329
+ if (typeof favicon === "string") {
7330
+ refs.push(favicon);
7331
+ } else if (favicon) {
7332
+ refs.push(favicon.light, favicon.dark);
7333
+ }
7334
+ refs.push(config.theme?.backgroundImage, config.theme?.backgroundImageDark);
7335
+ return refs;
7336
+ };
7337
+ var assetSegments = (config) => {
7338
+ const segments = new Set;
7339
+ for (const ref of assetRefs(config)) {
7340
+ if (typeof ref !== "string" || !ref.startsWith("/")) {
7341
+ continue;
7342
+ }
7343
+ const [segment] = ref.replace(/^\/+/u, "").split("/");
7344
+ if (segment) {
7345
+ segments.add(segment);
7346
+ }
7347
+ }
7348
+ return [...segments];
7349
+ };
6838
7350
 
6839
7351
  // src/migrate/mintlify/config.ts
6840
- import { readFile as readFile8 } from "node:fs/promises";
6841
- import { dirname as dirname8, relative as relative7, resolve as resolve4 } from "pathe";
7352
+ import { readFile as readFile11 } from "node:fs/promises";
7353
+ import { dirname as dirname8, relative as relative7, resolve as resolve5 } from "pathe";
6842
7354
  var MINTLIFY_DEFAULT_IGNORES = [
6843
7355
  "**/_*",
6844
7356
  "**/.*",
@@ -6879,7 +7391,7 @@ var isInsideRoot = (root, candidate) => {
6879
7391
  };
6880
7392
  var readJsonFile = async (file) => {
6881
7393
  try {
6882
- return JSON.parse(await readFile8(file, "utf-8"));
7394
+ return JSON.parse(await readFile11(file, "utf-8"));
6883
7395
  } catch (error) {
6884
7396
  throw new BlumeError({
6885
7397
  code: "BLUME_MINTLIFY_CONFIG_INVALID",
@@ -6899,7 +7411,7 @@ var resolveRefs = async (value, options) => {
6899
7411
  }
6900
7412
  const ref = asString(object.$ref);
6901
7413
  if (ref) {
6902
- const refFile = resolve4(dirname8(options.file), ref);
7414
+ const refFile = resolve5(dirname8(options.file), ref);
6903
7415
  if (!isInsideRoot(options.root, refFile)) {
6904
7416
  throw new BlumeError({
6905
7417
  code: "BLUME_MINTLIFY_REF_OUTSIDE_ROOT",
@@ -7255,13 +7767,15 @@ var mintlifySelectors = (spec) => {
7255
7767
  };
7256
7768
  var mintignorePatterns = async (root) => {
7257
7769
  try {
7258
- const raw = await readFile8(resolve4(root, ".mintignore"), "utf-8");
7770
+ const raw = await readFile11(resolve5(root, ".mintignore"), "utf-8");
7259
7771
  return raw.split(`
7260
7772
  `).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).filter((line) => !line.startsWith("!")).map((line) => line.endsWith("/") ? `${line}**` : line);
7261
7773
  } catch {
7262
7774
  return [];
7263
7775
  }
7264
7776
  };
7777
+ var REDIRECT_PARAM = /:(?<name>[A-Za-z_]\w*)(?<modifier>[*+?])?/gu;
7778
+ var toAstroRedirectPath = (path) => path.replaceAll(REDIRECT_PARAM, (_match, name, modifier) => modifier === "*" || modifier === "+" ? `[...${name}]` : `[${name}]`);
7265
7779
  var mintlifyRedirects = (spec) => asArray(spec.redirects).flatMap((redirect) => {
7266
7780
  const object = asObject(redirect);
7267
7781
  if (!object) {
@@ -7272,8 +7786,72 @@ var mintlifyRedirects = (spec) => asArray(spec.redirects).flatMap((redirect) =>
7272
7786
  if (!from || !to) {
7273
7787
  return [];
7274
7788
  }
7275
- return [{ from, to }];
7789
+ return [{ from: toAstroRedirectPath(from), to: toAstroRedirectPath(to) }];
7276
7790
  });
7791
+ var openapiRouteFromDirectory = (value) => {
7792
+ const directory = normalizeDirectory(asString(value) ?? "");
7793
+ return directory.length > 0 ? `/${directory}` : undefined;
7794
+ };
7795
+ var openapiSourcesFromValue = (value, context) => {
7796
+ if (typeof value === "string") {
7797
+ if (value.length === 0 || API_ENDPOINT_REF.test(value)) {
7798
+ return [];
7799
+ }
7800
+ return [
7801
+ withoutUndefined({
7802
+ label: context.label,
7803
+ route: context.route,
7804
+ spec: value
7805
+ })
7806
+ ];
7807
+ }
7808
+ if (Array.isArray(value)) {
7809
+ return value.flatMap((item) => openapiSourcesFromValue(item, context));
7810
+ }
7811
+ const object = asObject(value);
7812
+ if (!object) {
7813
+ return [];
7814
+ }
7815
+ return openapiSourcesFromValue(object.source ?? object.openapi, {
7816
+ label: context.label,
7817
+ route: openapiRouteFromDirectory(object.directory) ?? context.route
7818
+ });
7819
+ };
7820
+ var mintlifyOpenapi = (spec) => {
7821
+ const drafts = [];
7822
+ const visit = (node) => {
7823
+ if (Array.isArray(node)) {
7824
+ for (const item of node) {
7825
+ visit(item);
7826
+ }
7827
+ return;
7828
+ }
7829
+ const object = asObject(node);
7830
+ if (!object) {
7831
+ return;
7832
+ }
7833
+ if (hasOwn(object, "openapi")) {
7834
+ drafts.push(...openapiSourcesFromValue(object.openapi, {
7835
+ label: labelForNavItem(object)
7836
+ }));
7837
+ }
7838
+ for (const children of childNavigationArrays(object)) {
7839
+ visit(children);
7840
+ }
7841
+ };
7842
+ visit(spec.navigation);
7843
+ drafts.push(...openapiSourcesFromValue(spec.openapi, {}));
7844
+ drafts.push(...openapiSourcesFromValue(asObject(spec.api)?.openapi, {}));
7845
+ const seen = new Set;
7846
+ const sources = drafts.flatMap((draft) => {
7847
+ if (seen.has(draft.spec)) {
7848
+ return [];
7849
+ }
7850
+ seen.add(draft.spec);
7851
+ return [draft];
7852
+ });
7853
+ return sources.length > 0 ? { enabled: true, sources } : undefined;
7854
+ };
7277
7855
  var mintlifyLogo = (value) => {
7278
7856
  if (typeof value === "string") {
7279
7857
  return value;
@@ -7305,6 +7883,12 @@ var mintlifyFavicon = (value) => {
7305
7883
  }
7306
7884
  return withoutUndefined({ dark, light });
7307
7885
  };
7886
+ var mintlifyIcons = (value) => {
7887
+ const library = asString(asObject(value)?.library);
7888
+ return {
7889
+ library: library === "lucide" || library === "tabler" ? library : "fontawesome"
7890
+ };
7891
+ };
7308
7892
  var mintlifyBanner = (value) => {
7309
7893
  const object = asObject(value);
7310
7894
  const content = object ? asString(object.content) : undefined;
@@ -7395,6 +7979,27 @@ var mintlifyMarkdown = (value, styling) => {
7395
7979
  schema: object?.schema === false ? false : undefined
7396
7980
  });
7397
7981
  };
7982
+ var FAMILY_TO_SLUG = Object.fromEntries(Object.entries(GOOGLE_FONTS).map(([slug, def]) => [
7983
+ def.family.toLowerCase(),
7984
+ slug
7985
+ ]));
7986
+ var fontSlugForFamily = (value) => {
7987
+ const family = asString(value);
7988
+ return family ? FAMILY_TO_SLUG[family.toLowerCase()] : undefined;
7989
+ };
7990
+ var mintlifyFonts = (value) => {
7991
+ const object = asObject(value);
7992
+ if (!object) {
7993
+ return;
7994
+ }
7995
+ const heading = asObject(object.heading);
7996
+ const body = asObject(object.body);
7997
+ const fonts = withoutUndefined({
7998
+ body: fontSlugForFamily(body?.family) ?? fontSlugForFamily(object.family),
7999
+ display: fontSlugForFamily(heading?.family) ?? fontSlugForFamily(object.family)
8000
+ });
8001
+ return Object.keys(fonts).length > 0 ? fonts : undefined;
8002
+ };
7398
8003
  var mintlifySeo = (value) => {
7399
8004
  const object = asObject(value);
7400
8005
  const metatags = asObject(object?.metatags);
@@ -7406,8 +8011,8 @@ var mintlifySeo = (value) => {
7406
8011
  };
7407
8012
  };
7408
8013
  var loadMintlifyConfig = async (root, file) => {
7409
- const projectRoot = resolve4(root);
7410
- const configFile = resolve4(file);
8014
+ const projectRoot = resolve5(root);
8015
+ const configFile = resolve5(file);
7411
8016
  const spec = asObject(await resolveRefs(await readJsonFile(configFile), {
7412
8017
  file: configFile,
7413
8018
  root: projectRoot,
@@ -7440,6 +8045,7 @@ var loadMintlifyConfig = async (root, file) => {
7440
8045
  },
7441
8046
  description: asString(spec.description),
7442
8047
  favicon: mintlifyFavicon(spec.favicon),
8048
+ icons: mintlifyIcons(spec.icons),
7443
8049
  logo: mintlifyLogo(spec.logo),
7444
8050
  markdown: mintlifyMarkdown(spec.markdown, styling),
7445
8051
  navigation: {
@@ -7449,6 +8055,7 @@ var loadMintlifyConfig = async (root, file) => {
7449
8055
  sidebarVariants: await mintlifySidebarVariants(spec),
7450
8056
  tabs: mintlifyTabs(spec)
7451
8057
  },
8058
+ openapi: mintlifyOpenapi(spec),
7452
8059
  redirects: mintlifyRedirects(spec),
7453
8060
  search: {
7454
8061
  indexing: {
@@ -7466,6 +8073,7 @@ var loadMintlifyConfig = async (root, file) => {
7466
8073
  backgroundDecoration: mintlifyBackgroundDecoration(spec.background),
7467
8074
  backgroundImage: backgroundImage.light,
7468
8075
  backgroundImageDark: backgroundImage.dark,
8076
+ fonts: mintlifyFonts(spec.fonts ?? spec.font),
7469
8077
  mode: appearance.default === "light" || appearance.default === "dark" || appearance.default === "system" ? appearance.default : "system",
7470
8078
  strict: appearance.strict === true
7471
8079
  },
@@ -7503,12 +8111,12 @@ var mintlifyI18n = (spec) => {
7503
8111
  // src/core/bridge.ts
7504
8112
  var MINTLIFY_CONFIG_FILES = ["docs.json", "mint.json"];
7505
8113
  var detectMintlifyBridge = async (root) => {
7506
- const configFile = MINTLIFY_CONFIG_FILES.map((name) => join12(root, name)).find((candidate) => existsSync7(candidate));
8114
+ const configFile = MINTLIFY_CONFIG_FILES.map((name) => join18(root, name)).find((candidate) => existsSync10(candidate));
7507
8115
  if (!configFile) {
7508
8116
  return null;
7509
8117
  }
7510
8118
  const config = await loadMintlifyConfig(root, configFile);
7511
- const spec = JSON.parse(await readFile9(configFile, "utf-8"));
8119
+ const spec = JSON.parse(await readFile12(configFile, "utf-8"));
7512
8120
  const i18n = mintlifyI18n(spec);
7513
8121
  if (i18n) {
7514
8122
  config.i18n = i18n;
@@ -7519,11 +8127,13 @@ var detectMintlifyBridge = async (root) => {
7519
8127
  const variables = config.variables ?? {};
7520
8128
  const root_ = config.content?.root ?? ".";
7521
8129
  const exclude = config.content?.exclude ?? [];
8130
+ const assets = assetSegments(config).filter((segment) => segment !== "public" && existsSync10(join18(root, segment)));
7522
8131
  return {
7523
8132
  configFile,
7524
8133
  raw: {
7525
8134
  ...config,
7526
8135
  content: {
8136
+ assets,
7527
8137
  exclude,
7528
8138
  root: root_,
7529
8139
  sources: [
@@ -7591,42 +8201,6 @@ var createModuleLoader = () => {
7591
8201
  };
7592
8202
  };
7593
8203
 
7594
- // src/core/project.ts
7595
- import { existsSync as existsSync8 } from "node:fs";
7596
- import { isAbsolute as isAbsolute4, join as join13, resolve as resolve5 } from "pathe";
7597
- var CONFIG_FILENAMES = [
7598
- "blume.config.ts",
7599
- "blume.config.mjs",
7600
- "blume.config.js"
7601
- ];
7602
- var THEME_FILENAMES = ["theme.css"];
7603
- var COMPONENTS_FILENAMES = ["components.tsx", "components.ts"];
7604
- var firstExisting = (root, names) => {
7605
- for (const name of names) {
7606
- const candidate = join13(root, name);
7607
- if (existsSync8(candidate)) {
7608
- return candidate;
7609
- }
7610
- }
7611
- return null;
7612
- };
7613
- var findConfigFile = (root) => firstExisting(root, CONFIG_FILENAMES);
7614
- var resolveProjectContext = (root, config) => {
7615
- const absoluteRoot = resolve5(root);
7616
- const contentRoot = isAbsolute4(config.content.root) ? config.content.root : join13(absoluteRoot, config.content.root);
7617
- const pagesPath = join13(absoluteRoot, config.content.pages);
7618
- const pagesRoot = existsSync8(pagesPath) ? pagesPath : null;
7619
- return {
7620
- componentsFile: firstExisting(absoluteRoot, COMPONENTS_FILENAMES),
7621
- configFile: findConfigFile(absoluteRoot),
7622
- contentRoot,
7623
- outDir: join13(absoluteRoot, ".blume"),
7624
- pagesRoot,
7625
- root: absoluteRoot,
7626
- themeFile: firstExisting(absoluteRoot, THEME_FILENAMES)
7627
- };
7628
- };
7629
-
7630
8204
  // src/core/schema.ts
7631
8205
  import { z as z2 } from "zod";
7632
8206
  var iconName = z2.string().min(1);
@@ -7656,7 +8230,17 @@ var changelogMetaSchema = z2.object({
7656
8230
  date: dateSchema.optional(),
7657
8231
  version: z2.string().optional()
7658
8232
  }).strict();
8233
+ var authorSchema = z2.union([
8234
+ z2.string(),
8235
+ z2.object({
8236
+ avatar: z2.string().optional(),
8237
+ image: z2.string().optional(),
8238
+ name: z2.string(),
8239
+ url: z2.string().optional()
8240
+ }).passthrough()
8241
+ ]);
7659
8242
  var pageMetaBaseSchema = z2.object({
8243
+ authors: z2.union([authorSchema, z2.array(authorSchema)]).optional(),
7660
8244
  changelog: changelogMetaSchema.optional(),
7661
8245
  date: dateSchema.optional(),
7662
8246
  deprecated: z2.boolean().default(false),
@@ -7810,6 +8394,7 @@ var contentSourceSchema = z2.discriminatedUnion("type", [
7810
8394
  customSourceSchema
7811
8395
  ]);
7812
8396
  var contentConfigSchema = z2.object({
8397
+ assets: z2.array(z2.string()).default([]),
7813
8398
  defaultType: z2.string().default("doc"),
7814
8399
  exclude: z2.array(z2.string()).default(["**/_*", "**/.*"]),
7815
8400
  include: z2.array(z2.string()).default(["**/*.{md,mdx}"]),
@@ -8096,7 +8681,10 @@ var openapiSourceSchema = z2.object({
8096
8681
  spec: z2.string()
8097
8682
  }).strict();
8098
8683
  var openapiConfigSchema = z2.object({
8684
+ codeSamples: z2.array(z2.string()).default(["curl", "js", "python"]),
8099
8685
  enabled: z2.boolean().default(false),
8686
+ expandSchemas: z2.boolean().default(false),
8687
+ renderer: z2.enum(["blume", "scalar"]).default("blume"),
8100
8688
  route: z2.string().default("/reference"),
8101
8689
  sources: z2.array(openapiSourceSchema).default([]),
8102
8690
  spec: z2.string().optional(),
@@ -8125,6 +8713,9 @@ var tocConfigSchema = z2.union([
8125
8713
  minLevel: value.minHeadingLevel ?? 2
8126
8714
  };
8127
8715
  });
8716
+ var iconsConfigSchema = z2.object({
8717
+ library: z2.enum(["lucide", "fontawesome", "tabler"]).default("lucide")
8718
+ }).strict();
8128
8719
  var blumeConfigSchema = z2.object({
8129
8720
  ai: aiConfigSchema.default({}),
8130
8721
  analytics: analyticsConfigSchema.optional(),
@@ -8139,6 +8730,7 @@ var blumeConfigSchema = z2.object({
8139
8730
  feedback: z2.boolean().default(true),
8140
8731
  github: githubConfigSchema.optional(),
8141
8732
  i18n: i18nConfigSchema.optional(),
8733
+ icons: iconsConfigSchema.default({}),
8142
8734
  lastModified: lastModifiedConfigSchema.default(false),
8143
8735
  logo: logoConfigSchema.optional(),
8144
8736
  markdown: markdownConfigSchema.default({}),
@@ -8180,7 +8772,7 @@ var loadConfig = async (root, options = {}) => {
8180
8772
  const sourceFile = bridge?.configFile ?? configFile;
8181
8773
  const parsed = blumeConfigSchema.safeParse(raw ?? {});
8182
8774
  if (!parsed.success) {
8183
- const source = sourceFile && existsSync9(sourceFile) ? readFileSync6(sourceFile, "utf-8") : undefined;
8775
+ const source = sourceFile && existsSync11(sourceFile) ? readFileSync7(sourceFile, "utf-8") : undefined;
8184
8776
  const diagnostics = diagnosticsFromZod(parsed.error, {
8185
8777
  code: "BLUME_CONFIG_INVALID",
8186
8778
  file: sourceFile ?? undefined,
@@ -8657,7 +9249,7 @@ var discoverFolderMeta = async (contentRoot) => {
8657
9249
  };
8658
9250
 
8659
9251
  // src/core/sources/normalize.ts
8660
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
9252
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
8661
9253
  import GithubSlugger from "github-slugger";
8662
9254
  import { extname as extname4 } from "pathe";
8663
9255
  var NUMERIC_PREFIX2 = /^\d+[-_.]/u;
@@ -8665,7 +9257,7 @@ var GROUP_FOLDER2 = /^\((?<label>.+)\)$/u;
8665
9257
  var WORD_SPLIT2 = /[-_]/u;
8666
9258
  var stripNumericPrefix = (segment) => segment.replace(NUMERIC_PREFIX2, "");
8667
9259
  var groupLabel = (segment) => segment.match(GROUP_FOLDER2)?.groups?.label ?? null;
8668
- var slugify2 = (text) => text.toLowerCase().trim().replaceAll(/[^\w\s-]/gu, "").replaceAll(/[\s_]+/gu, "-").replaceAll(/-+/gu, "-").replaceAll(/^-|-$/gu, "");
9260
+ var slugify3 = (text) => text.toLowerCase().trim().replaceAll(/[^\w\s-]/gu, "").replaceAll(/[\s_]+/gu, "-").replaceAll(/-+/gu, "-").replaceAll(/^-|-$/gu, "");
8669
9261
  var titleCase = (value) => value.split(WORD_SPLIT2).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
8670
9262
  var mapRoute = (relativePath) => {
8671
9263
  const withoutExt = relativePath.slice(0, relativePath.length - extname4(relativePath).length);
@@ -8731,8 +9323,9 @@ var extractLinks = (body) => {
8731
9323
  if (target === undefined || match.index === undefined) {
8732
9324
  continue;
8733
9325
  }
9326
+ const targetOffset = match.index + match[0].indexOf("](") + "](".length;
8734
9327
  links.push({
8735
- column: line.indexOf(target, match.index) + 1,
9328
+ column: targetOffset + 1,
8736
9329
  line: lineNumber,
8737
9330
  target
8738
9331
  });
@@ -8782,7 +9375,7 @@ var normalizeEntry2 = (entry, ctx) => {
8782
9375
  const ext = format === "mdx" ? ".mdx" : ".md";
8783
9376
  const result = pageMetaSchema.safeParse(entry.data);
8784
9377
  if (!result.success) {
8785
- const source = entry.raw ?? (entry.sourcePath && existsSync10(entry.sourcePath) ? readFileSync7(entry.sourcePath, "utf-8") : undefined);
9378
+ const source = entry.raw ?? (entry.sourcePath && existsSync12(entry.sourcePath) ? readFileSync8(entry.sourcePath, "utf-8") : undefined);
8786
9379
  return {
8787
9380
  diagnostics: diagnosticsFromZod(result.error, {
8788
9381
  code: "BLUME_FRONTMATTER_INVALID",
@@ -8831,15 +9424,15 @@ var normalizeEntry2 = (entry, ctx) => {
8831
9424
  };
8832
9425
 
8833
9426
  // src/core/sources/resolve.ts
8834
- import { join as join22 } from "pathe";
9427
+ import { join as join26 } from "pathe";
8835
9428
 
8836
9429
  // src/core/sources/filesystem.ts
8837
- import { existsSync as existsSync11, watch as fsWatch } from "node:fs";
8838
- import { readFile as readFile10 } from "node:fs/promises";
8839
- import { extname as extname5, isAbsolute as isAbsolute5, join as join14, relative as relative10, resolve as resolve6 } from "pathe";
9430
+ import { existsSync as existsSync13, watch as fsWatch } from "node:fs";
9431
+ import { readFile as readFile13 } from "node:fs/promises";
9432
+ import { extname as extname5, isAbsolute as isAbsolute6, join as join19, relative as relative10, resolve as resolve6 } from "pathe";
8840
9433
  import { glob as glob6 } from "tinyglobby";
8841
9434
  var filesystemSource = (options) => {
8842
- const contentRoot = isAbsolute5(options.root) ? options.root : join14(resolve6(options.projectRoot), options.root);
9435
+ const contentRoot = isAbsolute6(options.root) ? options.root : join19(resolve6(options.projectRoot), options.root);
8843
9436
  const load2 = async () => {
8844
9437
  const files = await glob6(options.include, {
8845
9438
  absolute: true,
@@ -8849,123 +9442,47 @@ var filesystemSource = (options) => {
8849
9442
  });
8850
9443
  files.sort();
8851
9444
  const entries = await Promise.all(files.map(async (file) => {
8852
- const source = await readFile10(file, "utf-8");
9445
+ const source = await readFile13(file, "utf-8");
8853
9446
  const ext = extname5(file).toLowerCase();
8854
9447
  const format = ext === ".mdx" ? "mdx" : "md";
8855
- const parsed = frontmatter_default(source);
8856
- return {
8857
- body: { format, text: parsed.content },
8858
- data: parsed.data,
8859
- ref: relative10(contentRoot, file),
8860
- sourcePath: file
8861
- };
8862
- }));
8863
- return { diagnostics: [], entries };
8864
- };
8865
- const validate = () => {
8866
- if (!existsSync11(contentRoot)) {
8867
- throw new BlumeError({
8868
- code: options.missingCode ?? "BLUME_CONTENT_ROOT_MISSING",
8869
- file: contentRoot,
8870
- message: `Content root not found: ${options.root}`,
8871
- severity: "error",
8872
- suggestion: `Create a "${options.root}" folder with at least one .md or .mdx file, or set content.root in blume.config.ts.`
8873
- });
8874
- }
8875
- };
8876
- const watch = (onChange) => {
8877
- if (!existsSync11(contentRoot)) {
8878
- return () => {};
8879
- }
8880
- const watcher = fsWatch(contentRoot, { recursive: true }, onChange);
8881
- return () => watcher.close();
8882
- };
8883
- return {
8884
- contentRoot,
8885
- load: load2,
8886
- name: options.name,
8887
- prefix: options.prefix,
8888
- read: (ref) => readFile10(join14(contentRoot, ref), "utf-8"),
8889
- staged: false,
8890
- validate,
8891
- watch
8892
- };
8893
- };
8894
-
8895
- // src/core/sources/cache.ts
8896
- import { mkdir as mkdir3, readFile as readFile11, writeFile as writeFile3 } from "node:fs/promises";
8897
- import { join as join15 } from "pathe";
8898
- var hashText = (text) => {
8899
- let hash = 5381;
8900
- for (let i = 0;i < text.length; i += 1) {
8901
- hash = (hash * 33 + (text.codePointAt(i) ?? 0)) % 2147483647;
8902
- }
8903
- return hash.toString(36);
8904
- };
8905
- var entriesDigest = (entries) => hashText(entries.map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`).join("|"));
8906
- var pollingWatch = (load2, intervalSeconds) => (onChange) => {
8907
- let last = "";
8908
- const tick = async () => {
8909
- try {
8910
- const { entries } = await load2();
8911
- const next = entriesDigest(entries);
8912
- if (last && next !== last) {
8913
- onChange();
8914
- }
8915
- last = next;
8916
- } catch {}
8917
- };
8918
- const timer = setInterval(() => {
8919
- tick();
8920
- }, intervalSeconds * 1000);
8921
- return () => clearInterval(timer);
8922
- };
8923
- var snapshotCache = (cacheDir) => {
8924
- const file = join15(cacheDir, "entries.json");
8925
- return {
8926
- read: async () => {
8927
- try {
8928
- return JSON.parse(await readFile11(file, "utf-8"));
8929
- } catch {
8930
- return [];
8931
- }
8932
- },
8933
- write: async (entries) => {
8934
- try {
8935
- await mkdir3(cacheDir, { recursive: true });
8936
- await writeFile3(file, `${JSON.stringify(entries)}
8937
- `, "utf-8");
8938
- } catch {}
8939
- }
9448
+ const parsed = frontmatter_default(source);
9449
+ return {
9450
+ body: { format, text: parsed.content },
9451
+ data: parsed.data,
9452
+ ref: relative10(contentRoot, file),
9453
+ sourcePath: file
9454
+ };
9455
+ }));
9456
+ return { diagnostics: [], entries };
8940
9457
  };
8941
- };
8942
- var loadWithCache = async (name, cache, fetchEntries, refresh = true) => {
8943
- if (!refresh) {
8944
- const cached3 = await cache.read();
8945
- if (cached3.length > 0) {
8946
- return { diagnostics: [], entries: cached3 };
9458
+ const validate = () => {
9459
+ if (!existsSync13(contentRoot)) {
9460
+ throw new BlumeError({
9461
+ code: options.missingCode ?? "BLUME_CONTENT_ROOT_MISSING",
9462
+ file: contentRoot,
9463
+ message: `Content root not found: ${options.root}`,
9464
+ severity: "error",
9465
+ suggestion: `Create a "${options.root}" folder with at least one .md or .mdx file, or set content.root in blume.config.ts.`
9466
+ });
8947
9467
  }
8948
- }
8949
- try {
8950
- const entries = await fetchEntries();
8951
- await cache.write(entries);
8952
- return { diagnostics: [], entries };
8953
- } catch (error) {
8954
- const fallback = await cache.read();
8955
- if (fallback.length > 0) {
8956
- const diagnostic = {
8957
- code: "BLUME_SOURCE_OFFLINE",
8958
- message: `Source "${name}" could not be fetched (${error.message}); served ${fallback.length} cached entries.`,
8959
- severity: "warning"
8960
- };
8961
- return { diagnostics: [diagnostic], entries: fallback };
9468
+ };
9469
+ const watch = (onChange) => {
9470
+ if (!existsSync13(contentRoot)) {
9471
+ return () => {};
8962
9472
  }
8963
- throw new BlumeError({
8964
- code: "BLUME_SOURCE_FETCH_FAILED",
8965
- message: `Source "${name}" failed to load and no cache is available: ${error.message}`,
8966
- severity: "error"
8967
- });
8968
- }
9473
+ const watcher = fsWatch(contentRoot, { recursive: true }, onChange);
9474
+ return () => watcher.close();
9475
+ };
9476
+ return {
9477
+ contentRoot,
9478
+ load: load2,
9479
+ name: options.name,
9480
+ prefix: options.prefix,
9481
+ read: (ref) => readFile13(join19(contentRoot, ref), "utf-8"),
9482
+ staged: false,
9483
+ validate,
9484
+ watch
9485
+ };
8969
9486
  };
8970
9487
 
8971
9488
  // src/core/sources/github-releases.ts
@@ -8973,9 +9490,9 @@ var DEFAULT_BASE_URL = "https://api.github.com";
8973
9490
  var DEFAULT_LIMIT = 100;
8974
9491
  var PER_PAGE = 100;
8975
9492
  var LEADING_V = /^v/iu;
8976
- var NON_SLUG2 = /[^a-z0-9]+/gu;
9493
+ var NON_SLUG3 = /[^a-z0-9]+/gu;
8977
9494
  var EDGE_DASHES = /^-+|-+$/gu;
8978
- var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG2, "-").replaceAll(EDGE_DASHES, "");
9495
+ var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG3, "-").replaceAll(EDGE_DASHES, "");
8979
9496
  var githubHeaders = () => {
8980
9497
  const headers = new Headers({ Accept: "application/vnd.github+json" });
8981
9498
  const token = process.env.GITHUB_TOKEN;
@@ -9135,11 +9652,12 @@ var enumerateGithub = async (github, include, doFetch) => {
9135
9652
  }
9136
9653
  const body = await res.json();
9137
9654
  const prefix = base ? `${base}/` : "";
9138
- return (body.tree ?? []).filter((node) => node.type === "blob" && node.path.startsWith(prefix)).map((node) => node.path.slice(prefix.length)).filter((rel) => matchesInclude(rel, include)).map((rel) => ({
9655
+ const refs = (body.tree ?? []).filter((node) => node.type === "blob" && node.path.startsWith(prefix)).map((node) => node.path.slice(prefix.length)).filter((rel) => matchesInclude(rel, include)).map((rel) => ({
9139
9656
  editUrl: `https://github.com/${owner}/${repo}/edit/${ref}/${prefix}${rel}`,
9140
9657
  fetchUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${prefix}${rel}`,
9141
9658
  ref: rel
9142
9659
  }));
9660
+ return { refs, truncated: body.truncated === true };
9143
9661
  };
9144
9662
  var mdxRemoteSource = (options, ctx) => {
9145
9663
  const doFetch = options.fetchImpl ?? globalThis.fetch;
@@ -9151,11 +9669,12 @@ var mdxRemoteSource = (options, ctx) => {
9151
9669
  }
9152
9670
  if (options.files && options.url) {
9153
9671
  const base = options.url.replace(/\/$/u, "");
9154
- return options.files.filter((ref) => matchesInclude(ref, options.include)).map((ref) => ({
9672
+ const refs = options.files.filter((ref) => matchesInclude(ref, options.include)).map((ref) => ({
9155
9673
  editUrl: `${base}/${ref}`,
9156
9674
  fetchUrl: `${base}/${ref}`,
9157
9675
  ref
9158
9676
  }));
9677
+ return { refs, truncated: false };
9159
9678
  }
9160
9679
  throw new BlumeError({
9161
9680
  code: "BLUME_SOURCE_MISCONFIGURED",
@@ -9181,12 +9700,40 @@ var mdxRemoteSource = (options, ctx) => {
9181
9700
  };
9182
9701
  };
9183
9702
  const load2 = async () => {
9703
+ const skipped = [];
9184
9704
  const result = await loadWithCache(options.name, cache, async () => {
9185
- const refs = await enumerate();
9186
- return await Promise.all(refs.map(fetchEntry));
9705
+ const { refs, truncated } = await enumerate();
9706
+ if (truncated) {
9707
+ skipped.push({
9708
+ code: "BLUME_SOURCE_TRUNCATED",
9709
+ message: `Source "${options.name}" hit GitHub's tree listing limit; some files were not enumerated. Narrow the source path or split the repo.`,
9710
+ severity: "warning"
9711
+ });
9712
+ }
9713
+ const settled = await Promise.all(refs.map(async (ref) => {
9714
+ try {
9715
+ return await fetchEntry(ref);
9716
+ } catch (error) {
9717
+ skipped.push({
9718
+ code: "BLUME_SOURCE_FETCH_FAILED",
9719
+ message: `Source "${options.name}" skipped "${ref.ref}" (${error.message}); the rest were imported.`,
9720
+ severity: "warning"
9721
+ });
9722
+ return null;
9723
+ }
9724
+ }));
9725
+ const entries = settled.filter((entry) => entry !== null);
9726
+ if (refs.length > 0 && entries.length === 0) {
9727
+ skipped.length = 0;
9728
+ throw new Error(`all ${refs.length} remote file(s) failed to fetch`);
9729
+ }
9730
+ return entries;
9187
9731
  }, ctx.refresh ?? true);
9188
9732
  snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));
9189
- return result;
9733
+ return {
9734
+ ...result,
9735
+ diagnostics: [...result.diagnostics, ...skipped]
9736
+ };
9190
9737
  };
9191
9738
  const read = async (ref) => {
9192
9739
  const cached3 = snapshot.get(ref);
@@ -9208,21 +9755,25 @@ var mdxRemoteSource = (options, ctx) => {
9208
9755
  };
9209
9756
 
9210
9757
  // src/core/sources/mintlify.ts
9211
- import { existsSync as existsSync13, watch as fsWatch2 } from "node:fs";
9212
- import { readFile as readFile13 } from "node:fs/promises";
9213
- import { isAbsolute as isAbsolute6, join as join18, relative as relative13, resolve as resolve8 } from "pathe";
9758
+ import { existsSync as existsSync15, watch as fsWatch2 } from "node:fs";
9759
+ import { readFile as readFile15 } from "node:fs/promises";
9760
+ import { isAbsolute as isAbsolute8, join as join22, relative as relative14, resolve as resolve8 } from "pathe";
9214
9761
  import { glob as glob7 } from "tinyglobby";
9215
9762
 
9216
9763
  // src/migrate/shared.ts
9217
- import { existsSync as existsSync12 } from "node:fs";
9218
- import { readFile as readFile12, writeFile as writeFile4 } from "node:fs/promises";
9219
- import { join as join16 } from "pathe";
9764
+ import { existsSync as existsSync14 } from "node:fs";
9765
+ import { readFile as readFile14, writeFile as writeFile5 } from "node:fs/promises";
9766
+ import { isAbsolute as isAbsolute7, join as join20, relative as relative11 } from "pathe";
9767
+ var isInsideRoot2 = (root, candidate) => {
9768
+ const rel = relative11(root, candidate);
9769
+ return rel === "" || !rel.startsWith("..") && !isAbsolute7(rel);
9770
+ };
9220
9771
  var writeBlumeConfig = async (root, config) => {
9221
9772
  const body = `import { defineConfig } from "blume";
9222
9773
 
9223
9774
  export default defineConfig(${JSON.stringify(config, null, 2)});
9224
9775
  `;
9225
- await writeFile4(join16(root, "blume.config.ts"), body, "utf-8");
9776
+ await writeFile5(join20(root, "blume.config.ts"), body, "utf-8");
9226
9777
  };
9227
9778
  var BLUME_SCRIPTS = {
9228
9779
  build: "blume build",
@@ -9230,13 +9781,13 @@ var BLUME_SCRIPTS = {
9230
9781
  start: "blume preview"
9231
9782
  };
9232
9783
  var rewriteFrameworkScripts = async (root, cli, remove) => {
9233
- const pkgPath = join16(root, "package.json");
9234
- if (!existsSync12(pkgPath)) {
9784
+ const pkgPath = join20(root, "package.json");
9785
+ if (!existsSync14(pkgPath)) {
9235
9786
  return false;
9236
9787
  }
9237
9788
  let pkg;
9238
9789
  try {
9239
- pkg = JSON.parse(await readFile12(pkgPath, "utf-8"));
9790
+ pkg = JSON.parse(await readFile14(pkgPath, "utf-8"));
9240
9791
  } catch {
9241
9792
  return false;
9242
9793
  }
@@ -9259,30 +9810,12 @@ var rewriteFrameworkScripts = async (root, cli, remove) => {
9259
9810
  }
9260
9811
  if (changed) {
9261
9812
  pkg.scripts = next;
9262
- await writeFile4(pkgPath, `${JSON.stringify(pkg, null, 2)}
9813
+ await writeFile5(pkgPath, `${JSON.stringify(pkg, null, 2)}
9263
9814
  `, "utf-8");
9264
9815
  }
9265
9816
  return changed;
9266
9817
  };
9267
- var gitignoreKey = (line) => line.trim().replace(/\/+$/u, "");
9268
- var ensureGitignore = async (root, entries) => {
9269
- const path = join16(root, ".gitignore");
9270
- const existing = existsSync12(path) ? await readFile12(path, "utf-8") : "";
9271
- const present = new Set(existing.split(`
9272
- `).map(gitignoreKey).filter(Boolean));
9273
- const added = entries.filter((entry) => !present.has(gitignoreKey(entry)));
9274
- if (added.length === 0) {
9275
- return [];
9276
- }
9277
- const gap = existing.length > 0 && !existing.endsWith(`
9278
- `) ? `
9279
- ` : "";
9280
- await writeFile4(path, `${existing}${gap}${added.join(`
9281
- `)}
9282
- `, "utf-8");
9283
- return added;
9284
- };
9285
- var leftoverFiles = (root, candidates) => candidates.filter((candidate) => existsSync12(join16(root, candidate)));
9818
+ var leftoverFiles = (root, candidates) => candidates.filter((candidate) => existsSync14(join20(root, candidate)));
9286
9819
  var attribute = (attrs, name) => {
9287
9820
  const match = attrs.match(new RegExp(`\\b${name}=(?:"(?<dq>[^"]*)"|'(?<sq>[^']*)')`, "u"));
9288
9821
  return match?.groups?.dq ?? match?.groups?.sq;
@@ -9679,7 +10212,7 @@ var isLiteralObject = (value) => typeof value === "object" && value !== null &&
9679
10212
  var asLiteralArray = (value) => Array.isArray(value) ? value : undefined;
9680
10213
 
9681
10214
  // src/migrate/mintlify/content.ts
9682
- import { dirname as dirname10, join as join17, relative as relative11 } from "pathe";
10215
+ import { dirname as dirname10, join as join21, relative as relative12 } from "pathe";
9683
10216
  var CALLOUT_DIRECTIVES = {
9684
10217
  Check: "success",
9685
10218
  Danger: "danger",
@@ -9715,9 +10248,9 @@ var rewriteSnippetImports = (source, options) => {
9715
10248
  if (/\.mdx?$/u.test(importSource)) {
9716
10249
  return "";
9717
10250
  }
9718
- const target = join17(options.root, importSource.replace(/^\/+/u, ""));
10251
+ const target = join21(options.root, importSource.replace(/^\/+/u, ""));
9719
10252
  components.push(importSource.replace(/^\/+/u, ""));
9720
- let rel = relative11(dirname10(options.filePath), target);
10253
+ let rel = relative12(dirname10(options.filePath), target);
9721
10254
  if (!rel.startsWith(".")) {
9722
10255
  rel = `./${rel}`;
9723
10256
  }
@@ -9725,7 +10258,7 @@ var rewriteSnippetImports = (source, options) => {
9725
10258
  });
9726
10259
  return { components, source: next };
9727
10260
  };
9728
- var UNSUPPORTED_COMPONENTS = ["ParamField", "ResponseField"];
10261
+ var UNSUPPORTED_COMPONENTS = ["Update"];
9729
10262
  var unsupportedMintlifyComponents = (source) => UNSUPPORTED_COMPONENTS.filter((name) => new RegExp(`<${name}\\b`, "u").test(source));
9730
10263
 
9731
10264
  // src/migrate/mintlify/frontmatter.ts
@@ -9907,7 +10440,7 @@ var rewriteMintlifySvgIconProps = (source) => {
9907
10440
 
9908
10441
  // src/migrate/mintlify/snippets.ts
9909
10442
  import { readFile as readFileFromDisk } from "node:fs/promises";
9910
- import { dirname as dirname11, relative as relative12, resolve as resolve7 } from "pathe";
10443
+ import { dirname as dirname11, relative as relative13, resolve as resolve7 } from "pathe";
9911
10444
  var MARKDOWN_SNIPPET_IMPORT = /^import\s+(?<name>[$A-Z_a-z][$\w]*)\s+from\s+["'](?<source>[^"']+\.mdx?)["'];?\s*$/gmu;
9912
10445
  var NAMED_SNIPPET_IMPORT = /^import\s+\{(?<names>[^}]+)\}\s+from\s+["'](?<source>[^"']+\.mdx?)["'];?\s*$/gmu;
9913
10446
  var EXPORTED_STRING_CONST = /^export\s+const\s+(?<name>[$A-Z_a-z][$\w]*)\s*=\s*(?:"(?<double>(?:\\.|[^"\\])*)"|'(?<single>(?:\\.|[^'\\])*)'|`(?<template>(?:\\.|[^`\\])*)`)\s*;?\s*$/gmu;
@@ -9916,15 +10449,15 @@ var PLACEHOLDER = /\{(?<name>[$A-Z_a-z][$\w]*)\}/gu;
9916
10449
  var GLOBAL_VARIABLE = /\{\{\s*(?<name>[A-Za-z0-9-]+)\s*\}\}/gu;
9917
10450
  var FRONTMATTER_BLOCK = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/u;
9918
10451
  var USER_EXPORT = /^(?:export\s+)?(?:const|let|var)\s+user\s*=|^import\s+\{\s*user\s*\}/mu;
9919
- var isInsideRoot2 = (root, candidate) => {
9920
- const rel = relative12(root, candidate);
10452
+ var isInsideRoot3 = (root, candidate) => {
10453
+ const rel = relative13(root, candidate);
9921
10454
  return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
9922
10455
  };
9923
10456
  var escapeRegExp2 = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
9924
10457
  var snippetSelfClosingTagPattern = (name) => new RegExp(`<${escapeRegExp2(name)}(?<attrs>[^>]*?)\\s*/>`, "gu");
9925
10458
  var snippetPairedTagPattern = (name) => new RegExp(`<${escapeRegExp2(name)}(?<attrs>[^>]*?)>[\\s\\S]*?</${escapeRegExp2(name)}>`, "gu");
9926
10459
  var rootRelativePath = (root, file) => {
9927
- const rel = relative12(root, file);
10460
+ const rel = relative13(root, file);
9928
10461
  return rel ? `/${rel}` : "/";
9929
10462
  };
9930
10463
  var snippetCycleMessage = (root, file, trail) => {
@@ -9934,7 +10467,7 @@ var snippetCycleMessage = (root, file, trail) => {
9934
10467
  };
9935
10468
  var resolveSnippetPath = (options) => {
9936
10469
  const target = options.source.startsWith("/") ? resolve7(options.root, options.source.slice(1)) : resolve7(dirname11(options.filePath), options.source);
9937
- return isInsideRoot2(options.root, target) ? target : null;
10470
+ return isInsideRoot3(options.root, target) ? target : null;
9938
10471
  };
9939
10472
  var collectImports2 = (source) => [...source.matchAll(MARKDOWN_SNIPPET_IMPORT)].flatMap((match) => {
9940
10473
  const name = match.groups?.name;
@@ -9996,9 +10529,9 @@ var rewriteMintlifyMarkdownSnippets = async (source, options) => {
9996
10529
  throw new Error(snippetCycleMessage(options.root, file, options.trail ?? []));
9997
10530
  }
9998
10531
  seen.add(file);
9999
- const readFile13 = options.readFile ?? readFileFromDisk;
10532
+ const readFile15 = options.readFile ?? readFileFromDisk;
10000
10533
  try {
10001
- const raw = await readFile13(file);
10534
+ const raw = await readFile15(file);
10002
10535
  const content = frontmatter_default(raw).content.trim();
10003
10536
  const transformed = await rewriteMintlifyMarkdownSnippets(content, {
10004
10537
  ...options,
@@ -10039,7 +10572,7 @@ var rewriteMintlifyMarkdownSnippets = async (source, options) => {
10039
10572
  return await inlineAt(0, source);
10040
10573
  };
10041
10574
  var rewriteMintlifySnippetVariables = async (source, options) => {
10042
- const readFile13 = options.readFile ?? readFileFromDisk;
10575
+ const readFile15 = options.readFile ?? readFileFromDisk;
10043
10576
  const inlineImport = async (current, variableImport) => {
10044
10577
  const file = resolveSnippetPath({
10045
10578
  filePath: options.filePath,
@@ -10049,7 +10582,7 @@ var rewriteMintlifySnippetVariables = async (source, options) => {
10049
10582
  if (!file) {
10050
10583
  return current;
10051
10584
  }
10052
- const exports = collectStringExports(frontmatter_default(await readFile13(file)).content);
10585
+ const exports = collectStringExports(frontmatter_default(await readFile15(file)).content);
10053
10586
  let next = current;
10054
10587
  for (const name of variableImport.names) {
10055
10588
  const value = exports.get(name.imported);
@@ -10127,7 +10660,7 @@ var MINTLIFY_SOURCE_IGNORES = [
10127
10660
  "snippets/**"
10128
10661
  ];
10129
10662
  var mintlifySource = (options) => {
10130
- const contentRoot = isAbsolute6(options.root) ? options.root : join18(resolve8(options.projectRoot), options.root);
10663
+ const contentRoot = isAbsolute8(options.root) ? options.root : join22(resolve8(options.projectRoot), options.root);
10131
10664
  const ignore = [...new Set([...options.exclude, ...MINTLIFY_SOURCE_IGNORES])];
10132
10665
  const transform = (raw, file) => transformMintlifyContent(raw, {
10133
10666
  filePath: file,
@@ -10144,7 +10677,7 @@ var mintlifySource = (options) => {
10144
10677
  files.sort();
10145
10678
  const unsupported = new Set;
10146
10679
  const entries = await Promise.all(files.map(async (file) => {
10147
- const result = await transform(await readFile13(file, "utf-8"), file);
10680
+ const result = await transform(await readFile15(file, "utf-8"), file);
10148
10681
  for (const name of result.unsupported) {
10149
10682
  unsupported.add(name);
10150
10683
  }
@@ -10153,21 +10686,21 @@ var mintlifySource = (options) => {
10153
10686
  body: { format: "mdx", text: parsed.content },
10154
10687
  data: parsed.data,
10155
10688
  raw: result.content,
10156
- ref: relative13(contentRoot, file),
10689
+ ref: relative14(contentRoot, file),
10157
10690
  sourcePath: file
10158
10691
  };
10159
10692
  }));
10160
10693
  const diagnostics = unsupported.size > 0 ? [
10161
10694
  {
10162
10695
  code: "BLUME_MINTLIFY_UNSUPPORTED",
10163
- message: `Mintlify components without a Blume equivalent were left as-is: ${[...unsupported].toSorted().join(", ")}. Use the OpenAPI reference for API parameters.`,
10696
+ message: `Mintlify components without a Blume equivalent were left as-is: ${[...unsupported].toSorted().join(", ")}. Replace them by hand or provide a matching component.`,
10164
10697
  severity: "warning"
10165
10698
  }
10166
10699
  ] : [];
10167
10700
  return { diagnostics, entries };
10168
10701
  };
10169
10702
  const validate = () => {
10170
- if (!existsSync13(contentRoot)) {
10703
+ if (!existsSync15(contentRoot)) {
10171
10704
  throw new BlumeError({
10172
10705
  code: "BLUME_CONTENT_ROOT_MISSING",
10173
10706
  file: contentRoot,
@@ -10179,11 +10712,11 @@ var mintlifySource = (options) => {
10179
10712
  };
10180
10713
  const watch = (onChange) => {
10181
10714
  const disposers = [];
10182
- if (existsSync13(contentRoot)) {
10715
+ if (existsSync15(contentRoot)) {
10183
10716
  const watcher = fsWatch2(contentRoot, { recursive: true }, onChange);
10184
10717
  disposers.push(() => watcher.close());
10185
10718
  }
10186
- if (options.configFile && existsSync13(options.configFile)) {
10719
+ if (options.configFile && existsSync15(options.configFile)) {
10187
10720
  const watcher = fsWatch2(options.configFile, onChange);
10188
10721
  disposers.push(() => watcher.close());
10189
10722
  }
@@ -10194,8 +10727,8 @@ var mintlifySource = (options) => {
10194
10727
  };
10195
10728
  };
10196
10729
  const read = async (ref) => {
10197
- const file = join18(contentRoot, ref);
10198
- const result = await transform(await readFile13(file, "utf-8"), file);
10730
+ const file = join22(contentRoot, ref);
10731
+ const result = await transform(await readFile15(file, "utf-8"), file);
10199
10732
  return result.content;
10200
10733
  };
10201
10734
  return {
@@ -10211,11 +10744,12 @@ var mintlifySource = (options) => {
10211
10744
  };
10212
10745
 
10213
10746
  // src/core/sources/notion.ts
10214
- import { join as join20 } from "pathe";
10747
+ import { setTimeout as sleep } from "node:timers/promises";
10748
+ import { join as join24 } from "pathe";
10215
10749
 
10216
10750
  // src/core/sources/assets.ts
10217
- import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
10218
- import { extname as extname6, join as join19 } from "pathe";
10751
+ import { mkdir as mkdir4, writeFile as writeFile6 } from "node:fs/promises";
10752
+ import { extname as extname6, join as join23 } from "pathe";
10219
10753
  var MD_IMAGE = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
10220
10754
  var REMOTE = /^https?:\/\//u;
10221
10755
  var SAFE_EXT = /^\.[a-z0-9]+$/iu;
@@ -10244,7 +10778,7 @@ var materializeAssets = async (markdown, ctx) => {
10244
10778
  const bytes = new Uint8Array(await res.arrayBuffer());
10245
10779
  const file = `${hashText(url)}${extFor(url)}`;
10246
10780
  await mkdir4(ctx.assetsDir, { recursive: true });
10247
- await writeFile5(join19(ctx.assetsDir, file), bytes);
10781
+ await writeFile6(join23(ctx.assetsDir, file), bytes);
10248
10782
  rewrites.set(url, `${ctx.assetsBaseUrl}/${file}`);
10249
10783
  } catch (error) {
10250
10784
  diagnostics.push({
@@ -10279,6 +10813,28 @@ var richToMarkdown = (rich = []) => rich.map((node) => {
10279
10813
  return node.href ? `[${text}](${node.href})` : text;
10280
10814
  }).join("");
10281
10815
  var blockField = (block) => block[block.type]?.rich_text ?? [];
10816
+ var RATE_LIMITED = 429;
10817
+ var MAX_RETRIES = 4;
10818
+ var BASE_DELAY_MS = 500;
10819
+ var SECOND_MS = 1000;
10820
+ var withNotionRetry = async (call) => {
10821
+ let lastError;
10822
+ for (let attempt = 0;attempt <= MAX_RETRIES; attempt += 1) {
10823
+ try {
10824
+ return await call();
10825
+ } catch (error) {
10826
+ lastError = error;
10827
+ const { status } = error;
10828
+ if (status !== RATE_LIMITED || attempt === MAX_RETRIES) {
10829
+ throw error;
10830
+ }
10831
+ const retryAfter = Number(error.headers?.["retry-after"]);
10832
+ const wait = retryAfter > 0 ? retryAfter * SECOND_MS : BASE_DELAY_MS * 2 ** attempt;
10833
+ await sleep(wait);
10834
+ }
10835
+ }
10836
+ throw lastError instanceof Error ? lastError : new Error("Notion request failed after retries.");
10837
+ };
10282
10838
  var collectAll = async (page, cursor, acc = []) => {
10283
10839
  const res = await page(cursor);
10284
10840
  const all = [...acc, ...res.results];
@@ -10338,8 +10894,8 @@ ${text}
10338
10894
  };
10339
10895
  var notionSource = (options, ctx) => {
10340
10896
  const props = options.properties ?? {};
10341
- const cache = snapshotCache(ctx?.cacheDir ?? join20(".blume", "cache", options.name));
10342
- const assetsDir = ctx?.assetsDir ?? join20(".blume", "public", "blume-assets", options.name);
10897
+ const cache = snapshotCache(ctx?.cacheDir ?? join24(".blume", "cache", options.name));
10898
+ const assetsDir = ctx?.assetsDir ?? join24(".blume", "public", "blume-assets", options.name);
10343
10899
  const assetsBaseUrl = ctx?.assetsBaseUrl ?? `/blume-assets/${options.name}`;
10344
10900
  let snapshot = new Map;
10345
10901
  const resolveClient = async () => {
@@ -10358,7 +10914,7 @@ var notionSource = (options, ctx) => {
10358
10914
  }
10359
10915
  return new Client({ auth: options.token ?? process.env.NOTION_TOKEN });
10360
10916
  };
10361
- const childrenOf = (client, blockId) => collectAll((cursor) => client.blocks.children.list({ block_id: blockId, start_cursor: cursor }));
10917
+ const childrenOf = (client, blockId) => collectAll((cursor) => withNotionRetry(() => client.blocks.children.list({ block_id: blockId, start_cursor: cursor })));
10362
10918
  const renderContainer = async (client, block, render) => {
10363
10919
  const children = async (target) => {
10364
10920
  if (!target.has_children) {
@@ -10444,10 +11000,10 @@ ${rendered.join(`
10444
11000
  data.sidebar = { order };
10445
11001
  }
10446
11002
  const slugProp = richToMarkdown(page.properties[props.slug ?? "Slug"]?.rich_text);
10447
- const slug = slugify2(slugProp || title) || page.id;
11003
+ const slug = slugify3(slugProp || title) || page.id;
10448
11004
  return { data, slug };
10449
11005
  };
10450
- const toEntry = async (client, page) => {
11006
+ const toEntry2 = async (client, page) => {
10451
11007
  const { data, slug } = frontmatter(page);
10452
11008
  const mdx = await renderBlocks(client, await childrenOf(client, page.id));
10453
11009
  const assets = await materializeAssets(mdx, {
@@ -10472,11 +11028,11 @@ ${rendered.join(`
10472
11028
  const assetDiagnostics = [];
10473
11029
  const result = await loadWithCache(options.name, cache, async () => {
10474
11030
  const client = await resolveClient();
10475
- const pages = await collectAll((cursor) => client.databases.query({
11031
+ const pages = await collectAll((cursor) => withNotionRetry(() => client.databases.query({
10476
11032
  database_id: options.database,
10477
11033
  start_cursor: cursor
10478
- }));
10479
- const built = await Promise.all(pages.map((page) => toEntry(client, page)));
11034
+ })));
11035
+ const built = await Promise.all(pages.map((page) => toEntry2(client, page)));
10480
11036
  for (const item of built) {
10481
11037
  assetDiagnostics.push(...item.diagnostics);
10482
11038
  }
@@ -10507,7 +11063,7 @@ ${rendered.join(`
10507
11063
  };
10508
11064
 
10509
11065
  // src/core/sources/sanity.ts
10510
- import { join as join21 } from "pathe";
11066
+ import { join as join25 } from "pathe";
10511
11067
 
10512
11068
  // src/core/sources/portable-text.ts
10513
11069
  var HEADING_STYLES = {
@@ -10646,11 +11202,11 @@ var resolveClient = async (options, preview) => {
10646
11202
  };
10647
11203
  var sanitySource = (options, ctx) => {
10648
11204
  const fields = options.fields ?? {};
10649
- const cache = snapshotCache(ctx?.cacheDir ?? join21(".blume", "cache", options.name));
11205
+ const cache = snapshotCache(ctx?.cacheDir ?? join25(".blume", "cache", options.name));
10650
11206
  let snapshot = new Map;
10651
- const toEntry = (doc) => {
11207
+ const toEntry2 = (doc) => {
10652
11208
  const slugValue = asString2(getPath(doc, fields.slug ?? "slug.current")) ?? asString2(doc._id) ?? "untitled";
10653
- const slug = slugify2(slugValue) || "untitled";
11209
+ const slug = slugify3(slugValue) || slugify3(asString2(doc._id) ?? "") || "untitled";
10654
11210
  const data = {};
10655
11211
  const title = asString2(getPath(doc, fields.title ?? "title"));
10656
11212
  const description = asString2(getPath(doc, fields.description ?? "description"));
@@ -10682,7 +11238,7 @@ var sanitySource = (options, ctx) => {
10682
11238
  const result = await loadWithCache(options.name, cache, async () => {
10683
11239
  const client = await resolveClient(options, ctx?.preview ?? false);
10684
11240
  const docs = await client.fetch(options.query);
10685
- return docs.map(toEntry);
11241
+ return docs.map(toEntry2);
10686
11242
  }, ctx?.refresh ?? true);
10687
11243
  snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));
10688
11244
  return result;
@@ -10721,8 +11277,8 @@ var uniqueNamer = () => {
10721
11277
  };
10722
11278
  var sourceContext = (context, name, runtime) => ({
10723
11279
  assetsBaseUrl: `/blume-assets/${name}`,
10724
- assetsDir: join22(context.outDir, "public", "blume-assets", name),
10725
- cacheDir: join22(context.outDir, "cache", name),
11280
+ assetsDir: join26(context.outDir, "public", "blume-assets", name),
11281
+ cacheDir: join26(context.outDir, "cache", name),
10726
11282
  mode: runtime.mode,
10727
11283
  preview: runtime.preview,
10728
11284
  projectRoot: context.root,
@@ -10804,7 +11360,7 @@ var baseName = (def) => {
10804
11360
  }
10805
11361
  return def.prefix ?? def.type;
10806
11362
  };
10807
- var resolveSources = (config, context, runtime) => {
11363
+ var contentSources = (config, context, runtime) => {
10808
11364
  const defs = config.content.sources;
10809
11365
  if (!defs || defs.length === 0) {
10810
11366
  return [
@@ -10820,6 +11376,14 @@ var resolveSources = (config, context, runtime) => {
10820
11376
  const nameFor = uniqueNamer();
10821
11377
  return defs.map((def) => buildSource(def, nameFor(baseName(def)), context, runtime));
10822
11378
  };
11379
+ var resolveSources = (config, context, runtime) => {
11380
+ const sources = contentSources(config, context, runtime);
11381
+ const references = blumeReferences(config);
11382
+ if (references.length > 0) {
11383
+ sources.push(openApiSource(references, sourceContext(context, "openapi", runtime)));
11384
+ }
11385
+ return sources;
11386
+ };
10823
11387
 
10824
11388
  // src/core/project-graph.ts
10825
11389
  var applyConfigOverrides = (config, overrides) => {
@@ -10848,7 +11412,9 @@ var scanProject = async (root, options = {}) => {
10848
11412
  });
10849
11413
  const { bridge } = configResult;
10850
11414
  const config = applyConfigOverrides(configResult.config, options.overrides);
10851
- const context = resolveProjectContext(root, config);
11415
+ const context = resolveProjectContext(root, config, {
11416
+ runtimeDir: options.runtimeDir
11417
+ });
10852
11418
  const sources = resolveSources(config, context, {
10853
11419
  mode,
10854
11420
  preview,
@@ -10916,8 +11482,8 @@ var scanProject = async (root, options = {}) => {
10916
11482
  };
10917
11483
 
10918
11484
  // src/cli/env.ts
10919
- import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
10920
- import { dirname as dirname12, join as join23, resolve as resolve9 } from "pathe";
11485
+ import { existsSync as existsSync16, readFileSync as readFileSync9 } from "node:fs";
11486
+ import { dirname as dirname12, join as join27, resolve as resolve9 } from "pathe";
10921
11487
  var ENV_LINE = /^\s*(?:export\s+)?(?<key>[A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?<value>.*?)\s*$/u;
10922
11488
  var DOUBLE_QUOTED2 = /^"(?<body>[\s\S]*)"$/u;
10923
11489
  var SINGLE_QUOTED = /^'(?<body>[\s\S]*)'$/u;
@@ -10955,8 +11521,8 @@ var applyEnv = (parsed) => {
10955
11521
  };
10956
11522
  var loadFile = (path) => {
10957
11523
  try {
10958
- if (existsSync14(path)) {
10959
- applyEnv(parseEnv(readFileSync8(path, "utf-8")));
11524
+ if (existsSync16(path)) {
11525
+ applyEnv(parseEnv(readFileSync9(path, "utf-8")));
10960
11526
  }
10961
11527
  } catch {}
10962
11528
  };
@@ -10964,10 +11530,10 @@ var loadEnvFiles = (startDir) => {
10964
11530
  let dir = resolve9(startDir);
10965
11531
  let done = false;
10966
11532
  while (!done) {
10967
- loadFile(join23(dir, ".env.local"));
10968
- loadFile(join23(dir, ".env"));
11533
+ loadFile(join27(dir, ".env.local"));
11534
+ loadFile(join27(dir, ".env"));
10969
11535
  const parent = dirname12(dir);
10970
- done = existsSync14(join23(dir, ".git")) || parent === dir;
11536
+ done = existsSync16(join27(dir, ".git")) || parent === dir;
10971
11537
  dir = parent;
10972
11538
  }
10973
11539
  };
@@ -11040,7 +11606,8 @@ var prepareProject = async (options) => {
11040
11606
  mode: options.mode,
11041
11607
  overrides: options.overrides,
11042
11608
  preview: options.preview,
11043
- refresh: options.refresh
11609
+ refresh: options.refresh,
11610
+ runtimeDir: options.runtimeDir
11044
11611
  });
11045
11612
  } catch (error) {
11046
11613
  if (error instanceof BlumeError) {
@@ -11082,29 +11649,38 @@ var prepareProject = async (options) => {
11082
11649
 
11083
11650
  // src/cli/commands/build.ts
11084
11651
  var ADAPTERS = ["vercel", "node", "netlify", "cloudflare"];
11652
+ var validateBudgetFlags = (args) => {
11653
+ for (const flag of ["budget-js", "budget-css"]) {
11654
+ const value = args[flag];
11655
+ if (value !== undefined && !(Number(value) > 0)) {
11656
+ logger.error(`Invalid --${flag} "${value}" (expected a positive number of kB).`);
11657
+ process.exit(1);
11658
+ }
11659
+ }
11660
+ };
11085
11661
  var emitRedirectFiles = async (config, distDir) => {
11086
11662
  const { redirects } = config;
11087
11663
  if (redirects.length === 0 || config.deployment.output !== "static") {
11088
11664
  return;
11089
11665
  }
11090
- await writeFile6(join24(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
11666
+ await writeFile7(join28(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
11091
11667
  const platformFiles = [
11092
11668
  { content: buildNetlifyRedirects(redirects), name: "_redirects" },
11093
11669
  { content: buildVercelConfig(redirects), name: "vercel.json" }
11094
11670
  ];
11095
- await Promise.all(platformFiles.map((file) => existsSync15(join24(distDir, file.name)) ? Promise.resolve() : writeFile6(join24(distDir, file.name), file.content, "utf-8")));
11671
+ await Promise.all(platformFiles.map((file) => existsSync17(join28(distDir, file.name)) ? Promise.resolve() : writeFile7(join28(distDir, file.name), file.content, "utf-8")));
11096
11672
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
11097
11673
  };
11098
11674
  var formatBytes = (bytes) => bytes < 1024 ? `${bytes} B` : `${(bytes / 1024).toFixed(bytes < 1024 * 100 ? 1 : 0)} kB`;
11099
11675
  var astroAssets = async (distDir, ext) => {
11100
- const astroDir = join24(distDir, "_astro");
11101
- if (!existsSync15(astroDir)) {
11676
+ const astroDir = join28(distDir, "_astro");
11677
+ if (!existsSync17(astroDir)) {
11102
11678
  return [];
11103
11679
  }
11104
11680
  const entries = await readdir(astroDir);
11105
11681
  const files = entries.filter((name) => name.endsWith(`.${ext}`));
11106
11682
  const sized = await Promise.all(files.map(async (name) => {
11107
- const info = await stat(join24(astroDir, name));
11683
+ const info = await stat(join28(astroDir, name));
11108
11684
  return { name, size: info.size };
11109
11685
  }));
11110
11686
  return sized.toSorted((a, b) => b.size - a.size);
@@ -11146,6 +11722,58 @@ var enforceBudget = async (distDir, args) => {
11146
11722
  }
11147
11723
  return passed ? "pass" : "fail";
11148
11724
  };
11725
+ var publishBuildArtifacts = async (project, distDir, args) => {
11726
+ if (project.config.search.provider === "pagefind") {
11727
+ logger.start("Building search index");
11728
+ const indexed = await buildSearchIndex(distDir);
11729
+ logger.success(`Indexed ${indexed} page(s) for search`);
11730
+ }
11731
+ await syncSearchProvider(project, {
11732
+ start: (message) => logger.start(message),
11733
+ success: (message) => logger.success(message),
11734
+ warn: (message) => logger.warn(message)
11735
+ });
11736
+ if (project.config.ai.llmsTxt) {
11737
+ const { index, full } = await buildLlmsFiles(project);
11738
+ await Promise.all([
11739
+ writeFile7(join28(distDir, "llms.txt"), index, "utf-8"),
11740
+ writeFile7(join28(distDir, "llms-full.txt"), full, "utf-8")
11741
+ ]);
11742
+ logger.success("Generated llms.txt and llms-full.txt");
11743
+ }
11744
+ const sitemap = buildSitemap(project);
11745
+ if (sitemap && !existsSync17(join28(distDir, "sitemap.xml"))) {
11746
+ await writeFile7(join28(distDir, "sitemap.xml"), sitemap, "utf-8");
11747
+ logger.success("Generated sitemap.xml");
11748
+ }
11749
+ const robots = buildRobots(project);
11750
+ if (robots && !existsSync17(join28(distDir, "robots.txt"))) {
11751
+ await writeFile7(join28(distDir, "robots.txt"), robots, "utf-8");
11752
+ logger.success("Generated robots.txt");
11753
+ }
11754
+ await emitRedirectFiles(project.config, distDir);
11755
+ const { config } = project;
11756
+ const features = serverFeatures(config);
11757
+ logger.box([
11758
+ `Output ${config.deployment.output}`,
11759
+ `Adapter ${config.deployment.adapter ?? "none"}`,
11760
+ `Site ${config.deployment.site ?? "not set"}`,
11761
+ `Search ${config.search.provider}`,
11762
+ `Redirects ${config.redirects.length}`,
11763
+ `Sitemap ${sitemap ? "yes" : "no (set deployment.site)"}`,
11764
+ `Robots ${robots ? "yes" : "no"}`,
11765
+ `LLM files ${config.ai.llmsTxt ? "yes" : "no"}`,
11766
+ `Server features ${features.length > 0 ? features.join(", ") : "none"}`
11767
+ ].join(`
11768
+ `));
11769
+ if (args.analyze) {
11770
+ await reportBundleSizes(distDir);
11771
+ }
11772
+ if (await enforceBudget(distDir, args) === "fail") {
11773
+ process.exit(1);
11774
+ }
11775
+ logger.success(`Built to ${distDir}`);
11776
+ };
11149
11777
  var buildCommand = defineCommand2({
11150
11778
  args: {
11151
11779
  adapter: {
@@ -11168,6 +11796,10 @@ var buildCommand = defineCommand2({
11168
11796
  description: "Fail if total client JavaScript exceeds this many kB.",
11169
11797
  type: "string"
11170
11798
  },
11799
+ isolated: {
11800
+ description: "Build into an isolated .blume-verify runtime (and its own dist) so a running dev server and the real dist/ are untouched. For verifying changes while `blume dev` runs.",
11801
+ type: "boolean"
11802
+ },
11171
11803
  output: {
11172
11804
  description: "Output mode: static | server.",
11173
11805
  type: "string"
@@ -11184,6 +11816,11 @@ var buildCommand = defineCommand2({
11184
11816
  },
11185
11817
  async run({ args }) {
11186
11818
  const root = process.cwd();
11819
+ const runtimeDir = args.isolated ? ".blume-verify" : process.env.BLUME_RUNTIME_DIR;
11820
+ refuseIfDevRunning(root, "building", runtimeDir);
11821
+ if (args.isolated) {
11822
+ await ensureGitignore(root, [".blume-verify/"]);
11823
+ }
11187
11824
  if (args.output && args.output !== "static" && args.output !== "server") {
11188
11825
  logger.error(`Invalid --output "${args.output}" (use static | server).`);
11189
11826
  process.exit(1);
@@ -11192,6 +11829,7 @@ var buildCommand = defineCommand2({
11192
11829
  logger.error(`Invalid --adapter "${args.adapter}" (use ${ADAPTERS.join(" | ")}).`);
11193
11830
  process.exit(1);
11194
11831
  }
11832
+ validateBudgetFlags(args);
11195
11833
  const project = await prepareProject({
11196
11834
  mode: "build",
11197
11835
  overrides: {
@@ -11201,6 +11839,7 @@ var buildCommand = defineCommand2({
11201
11839
  },
11202
11840
  preview: args.preview,
11203
11841
  root,
11842
+ runtimeDir,
11204
11843
  strict: args.strict
11205
11844
  });
11206
11845
  logger.start(`Building ${project.graph.pages.length} page(s) (${project.config.deployment.output} output)`);
@@ -11208,68 +11847,27 @@ var buildCommand = defineCommand2({
11208
11847
  logLevel: "info",
11209
11848
  root: project.context.outDir
11210
11849
  });
11211
- const distDir = join24(root, "dist");
11212
- if (project.config.search.provider === "pagefind") {
11213
- logger.start("Building search index");
11214
- const indexed = await buildSearchIndex(distDir);
11215
- logger.success(`Indexed ${indexed} page(s) for search`);
11216
- }
11217
- await syncSearchProvider(project, {
11218
- start: (message) => logger.start(message),
11219
- success: (message) => logger.success(message),
11220
- warn: (message) => logger.warn(message)
11221
- });
11222
- if (project.config.ai.llmsTxt) {
11223
- const { index, full } = await buildLlmsFiles(project);
11224
- await Promise.all([
11225
- writeFile6(join24(distDir, "llms.txt"), index, "utf-8"),
11226
- writeFile6(join24(distDir, "llms-full.txt"), full, "utf-8")
11227
- ]);
11228
- logger.success("Generated llms.txt and llms-full.txt");
11229
- }
11230
- const sitemap = buildSitemap(project);
11231
- if (sitemap && !existsSync15(join24(distDir, "sitemap.xml"))) {
11232
- await writeFile6(join24(distDir, "sitemap.xml"), sitemap, "utf-8");
11233
- logger.success("Generated sitemap.xml");
11234
- }
11235
- const robots = buildRobots(project);
11236
- if (robots && !existsSync15(join24(distDir, "robots.txt"))) {
11237
- await writeFile6(join24(distDir, "robots.txt"), robots, "utf-8");
11238
- logger.success("Generated robots.txt");
11239
- }
11240
- await emitRedirectFiles(project.config, distDir);
11241
- const { config } = project;
11242
- const features = serverFeatures(config);
11243
- logger.box([
11244
- `Output ${config.deployment.output}`,
11245
- `Adapter ${config.deployment.adapter ?? "none"}`,
11246
- `Site ${config.deployment.site ?? "not set"}`,
11247
- `Search ${config.search.provider}`,
11248
- `Redirects ${config.redirects.length}`,
11249
- `Sitemap ${sitemap ? "yes" : "no (set deployment.site)"}`,
11250
- `Robots ${robots ? "yes" : "no"}`,
11251
- `LLM files ${config.ai.llmsTxt ? "yes" : "no"}`,
11252
- `Server features ${features.length > 0 ? features.join(", ") : "none"}`
11253
- ].join(`
11254
- `));
11255
- if (args.analyze) {
11256
- await reportBundleSizes(distDir);
11257
- }
11258
- if (await enforceBudget(distDir, args) === "fail") {
11259
- process.exit(1);
11850
+ const distDir = project.context.distDir ?? join28(root, "dist");
11851
+ if (runtimeDir) {
11852
+ logger.success(`Isolated build OK — output at ${distDir} (not published).`);
11853
+ return;
11260
11854
  }
11261
- logger.success(`Built to ${distDir}`);
11855
+ await publishBuildArtifacts(project, distDir, args);
11262
11856
  }
11263
11857
  });
11264
11858
 
11265
11859
  // src/cli/commands/check.ts
11266
- import { existsSync as existsSync16 } from "node:fs";
11860
+ import { existsSync as existsSync18 } from "node:fs";
11267
11861
  import { check } from "@astrojs/check";
11268
11862
  import { sync } from "astro";
11269
11863
  import { defineCommand as defineCommand3 } from "citty";
11270
- import { join as join25 } from "pathe";
11864
+ import { join as join29 } from "pathe";
11271
11865
  var checkCommand = defineCommand3({
11272
11866
  args: {
11867
+ isolated: {
11868
+ description: "Type-check in an isolated .blume-verify runtime so a running dev server is untouched. For verifying changes while `blume dev` runs.",
11869
+ type: "boolean"
11870
+ },
11273
11871
  preview: {
11274
11872
  description: "Include drafts and unpublished CMS content.",
11275
11873
  type: "boolean"
@@ -11285,21 +11883,27 @@ var checkCommand = defineCommand3({
11285
11883
  },
11286
11884
  async run({ args }) {
11287
11885
  const root = process.cwd();
11886
+ const runtimeDir = args.isolated ? ".blume-verify" : process.env.BLUME_RUNTIME_DIR;
11887
+ refuseIfDevRunning(root, "checking", runtimeDir);
11888
+ if (args.isolated) {
11889
+ await ensureGitignore(root, [".blume-verify/"]);
11890
+ }
11288
11891
  const project = await prepareProject({
11289
11892
  mode: "build",
11290
11893
  preview: args.preview,
11291
11894
  root,
11895
+ runtimeDir,
11292
11896
  strict: args.strict
11293
11897
  });
11294
11898
  const { outDir } = project.context;
11295
11899
  await sync({ logLevel: "warn", root: outDir });
11296
- const tsconfig = join25(root, "tsconfig.json");
11900
+ const tsconfig = join29(root, "tsconfig.json");
11297
11901
  logger.start(`Type-checking ${project.graph.pages.length} page(s)`);
11298
11902
  const failed = await check({
11299
11903
  minimumFailingSeverity: "error",
11300
11904
  minimumSeverity: "hint",
11301
11905
  root: outDir,
11302
- tsconfig: existsSync16(tsconfig) ? tsconfig : undefined,
11906
+ tsconfig: existsSync18(tsconfig) ? tsconfig : undefined,
11303
11907
  watch: false
11304
11908
  });
11305
11909
  if (failed) {
@@ -11315,6 +11919,9 @@ import { watch } from "node:fs";
11315
11919
  import { dev } from "astro";
11316
11920
  import { defineCommand as defineCommand4 } from "citty";
11317
11921
 
11922
+ // src/astro/static-assets.ts
11923
+ import { extname as extname7, join as join30, relative as relative15, resolve as resolve10, sep } from "pathe";
11924
+
11318
11925
  // src/astro/integration.ts
11319
11926
  var overlayServer = null;
11320
11927
  var overlayChannel = () => overlayServer?.ws ?? overlayServer?.hot;
@@ -11348,6 +11955,20 @@ ${body}`,
11348
11955
  });
11349
11956
  };
11350
11957
 
11958
+ // src/cli/args.ts
11959
+ var MAX_PORT = 65535;
11960
+ var parsePort = (value) => {
11961
+ if (value === undefined) {
11962
+ return;
11963
+ }
11964
+ const port = Number(value);
11965
+ if (!(Number.isInteger(port) && port >= 1 && port <= MAX_PORT)) {
11966
+ logger.error(`Invalid --port "${value}" (expected an integer 1-${MAX_PORT}).`);
11967
+ process.exit(1);
11968
+ }
11969
+ return port;
11970
+ };
11971
+
11351
11972
  // src/cli/commands/dev.ts
11352
11973
  var devCommand = defineCommand4({
11353
11974
  args: {
@@ -11376,7 +11997,8 @@ var devCommand = defineCommand4({
11376
11997
  const root = process.cwd();
11377
11998
  const preview = args.preview ?? false;
11378
11999
  const overrides = args["content-dir"] ? { contentRoot: args["content-dir"] } : undefined;
11379
- const port = args.port ? Number(args.port) : 4321;
12000
+ const explicitPort = parsePort(args.port);
12001
+ const port = explicitPort ?? 4321;
11380
12002
  const devServerUrl = `http://localhost:${port}`;
11381
12003
  const project = await prepareProject({
11382
12004
  devServerUrl,
@@ -11389,13 +12011,15 @@ var devCommand = defineCommand4({
11389
12011
  if (project.bridge) {
11390
12012
  logger.info('Detected docs.json — running in Mintlify bridge mode (no migration). Run "blume migrate mintlify" to convert permanently.');
11391
12013
  }
12014
+ const releaseLock = acquireDevLock(project.context.outDir);
12015
+ process.on("exit", releaseLock);
11392
12016
  const server = await dev({
11393
12017
  logLevel: args.debug ? "debug" : "info",
11394
12018
  root: project.context.outDir,
11395
12019
  server: {
11396
12020
  host: args.host ?? false,
11397
12021
  open: args.open ?? false,
11398
- port: args.port ? Number(args.port) : undefined
12022
+ port: explicitPort
11399
12023
  }
11400
12024
  });
11401
12025
  showBlumeErrorOverlay(project.diagnostics);
@@ -11436,6 +12060,7 @@ var devCommand = defineCommand4({
11436
12060
  for (const dispose of disposers) {
11437
12061
  dispose();
11438
12062
  }
12063
+ releaseLock();
11439
12064
  await server.stop();
11440
12065
  process.exit(0);
11441
12066
  };
@@ -11505,6 +12130,7 @@ var doctorCommand = defineCommand5({
11505
12130
  }
11506
12131
  if (args.json) {
11507
12132
  if (reportDiagnosticsJson(diagnostics, root)) {
12133
+ await flushStdout();
11508
12134
  process.exit(1);
11509
12135
  }
11510
12136
  return;
@@ -11520,15 +12146,19 @@ var doctorCommand = defineCommand5({
11520
12146
  });
11521
12147
 
11522
12148
  // src/cli/commands/eject.ts
11523
- import { readFile as readFile15, writeFile as writeFile8 } from "node:fs/promises";
12149
+ import { readFile as readFile17, writeFile as writeFile9 } from "node:fs/promises";
11524
12150
  import { defineCommand as defineCommand6 } from "citty";
11525
- import { join as join27, relative as relative15 } from "pathe";
12151
+ import { join as join32, relative as relative17 } from "pathe";
11526
12152
 
11527
12153
  // src/registry/eject.ts
11528
- import { existsSync as existsSync17 } from "node:fs";
11529
- import { cp, mkdir as mkdir5, readFile as readFile14, rm as rm2, writeFile as writeFile7 } from "node:fs/promises";
11530
- import { join as join26, relative as relative14 } from "pathe";
12154
+ import { existsSync as existsSync19 } from "node:fs";
12155
+ import { cp, mkdir as mkdir5, readFile as readFile16, rm as rm2, writeFile as writeFile8 } from "node:fs/promises";
12156
+ import { join as join31, relative as relative16 } from "pathe";
11531
12157
  var POSIX = (path) => path.split("\\").join("/");
12158
+ var ejectOpenApiData = (project) => {
12159
+ const source = project.sources.find(isOpenApiSource);
12160
+ return source ? source.openApiData() : {};
12161
+ };
11532
12162
  var askFiles = async (project, srcDir, genDir) => {
11533
12163
  const { ask } = project.config.ai;
11534
12164
  if (!ask?.enabled) {
@@ -11538,14 +12168,14 @@ var askFiles = async (project, srcDir, genDir) => {
11538
12168
  const files = [
11539
12169
  {
11540
12170
  content: askEndpointTemplate(resolveAskBackend(ask), grounded),
11541
- path: join26(srcDir, "pages", "api", "ask.ts")
12171
+ path: join31(srcDir, "pages", "api", "ask.ts")
11542
12172
  }
11543
12173
  ];
11544
12174
  if (grounded) {
11545
12175
  files.push({
11546
12176
  content: `${JSON.stringify(await buildAskData(project))}
11547
12177
  `,
11548
- path: join26(genDir, "ask-data.json")
12178
+ path: join31(genDir, "ask-data.json")
11549
12179
  });
11550
12180
  }
11551
12181
  return files;
@@ -11553,15 +12183,15 @@ var askFiles = async (project, srcDir, genDir) => {
11553
12183
  var eject = async (root) => {
11554
12184
  const project = await scanProject(root, { mode: "build" });
11555
12185
  const { context, config } = project;
11556
- const srcDir = join26(root, "src");
11557
- const genDir = join26(srcDir, "generated");
12186
+ const srcDir = join31(root, "src");
12187
+ const genDir = join31(srcDir, "generated");
11558
12188
  const askEnabled = config.ai.ask?.enabled ?? false;
11559
12189
  const exportPdf = config.export.pdf;
11560
12190
  const exportEpub = config.export.epub;
11561
12191
  const [pages, needsReactRaw, userTheme, rawMarkdown, islands, examples] = await Promise.all([
11562
12192
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
11563
12193
  detectNeedsReact(root),
11564
- context.themeFile ? readFile14(context.themeFile, "utf-8") : Promise.resolve(""),
12194
+ context.themeFile ? readFile16(context.themeFile, "utf-8") : Promise.resolve(""),
11565
12195
  buildRawMarkdown(project),
11566
12196
  discoverIslands(root),
11567
12197
  discoverExamples(root, config.examples)
@@ -11575,13 +12205,13 @@ var eject = async (root) => {
11575
12205
  const needsSvelte = frameworks.has("svelte");
11576
12206
  const relContext = {
11577
12207
  ...context,
11578
- contentRoot: POSIX(relative14(root, context.contentRoot)),
12208
+ contentRoot: POSIX(relative16(root, context.contentRoot)),
11579
12209
  outDir: ".",
11580
12210
  root: "."
11581
12211
  };
11582
- const componentsImport = context.componentsFile ? `../../${POSIX(relative14(root, context.componentsFile))}` : null;
12212
+ const componentsImport = context.componentsFile ? `../../${POSIX(relative16(root, context.componentsFile))}` : null;
11583
12213
  const relPages = pages.map((page) => ({
11584
- entrypoint: POSIX(relative14(root, page.entrypoint)),
12214
+ entrypoint: POSIX(relative16(root, page.entrypoint)),
11585
12215
  pattern: page.pattern
11586
12216
  }));
11587
12217
  const staged = collectStaged(project);
@@ -11598,17 +12228,19 @@ var eject = async (root) => {
11598
12228
  needsReact,
11599
12229
  needsSvelte,
11600
12230
  needsVue,
12231
+ openapiPath: "./src/generated/openapi.json",
11601
12232
  pages: relPages,
11602
12233
  searchClientPath: "./src/generated/search-client.ts",
11603
12234
  themePath: "./src/generated/app.css"
11604
12235
  }),
11605
- path: join26(root, "astro.config.mjs")
12236
+ path: join31(root, "astro.config.mjs")
11606
12237
  },
11607
12238
  {
11608
12239
  content: runtimeTsconfigTemplate(),
11609
- path: join26(root, "tsconfig.json")
12240
+ path: join31(root, "tsconfig.json"),
12241
+ skipIfExists: true
11610
12242
  },
11611
- { content: envTemplate(), path: join26(srcDir, "env.d.ts") },
12243
+ { content: envTemplate(), path: join31(srcDir, "env.d.ts") },
11612
12244
  {
11613
12245
  content: contentConfigTemplate({
11614
12246
  config,
@@ -11616,7 +12248,7 @@ var eject = async (root) => {
11616
12248
  staged: hasStaged,
11617
12249
  stagedBase: stagedDir
11618
12250
  }),
11619
- path: join26(srcDir, "content.config.ts")
12251
+ path: join31(srcDir, "content.config.ts")
11620
12252
  },
11621
12253
  {
11622
12254
  content: catchAllPageTemplate({
@@ -11626,19 +12258,19 @@ var eject = async (root) => {
11626
12258
  mathEnabled: config.markdown.math,
11627
12259
  needsReact
11628
12260
  }),
11629
- path: join26(srcDir, "pages", "[...slug].astro")
12261
+ path: join31(srcDir, "pages", "[...slug].astro")
11630
12262
  },
11631
12263
  {
11632
12264
  content: planComponentSlots(componentsImport, null).module,
11633
- path: join26(genDir, "components.ts")
12265
+ path: join31(genDir, "components.ts")
11634
12266
  },
11635
12267
  {
11636
12268
  content: islandMapTemplate(islands.islands),
11637
- path: join26(genDir, "islands.ts")
12269
+ path: join31(genDir, "islands.ts")
11638
12270
  },
11639
12271
  {
11640
12272
  content: exampleMapTemplate(examples.examples),
11641
- path: join26(genDir, "examples.ts")
12273
+ path: join31(genDir, "examples.ts")
11642
12274
  },
11643
12275
  {
11644
12276
  content: tailwindEntryTemplate({
@@ -11650,21 +12282,26 @@ var eject = async (root) => {
11650
12282
  twoslashCss: twoslashCss(),
11651
12283
  userTheme
11652
12284
  }),
11653
- path: join26(genDir, "app.css")
12285
+ path: join31(genDir, "app.css")
12286
+ },
12287
+ { content: buildRuntimeData(project), path: join31(genDir, "data.json") },
12288
+ {
12289
+ content: `${JSON.stringify(ejectOpenApiData(project))}
12290
+ `,
12291
+ path: join31(genDir, "openapi.json")
11654
12292
  },
11655
- { content: buildRuntimeData(project), path: join26(genDir, "data.json") },
11656
12293
  {
11657
12294
  content: `${JSON.stringify(rawMarkdown)}
11658
12295
  `,
11659
- path: join26(genDir, "raw-markdown.json")
12296
+ path: join31(genDir, "raw-markdown.json")
11660
12297
  },
11661
12298
  {
11662
12299
  content: rawMarkdownEndpointTemplate(),
11663
- path: join26(srcDir, "pages", "[...slug].md.ts")
12300
+ path: join31(srcDir, "pages", "[...slug].md.ts")
11664
12301
  },
11665
12302
  {
11666
12303
  content: rawMarkdownEndpointTemplate(),
11667
- path: join26(srcDir, "pages", "[...slug].mdx.ts")
12304
+ path: join31(srcDir, "pages", "[...slug].mdx.ts")
11668
12305
  }
11669
12306
  ];
11670
12307
  if (askEnabled) {
@@ -11673,34 +12310,34 @@ var eject = async (root) => {
11673
12310
  if (config.seo.og.enabled) {
11674
12311
  files.push({
11675
12312
  content: ogEndpointTemplate(customOgRoutes(pages, config.title)),
11676
- path: join26(srcDir, "pages", "og", "[...slug].png.ts")
12313
+ path: join31(srcDir, "pages", "og", "[...slug].png.ts")
11677
12314
  });
11678
12315
  }
11679
12316
  if (!routeIsTaken(pages, project.graph.pages, "/404")) {
11680
12317
  files.push({
11681
12318
  content: notFoundPageTemplate(),
11682
- path: join26(srcDir, "pages", "404.astro")
12319
+ path: join31(srcDir, "pages", "404.astro")
11683
12320
  });
11684
12321
  }
11685
12322
  files.push({
11686
12323
  content: searchClientTemplate(config),
11687
- path: join26(genDir, "search-client.ts")
12324
+ path: join31(genDir, "search-client.ts")
11688
12325
  });
11689
12326
  if (servesStaticIndex(config.search.provider)) {
11690
12327
  const documents = await buildSearchDocuments(project);
11691
12328
  files.push({
11692
12329
  content: `${JSON.stringify(documents)}
11693
12330
  `,
11694
- path: join26(genDir, "search.json")
12331
+ path: join31(genDir, "search.json")
11695
12332
  }, {
11696
12333
  content: searchEndpointTemplate(),
11697
- path: join26(srcDir, "pages", "blume-search.json.ts")
12334
+ path: join31(srcDir, "pages", "blume-search.json.ts")
11698
12335
  });
11699
12336
  }
11700
12337
  if (config.search.provider === "mixedbread") {
11701
12338
  files.push({
11702
12339
  content: mixedbreadSearchEndpointTemplate(config.search.mixedbread?.storeId ?? ""),
11703
- path: join26(srcDir, "pages", "api", "search.ts")
12340
+ path: join31(srcDir, "pages", "api", "search.ts")
11704
12341
  });
11705
12342
  }
11706
12343
  const feeds = buildRssFeeds(project);
@@ -11709,13 +12346,13 @@ var eject = async (root) => {
11709
12346
  files.push({
11710
12347
  content: `${JSON.stringify(feedXml)}
11711
12348
  `,
11712
- path: join26(genDir, "rss.json")
12349
+ path: join31(genDir, "rss.json")
11713
12350
  }, {
11714
12351
  content: rssEndpointTemplate(),
11715
- path: join26(srcDir, "pages", "[section]", "rss.xml.ts")
12352
+ path: join31(srcDir, "pages", "[section]", "rss.xml.ts")
11716
12353
  });
11717
12354
  }
11718
- if (hasReferences(config)) {
12355
+ if (hasScalarReferences(config)) {
11719
12356
  const references = await buildReferenceFiles({
11720
12357
  config,
11721
12358
  contentRoutes: new Set(project.graph.pages.map((page) => page.route)),
@@ -11724,40 +12361,41 @@ var eject = async (root) => {
11724
12361
  for (const file of references.files) {
11725
12362
  files.push({
11726
12363
  content: file.content,
11727
- path: join26(srcDir, "pages", file.pagePath)
12364
+ path: join31(srcDir, "pages", file.pagePath)
11728
12365
  });
11729
12366
  }
11730
12367
  }
11731
12368
  files.push(...islands.islands.map((island) => ({
11732
12369
  content: islandWrapperTemplate(island),
11733
- path: join26(genDir, "islands", `${island.name}.astro`)
12370
+ path: join31(genDir, "islands", `${island.name}.astro`)
11734
12371
  })), ...examples.examples.map((example) => ({
11735
12372
  content: exampleWrapperTemplate(example),
11736
- path: join26(genDir, "examples", `${exampleSlug(example.path)}.astro`)
12373
+ path: join31(genDir, "examples", `${exampleSlug(example.path)}.astro`)
11737
12374
  })));
11738
12375
  for (const [entryId, content] of staged) {
11739
- files.push({ content, path: join26(root, stagedDir, entryId) });
12376
+ files.push({ content, path: join31(root, stagedDir, entryId) });
11740
12377
  }
11741
- await Promise.all(files.map(async (file) => {
11742
- await mkdir5(join26(file.path, ".."), { recursive: true });
11743
- await writeFile7(file.path, file.content, "utf-8");
12378
+ const written = files.filter((file) => !(file.skipIfExists && existsSync19(file.path)));
12379
+ await Promise.all(written.map(async (file) => {
12380
+ await mkdir5(join31(file.path, ".."), { recursive: true });
12381
+ await writeFile8(file.path, file.content, "utf-8");
11744
12382
  }));
11745
- const assetsSrc = join26(context.outDir, "public", "blume-assets");
11746
- if (existsSync17(assetsSrc)) {
11747
- await cp(assetsSrc, join26(root, "public", "blume-assets"), {
12383
+ const assetsSrc = join31(context.outDir, "public", "blume-assets");
12384
+ if (existsSync19(assetsSrc)) {
12385
+ await cp(assetsSrc, join31(root, "public", "blume-assets"), {
11748
12386
  recursive: true
11749
12387
  });
11750
12388
  }
11751
12389
  await rm2(context.outDir, { force: true, recursive: true });
11752
- return files.map((file) => file.path);
12390
+ return written.map((file) => file.path);
11753
12391
  };
11754
12392
 
11755
12393
  // src/cli/commands/eject.ts
11756
12394
  var updatePackageScripts = async (root) => {
11757
- const pkgPath = join27(root, "package.json");
12395
+ const pkgPath = join32(root, "package.json");
11758
12396
  let pkg;
11759
12397
  try {
11760
- pkg = JSON.parse(await readFile15(pkgPath, "utf-8"));
12398
+ pkg = JSON.parse(await readFile17(pkgPath, "utf-8"));
11761
12399
  } catch {
11762
12400
  return;
11763
12401
  }
@@ -11768,7 +12406,7 @@ var updatePackageScripts = async (root) => {
11768
12406
  dev: "astro dev",
11769
12407
  preview: "astro preview"
11770
12408
  };
11771
- await writeFile8(pkgPath, `${JSON.stringify(pkg, null, 2)}
12409
+ await writeFile9(pkgPath, `${JSON.stringify(pkg, null, 2)}
11772
12410
  `, "utf-8");
11773
12411
  };
11774
12412
  var ejectCommand = defineCommand6({
@@ -11781,8 +12419,9 @@ var ejectCommand = defineCommand6({
11781
12419
  },
11782
12420
  async run({ args }) {
11783
12421
  const root = process.cwd();
12422
+ refuseIfDevRunning(root, "ejecting");
11784
12423
  if (!args.yes) {
11785
- logger.warn("Eject is one-way: it writes astro.config.mjs and src/ into your project and removes .blume.");
12424
+ logger.warn("Eject is one-way: it writes astro.config.mjs, src/, and (if absent) tsconfig.json, rewrites your package.json scripts, and removes .blume. An existing tsconfig.json is left untouched.");
11786
12425
  logger.info("Re-run with --yes to proceed.");
11787
12426
  return;
11788
12427
  }
@@ -11790,7 +12429,7 @@ var ejectCommand = defineCommand6({
11790
12429
  await updatePackageScripts(root);
11791
12430
  logger.success(`Ejected ${files.length} file(s):`);
11792
12431
  for (const file of files) {
11793
- process.stdout.write(` ${relative15(root, file)}
12432
+ process.stdout.write(` ${relative17(root, file)}
11794
12433
  `);
11795
12434
  }
11796
12435
  logger.box(`Your project is now a standalone Astro app.
@@ -11803,10 +12442,10 @@ The blume package remains importable.`);
11803
12442
  });
11804
12443
 
11805
12444
  // src/cli/commands/init.ts
11806
- import { existsSync as existsSync18 } from "node:fs";
11807
- import { mkdir as mkdir6, writeFile as writeFile9 } from "node:fs/promises";
12445
+ import { existsSync as existsSync20 } from "node:fs";
12446
+ import { mkdir as mkdir6, writeFile as writeFile10 } from "node:fs/promises";
11808
12447
  import { defineCommand as defineCommand7 } from "citty";
11809
- import { basename as basename4, dirname as dirname13, join as join28 } from "pathe";
12448
+ import { basename as basename4, dirname as dirname13, isAbsolute as isAbsolute9, join as join33, relative as relative18 } from "pathe";
11810
12449
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
11811
12450
  var packageTemplate = (name, version) => `{
11812
12451
  "name": ${JSON.stringify(name)},
@@ -11854,7 +12493,7 @@ var STARTERS = {
11854
12493
  files: (dir) => [
11855
12494
  {
11856
12495
  content: page("API Reference", "Explore the API.", "# API Reference\n\nYour OpenAPI spec renders at [`/api`](/api). Point `openapi.sources` at your own spec in `blume.config.ts`."),
11857
- path: join28(dir, "index.mdx")
12496
+ path: join33(dir, "index.mdx")
11858
12497
  }
11859
12498
  ]
11860
12499
  },
@@ -11869,7 +12508,7 @@ var STARTERS = {
11869
12508
  files: (dir) => [
11870
12509
  {
11871
12510
  content: page("Introduction", "Welcome to your new Blume docs.", "# Introduction\n\nWrite your docs here, and log releases under `changelog/`."),
11872
- path: join28(dir, "index.mdx")
12511
+ path: join33(dir, "index.mdx")
11873
12512
  },
11874
12513
  {
11875
12514
  content: `---
@@ -11880,7 +12519,7 @@ date: 2026-01-01
11880
12519
 
11881
12520
  The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries beside it.
11882
12521
  `,
11883
- path: join28(dir, "changelog", "v1-0-0.mdx")
12522
+ path: join33(dir, "changelog", "v1-0-0.mdx")
11884
12523
  }
11885
12524
  ]
11886
12525
  },
@@ -11893,7 +12532,7 @@ The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries besid
11893
12532
  Welcome to **Blume** — markdown-first docs powered by Astro and Vite.
11894
12533
 
11895
12534
  Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
11896
- path: join28(dir, "index.mdx")
12535
+ path: join33(dir, "index.mdx")
11897
12536
  }
11898
12537
  ]
11899
12538
  },
@@ -11904,11 +12543,11 @@ Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
11904
12543
  content: page("Introduction", "Get started with the SDK.", `# Introduction
11905
12544
 
11906
12545
  Install the SDK and make your first call. See [Installation](/installation).`),
11907
- path: join28(dir, "index.mdx")
12546
+ path: join33(dir, "index.mdx")
11908
12547
  },
11909
12548
  {
11910
12549
  content: page("Installation", "Install the SDK.", "# Installation\n\n```package-install\nyour-sdk\n```"),
11911
- path: join28(dir, "installation.mdx")
12550
+ path: join33(dir, "installation.mdx")
11912
12551
  }
11913
12552
  ]
11914
12553
  }
@@ -11918,12 +12557,12 @@ var commandsFor = (pm) => ({
11918
12557
  install: `${pm} install`
11919
12558
  });
11920
12559
  var writeFileSafe = async (path, content) => {
11921
- if (existsSync18(path)) {
12560
+ if (existsSync20(path)) {
11922
12561
  logger.info(`Skipped existing ${path}`);
11923
12562
  return false;
11924
12563
  }
11925
12564
  await mkdir6(dirname13(path), { recursive: true });
11926
- await writeFile9(path, content, "utf-8");
12565
+ await writeFile10(path, content, "utf-8");
11927
12566
  logger.success(`Created ${path}`);
11928
12567
  return true;
11929
12568
  };
@@ -11955,6 +12594,10 @@ var initCommand = defineCommand7({
11955
12594
  async run({ args }) {
11956
12595
  const root = process.cwd();
11957
12596
  const contentDir = args["content-dir"] ?? "docs";
12597
+ if (isAbsolute9(contentDir) || relative18(root, join33(root, contentDir)).startsWith("..")) {
12598
+ logger.error(`Invalid --content-dir "${contentDir}" (must be a path inside the project).`);
12599
+ process.exit(1);
12600
+ }
11958
12601
  const template = args.template ?? "docs";
11959
12602
  if (!TEMPLATES.includes(template)) {
11960
12603
  logger.error(`Unknown template "${args.template}" (use ${TEMPLATES.join(" | ")}).`);
@@ -11966,9 +12609,13 @@ var initCommand = defineCommand7({
11966
12609
  process.exit(1);
11967
12610
  }
11968
12611
  const starter = STARTERS[template];
11969
- const createdPackage = await writeFileSafe(join28(root, "package.json"), packageTemplate(toPackageName(basename4(root)), getBlumeVersion()));
11970
- await writeFileSafe(join28(root, "blume.config.ts"), starter.config);
11971
- await Promise.all(starter.files(contentDir).map((file) => writeFileSafe(join28(root, file.path), file.content)));
12612
+ const createdPackage = await writeFileSafe(join33(root, "package.json"), packageTemplate(toPackageName(basename4(root)), getBlumeVersion()));
12613
+ await writeFileSafe(join33(root, "blume.config.ts"), starter.config);
12614
+ await Promise.all(starter.files(contentDir).map((file) => writeFileSafe(join33(root, file.path), file.content)));
12615
+ const ignored = await ensureGitignore(root, [".blume/", "dist/"]);
12616
+ if (ignored.length > 0) {
12617
+ logger.success(`Added ${ignored.join(", ")} to .gitignore`);
12618
+ }
11972
12619
  const commands = commandsFor(pm);
11973
12620
  if (args.eject) {
11974
12621
  try {
@@ -12005,15 +12652,15 @@ var initCommand = defineCommand7({
12005
12652
  import { defineCommand as defineCommand8 } from "citty";
12006
12653
 
12007
12654
  // src/migrate/fumadocs/index.ts
12008
- import { existsSync as existsSync22 } from "node:fs";
12009
- import { mkdir as mkdir8, readFile as readFile17, rename as rename3, rm as rm3, writeFile as writeFile11 } from "node:fs/promises";
12010
- import { dirname as dirname16, join as join31, relative as relative16 } from "pathe";
12655
+ import { existsSync as existsSync24 } from "node:fs";
12656
+ import { mkdir as mkdir8, readFile as readFile19, rename as rename3, rm as rm3, writeFile as writeFile12 } from "node:fs/promises";
12657
+ import { dirname as dirname16, join as join36, relative as relative19 } from "pathe";
12011
12658
  import { glob as glob8 } from "tinyglobby";
12012
12659
 
12013
12660
  // src/migrate/fumadocs/config.ts
12014
- import { existsSync as existsSync19 } from "node:fs";
12015
- import { readFile as readFile16 } from "node:fs/promises";
12016
- import { basename as basename5, dirname as dirname14, join as join29 } from "pathe";
12661
+ import { existsSync as existsSync21 } from "node:fs";
12662
+ import { readFile as readFile18 } from "node:fs/promises";
12663
+ import { basename as basename5, dirname as dirname14, join as join34 } from "pathe";
12017
12664
  var SOURCE_FILES = [
12018
12665
  "lib/source.ts",
12019
12666
  "app/source.ts",
@@ -12043,7 +12690,7 @@ var GENERIC_NAMES = new Set([
12043
12690
  var gitRepoRoot = (start) => {
12044
12691
  let dir = start;
12045
12692
  for (;; ) {
12046
- if (existsSync19(join29(dir, ".git"))) {
12693
+ if (existsSync21(join34(dir, ".git"))) {
12047
12694
  return dir;
12048
12695
  }
12049
12696
  const parent = dirname14(dir);
@@ -12055,12 +12702,12 @@ var gitRepoRoot = (start) => {
12055
12702
  };
12056
12703
  var bareName = (name) => name.includes("/") ? name.slice(name.lastIndexOf("/") + 1) : name;
12057
12704
  var readTitle = async (root) => {
12058
- const packageJson = join29(root, "package.json");
12059
- if (!existsSync19(packageJson)) {
12705
+ const packageJson = join34(root, "package.json");
12706
+ if (!existsSync21(packageJson)) {
12060
12707
  return "Documentation";
12061
12708
  }
12062
12709
  try {
12063
- const parsed = JSON.parse(await readFile16(packageJson, "utf-8"));
12710
+ const parsed = JSON.parse(await readFile18(packageJson, "utf-8"));
12064
12711
  const { name } = parsed;
12065
12712
  if (typeof name !== "string" || !name.trim()) {
12066
12713
  return "Documentation";
@@ -12081,11 +12728,11 @@ var readTitle = async (root) => {
12081
12728
  };
12082
12729
  var scrapeBaseUrl = async (root) => {
12083
12730
  for (const candidate of SOURCE_FILES) {
12084
- const file = join29(root, candidate);
12085
- if (!existsSync19(file)) {
12731
+ const file = join34(root, candidate);
12732
+ if (!existsSync21(file)) {
12086
12733
  continue;
12087
12734
  }
12088
- const base = BASE_URL.exec(await readFile16(file, "utf-8"))?.groups?.base;
12735
+ const base = BASE_URL.exec(await readFile18(file, "utf-8"))?.groups?.base;
12089
12736
  if (base) {
12090
12737
  return base;
12091
12738
  }
@@ -12114,9 +12761,9 @@ var loadFumadocsConfig = async (root) => {
12114
12761
  };
12115
12762
 
12116
12763
  // src/migrate/fumadocs/content.ts
12117
- import { existsSync as existsSync20 } from "node:fs";
12764
+ import { existsSync as existsSync22 } from "node:fs";
12118
12765
  import { readFile as readFileFromDisk2 } from "node:fs/promises";
12119
- import { dirname as dirname15, resolve as resolve10 } from "pathe";
12766
+ import { dirname as dirname15, resolve as resolve11 } from "pathe";
12120
12767
  var FUMADOCS_IMPORT = /^import\s+[\s\S]*?\s+from\s+["']fumadocs-(?:ui|core|mdx)(?:\/[^"']*)?["'];?[ \t]*\n?/gmu;
12121
12768
  var stripFumadocsImports = (source) => {
12122
12769
  const stripped = source.replace(FUMADOCS_IMPORT, "");
@@ -12317,12 +12964,16 @@ var inlineFumadocsIncludes = async (source, options) => {
12317
12964
  if (!rawPath) {
12318
12965
  continue;
12319
12966
  }
12320
- const target = resolve10(dirname15(options.filePath), rawPath);
12967
+ const target = resolve11(dirname15(options.filePath), rawPath);
12968
+ if (!isInsideRoot2(options.root, target)) {
12969
+ warnings.push(`<include> target "${rawPath}" is outside the docs tree — left as-is.`);
12970
+ continue;
12971
+ }
12321
12972
  if (seen.has(target)) {
12322
12973
  warnings.push(`Circular <include> "${rawPath}" — left as-is.`);
12323
12974
  continue;
12324
12975
  }
12325
- if (!existsSync20(target)) {
12976
+ if (!existsSync22(target)) {
12326
12977
  warnings.push(`<include> target "${rawPath}" not found — left as-is.`);
12327
12978
  continue;
12328
12979
  }
@@ -12354,9 +13005,9 @@ var normalizeFumadocsPageMeta = (value) => {
12354
13005
  };
12355
13006
 
12356
13007
  // src/migrate/fumadocs/groups.ts
12357
- import { existsSync as existsSync21, statSync as statSync2 } from "node:fs";
12358
- import { mkdir as mkdir7, rename as rename2, writeFile as writeFile10 } from "node:fs/promises";
12359
- import { basename as basename6, join as join30 } from "pathe";
13008
+ import { existsSync as existsSync23, statSync as statSync2 } from "node:fs";
13009
+ import { mkdir as mkdir7, rename as rename2, writeFile as writeFile11 } from "node:fs/promises";
13010
+ import { basename as basename6, join as join35 } from "pathe";
12360
13011
 
12361
13012
  // src/migrate/fumadocs/meta.ts
12362
13013
  var SEPARATOR = /^---(?<label>.*)---$/u;
@@ -12501,13 +13152,16 @@ var isDirectory = (path) => {
12501
13152
  }
12502
13153
  };
12503
13154
  var resolveEntry = (docsDir, name) => {
13155
+ if (!isInsideRoot2(docsDir, join35(docsDir, name))) {
13156
+ return null;
13157
+ }
12504
13158
  for (const ext of PAGE_EXTS) {
12505
- const file = join30(docsDir, `${name}${ext}`);
12506
- if (existsSync21(file)) {
13159
+ const file = join35(docsDir, `${name}${ext}`);
13160
+ if (existsSync23(file)) {
12507
13161
  return { kind: "file", path: file };
12508
13162
  }
12509
13163
  }
12510
- const folder = join30(docsDir, name);
13164
+ const folder = join35(docsDir, name);
12511
13165
  return isDirectory(folder) ? { kind: "folder", path: folder } : null;
12512
13166
  };
12513
13167
  var humanize2 = (name) => name.split(WORD_SPLIT3).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -12542,8 +13196,8 @@ var moveItemIntoGroup = async (item, docsDir, groupDir, label, warnings) => {
12542
13196
  warnings.push(`Sidebar entry "${item.name}" in section "${label}" matched no page or folder; skipped.`);
12543
13197
  return null;
12544
13198
  }
12545
- const dest = join30(groupDir, basename6(resolved.path));
12546
- if (existsSync21(dest)) {
13199
+ const dest = join35(groupDir, basename6(resolved.path));
13200
+ if (existsSync23(dest)) {
12547
13201
  warnings.push(`Skipped moving "${item.name}" into section "${label}" (target already exists).`);
12548
13202
  return null;
12549
13203
  }
@@ -12570,7 +13224,7 @@ var reshapeSection = async (section, docsDir, order, warnings) => {
12570
13224
  }
12571
13225
  return;
12572
13226
  }
12573
- const groupDir = join30(docsDir, `(${section.label})`);
13227
+ const groupDir = join35(docsDir, `(${section.label})`);
12574
13228
  const sectionKeys = [];
12575
13229
  for (const item of section.items) {
12576
13230
  const key = await moveItemIntoGroup(item, docsDir, groupDir, section.label, warnings);
@@ -12583,7 +13237,7 @@ var reshapeSection = async (section, docsDir, order, warnings) => {
12583
13237
  }
12584
13238
  order.push(section.label);
12585
13239
  if (sectionKeys.length > 1) {
12586
- await writeFile10(join30(groupDir, "meta.ts"), renderMetaModule({ pages: sectionKeys }), "utf-8");
13240
+ await writeFile11(join35(groupDir, "meta.ts"), renderMetaModule({ pages: sectionKeys }), "utf-8");
12587
13241
  }
12588
13242
  };
12589
13243
  var reshapeFumadocsGroups = async (structure, docsDir) => {
@@ -12615,9 +13269,9 @@ var FUMADOCS_LEFTOVERS = [
12615
13269
  "app"
12616
13270
  ];
12617
13271
  var movePage = async (abs, base, root) => {
12618
- const rel = relative16(base, abs);
12619
- const dest = join31(root, "docs", rel);
12620
- if (existsSync22(dest)) {
13272
+ const rel = relative19(base, abs);
13273
+ const dest = join36(root, "docs", rel);
13274
+ if (existsSync24(dest)) {
12621
13275
  return {
12622
13276
  includeWarnings: [],
12623
13277
  moved: 0,
@@ -12626,8 +13280,11 @@ var movePage = async (abs, base, root) => {
12626
13280
  unsupported: []
12627
13281
  };
12628
13282
  }
12629
- const raw = await readFile17(abs, "utf-8");
12630
- const included = await inlineFumadocsIncludes(raw, { filePath: abs });
13283
+ const raw = await readFile19(abs, "utf-8");
13284
+ const included = await inlineFumadocsIncludes(raw, {
13285
+ filePath: abs,
13286
+ root: base
13287
+ });
12631
13288
  let text = stripFumadocsImports(included.content);
12632
13289
  text = rewriteFumadocsCallouts(text);
12633
13290
  text = rewriteFumadocsContainers(text);
@@ -12637,7 +13294,7 @@ var movePage = async (abs, base, root) => {
12637
13294
  const { data, removed } = normalizeFumadocsPageMeta(parsed.data);
12638
13295
  const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
12639
13296
  await mkdir8(dirname16(dest), { recursive: true });
12640
- await writeFile11(dest, content, "utf-8");
13297
+ await writeFile12(dest, content, "utf-8");
12641
13298
  await rm3(abs, { force: true });
12642
13299
  return {
12643
13300
  includeWarnings: included.warnings,
@@ -12651,22 +13308,22 @@ var writeMeta = async (dest, meta, rel, warnings) => {
12651
13308
  if (Object.keys(meta).length === 0) {
12652
13309
  return warnings;
12653
13310
  }
12654
- if (existsSync22(dest)) {
13311
+ if (existsSync24(dest)) {
12655
13312
  return [...warnings, `Skipped ${rel} (target already exists)`];
12656
13313
  }
12657
13314
  await mkdir8(dirname16(dest), { recursive: true });
12658
- await writeFile11(dest, renderMetaModule(meta), "utf-8");
13315
+ await writeFile12(dest, renderMetaModule(meta), "utf-8");
12659
13316
  return warnings;
12660
13317
  };
12661
13318
  var convertMeta = async (abs, base, root) => {
12662
- const rel = relative16(base, abs);
12663
- const raw = await readFile17(abs, "utf-8");
13319
+ const rel = relative19(base, abs);
13320
+ const raw = await readFile19(abs, "utf-8");
12664
13321
  let parsed;
12665
13322
  try {
12666
13323
  parsed = JSON.parse(raw);
12667
13324
  } catch {
12668
- const dest2 = join31(root, "docs", rel);
12669
- if (existsSync22(dest2)) {
13325
+ const dest2 = join36(root, "docs", rel);
13326
+ if (existsSync24(dest2)) {
12670
13327
  return [`Skipped ${rel} (target already exists)`];
12671
13328
  }
12672
13329
  await mkdir8(dirname16(dest2), { recursive: true });
@@ -12676,10 +13333,10 @@ var convertMeta = async (abs, base, root) => {
12676
13333
  ];
12677
13334
  }
12678
13335
  const dir = dirname16(rel) === "." ? "" : dirname16(rel);
12679
- const docsDir = join31(root, "docs", dir);
12680
- const dest = join31(docsDir, "meta.ts");
13336
+ const docsDir = join36(root, "docs", dir);
13337
+ const dest = join36(docsDir, "meta.ts");
12681
13338
  const structure = parseFumadocsPages(parsed.pages);
12682
- if (structure.hasSections && !existsSync22(dest)) {
13339
+ if (structure.hasSections && !existsSync24(dest)) {
12683
13340
  const self = translateFumadocsSelfMeta(parsed);
12684
13341
  const reshape = await reshapeFumadocsGroups(structure, docsDir);
12685
13342
  const meta2 = { ...self.meta };
@@ -12726,8 +13383,8 @@ var summarizePages = (results) => {
12726
13383
  };
12727
13384
  };
12728
13385
  var cleanupSourceDirs = async (root) => {
12729
- const docs = join31(root, "content", "docs");
12730
- if (existsSync22(docs)) {
13386
+ const docs = join36(root, "content", "docs");
13387
+ if (existsSync24(docs)) {
12731
13388
  const remaining = await glob8(["**/*"], { cwd: docs, dot: true });
12732
13389
  if (remaining.length > 0) {
12733
13390
  return [
@@ -12736,8 +13393,8 @@ var cleanupSourceDirs = async (root) => {
12736
13393
  }
12737
13394
  await rm3(docs, { force: true, recursive: true });
12738
13395
  }
12739
- const content = join31(root, "content");
12740
- if (existsSync22(content)) {
13396
+ const content = join36(root, "content");
13397
+ if (existsSync24(content)) {
12741
13398
  const remaining = await glob8(["**/*"], { cwd: content, dot: true });
12742
13399
  if (remaining.length === 0) {
12743
13400
  await rm3(content, { force: true, recursive: true });
@@ -12747,8 +13404,8 @@ var cleanupSourceDirs = async (root) => {
12747
13404
  };
12748
13405
  var migrateFumadocsProject = async (root) => {
12749
13406
  const { config, warnings: configWarnings } = await loadFumadocsConfig(root);
12750
- const base = join31(root, SOURCE_DIR);
12751
- if (!existsSync22(base)) {
13407
+ const base = join36(root, SOURCE_DIR);
13408
+ if (!existsSync24(base)) {
12752
13409
  await writeBlumeConfig(root, config);
12753
13410
  return {
12754
13411
  moved: 0,
@@ -12806,10 +13463,33 @@ var migrateFumadocsProject = async (root) => {
12806
13463
  };
12807
13464
 
12808
13465
  // src/migrate/mintlify/index.ts
12809
- import { existsSync as existsSync23 } from "node:fs";
12810
- import { mkdir as mkdir9, readFile as readFile18, rename as rename4, rm as rm4, writeFile as writeFile12 } from "node:fs/promises";
12811
- import { dirname as dirname17, join as join32 } from "pathe";
13466
+ import { existsSync as existsSync25 } from "node:fs";
13467
+ import { mkdir as mkdir9, readFile as readFile20, rename as rename4, rm as rm4, stat as stat2, writeFile as writeFile13 } from "node:fs/promises";
13468
+ import { dirname as dirname17, join as join37 } from "pathe";
12812
13469
  import { glob as glob9 } from "tinyglobby";
13470
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
13471
+ var hasFontFamily = (value) => {
13472
+ const object = asRecord(value);
13473
+ if (!object) {
13474
+ return false;
13475
+ }
13476
+ const named = (child) => typeof asRecord(child)?.family === "string";
13477
+ return typeof object.family === "string" || named(object.heading) || named(object.body);
13478
+ };
13479
+ var droppedChromeWarnings = (spec, config) => {
13480
+ const warnings = [];
13481
+ const navbar = asRecord(spec.navbar);
13482
+ if (navbar && (navbar.links || navbar.primary)) {
13483
+ warnings.push("Header links (navbar.links/navbar.primary) have no blume.config equivalent and were dropped; re-add them with navigation.tabs or a Header layout override.");
13484
+ }
13485
+ if (asRecord(spec.footer)?.socials) {
13486
+ warnings.push("Footer social links (footer.socials) have no blume.config equivalent and were dropped; add them with a Footer layout override.");
13487
+ }
13488
+ if (hasFontFamily(spec.fonts ?? spec.font) && !config.theme?.fonts) {
13489
+ warnings.push("docs.json font family isn't in Blume's curated Google Fonts set; set theme.fonts to a supported slug or add @font-face rules in theme.css.");
13490
+ }
13491
+ return warnings;
13492
+ };
12813
13493
  var prune = (value) => {
12814
13494
  if (Array.isArray(value)) {
12815
13495
  return value.map(prune);
@@ -12838,38 +13518,48 @@ var writeBlumeConfig2 = async (root, config) => {
12838
13518
 
12839
13519
  export default defineConfig(${JSON.stringify(prune(config), null, 2)});
12840
13520
  `;
12841
- await writeFile12(join32(root, "blume.config.ts"), body, "utf-8");
13521
+ await writeFile13(join37(root, "blume.config.ts"), body, "utf-8");
12842
13522
  };
12843
- var relocateAssets = async (root, refs) => {
12844
- const segments = new Set;
12845
- for (const ref of refs) {
12846
- if (typeof ref !== "string" || !ref.startsWith("/")) {
12847
- continue;
12848
- }
12849
- const [segment] = ref.replace(/^\/+/u, "").split("/");
12850
- if (segment) {
12851
- segments.add(segment);
12852
- }
12853
- }
13523
+ var relocateAssets = async (root, segments) => {
13524
+ const served = [];
12854
13525
  const moved = [];
12855
13526
  for (const segment of segments) {
12856
- const source = join32(root, segment);
12857
- if (!existsSync23(source) || segment === "public") {
13527
+ const source = join37(root, segment);
13528
+ if (!existsSync25(source) || segment === "public") {
12858
13529
  continue;
12859
13530
  }
12860
- const dest = join32(root, "public", segment);
12861
- if (existsSync23(dest)) {
13531
+ const stats = await stat2(source);
13532
+ if (stats.isDirectory()) {
13533
+ served.push(segment);
12862
13534
  continue;
12863
13535
  }
12864
- await mkdir9(join32(root, "public"), { recursive: true });
13536
+ const dest = join37(root, "public", segment);
13537
+ if (existsSync25(dest)) {
13538
+ continue;
13539
+ }
13540
+ await mkdir9(join37(root, "public"), { recursive: true });
12865
13541
  await rename4(source, dest);
12866
13542
  moved.push(segment);
12867
13543
  }
12868
- return moved;
13544
+ return { moved, served };
13545
+ };
13546
+ var applyRelocatedAssets = (config, assets, warnings) => {
13547
+ if (assets.served.length > 0) {
13548
+ config.content = {
13549
+ ...config.content,
13550
+ assets: [
13551
+ ...new Set([...config.content?.assets ?? [], ...assets.served])
13552
+ ]
13553
+ };
13554
+ warnings.push(`Kept asset dir(s) in place, served via content.assets: ${assets.served.join(", ")}.`);
13555
+ }
13556
+ if (assets.moved.length > 0) {
13557
+ warnings.push(`Moved assets into public/: ${assets.moved.join(", ")}.`);
13558
+ }
12869
13559
  };
12870
13560
  var cleanupSnippets = async (root, kept, warnings) => {
12871
- const dir = join32(root, "snippets");
12872
- if (!existsSync23(dir)) {
13561
+ const dir = join37(root, "snippets");
13562
+ if (!existsSync25(dir)) {
12873
13563
  return;
12874
13564
  }
12875
13565
  const markdown = await glob9(["**/*.{md,mdx}"], { absolute: true, cwd: dir });
@@ -12887,30 +13577,13 @@ var cleanupSnippets = async (root, kept, warnings) => {
12887
13577
  warnings.push(`Rewrote ${kept.size} component snippet import(s) to relative paths; verify they resolve.`);
12888
13578
  }
12889
13579
  };
12890
- var assetRefs = (config) => {
12891
- const refs = ["/images"];
12892
- const logo = config.logo;
12893
- if (typeof logo === "string") {
12894
- refs.push(logo);
12895
- } else if (logo) {
12896
- refs.push(logo.light, logo.dark);
12897
- }
12898
- const favicon = config.favicon;
12899
- if (typeof favicon === "string") {
12900
- refs.push(favicon);
12901
- } else if (favicon) {
12902
- refs.push(favicon.light, favicon.dark);
12903
- }
12904
- refs.push(config.theme?.backgroundImage, config.theme?.backgroundImageDark);
12905
- return refs;
12906
- };
12907
13580
  var migrateMintlifyProject = async (root) => {
12908
13581
  const warnings = [];
12909
- const configFile = existsSync23(join32(root, "docs.json")) ? join32(root, "docs.json") : join32(root, "mint.json");
13582
+ const configFile = existsSync25(join37(root, "docs.json")) ? join37(root, "docs.json") : join37(root, "mint.json");
12910
13583
  let config;
12911
- if (existsSync23(configFile)) {
13584
+ if (existsSync25(configFile)) {
12912
13585
  config = await loadMintlifyConfig(root, configFile);
12913
- const spec = JSON.parse(await readFile18(configFile, "utf-8"));
13586
+ const spec = JSON.parse(await readFile20(configFile, "utf-8"));
12914
13587
  const i18n = mintlifyI18n(spec);
12915
13588
  if (i18n) {
12916
13589
  config.i18n = i18n;
@@ -12919,6 +13592,11 @@ var migrateMintlifyProject = async (root) => {
12919
13592
  }
12920
13593
  warnings.push(`Mapped ${i18n.locales.length} languages to i18n.locales (default: ${i18n.defaultLocale}); review the locale labels.`);
12921
13594
  }
13595
+ const openapiSources = config.openapi?.sources ?? [];
13596
+ if (openapiSources.length > 0) {
13597
+ warnings.push(`Mapped ${openapiSources.length} OpenAPI spec source(s) to openapi.sources (native reference renderer); verify each spec path or URL resolves.`);
13598
+ }
13599
+ warnings.push(...droppedChromeWarnings(spec, config));
12922
13600
  } else {
12923
13601
  warnings.push("No docs.json or mint.json found; writing a default config.");
12924
13602
  config = { content: { root: "." }, title: "Documentation" };
@@ -12941,7 +13619,7 @@ var migrateMintlifyProject = async (root) => {
12941
13619
  const unsupported = new Set;
12942
13620
  const keptComponents = new Set;
12943
13621
  for (const file of files) {
12944
- const raw = await readFile18(file, "utf-8");
13622
+ const raw = await readFile20(file, "utf-8");
12945
13623
  const result = await transformMintlifyContent(raw, {
12946
13624
  filePath: file,
12947
13625
  root,
@@ -12949,7 +13627,7 @@ var migrateMintlifyProject = async (root) => {
12949
13627
  });
12950
13628
  if (result.content !== raw) {
12951
13629
  await mkdir9(dirname17(file), { recursive: true });
12952
- await writeFile12(file, result.content, "utf-8");
13630
+ await writeFile13(file, result.content, "utf-8");
12953
13631
  }
12954
13632
  for (const key of result.removed) {
12955
13633
  removedKeys.add(key);
@@ -12962,32 +13640,30 @@ var migrateMintlifyProject = async (root) => {
12962
13640
  }
12963
13641
  moved += 1;
12964
13642
  }
12965
- const movedAssets = await relocateAssets(root, assetRefs(config));
13643
+ const assets = await relocateAssets(root, assetSegments(config));
12966
13644
  await cleanupSnippets(root, keptComponents, warnings);
12967
13645
  if (config.content?.exclude) {
12968
13646
  config.content.exclude = [...new Set(config.content.exclude)];
12969
13647
  }
13648
+ applyRelocatedAssets(config, assets, warnings);
12970
13649
  await writeBlumeConfig2(root, config);
12971
13650
  if (Object.keys(variables).length > 0) {
12972
13651
  warnings.push(`Inlined ${Object.keys(variables).length} docs.json variable(s) into content; Blume has no runtime variable substitution.`);
12973
13652
  }
12974
- if (movedAssets.length > 0) {
12975
- warnings.push(`Moved assets into public/: ${movedAssets.join(", ")}.`);
12976
- }
12977
13653
  if (removedKeys.size > 0) {
12978
13654
  warnings.push(`Dropped unsupported page frontmatter keys: ${[...removedKeys].join(", ")}.`);
12979
13655
  }
12980
13656
  if (unsupported.size > 0) {
12981
- warnings.push(`Components without a Blume equivalent need manual review (use the OpenAPI reference instead): ${[...unsupported].join(", ")}.`);
13657
+ warnings.push(`Components without a Blume equivalent need manual review: ${[...unsupported].join(", ")}.`);
12982
13658
  }
12983
13659
  warnings.push("Review blume.config.ts; navigation, theme, and chrome were mapped from docs.json.");
12984
13660
  return { moved, warnings };
12985
13661
  };
12986
13662
 
12987
13663
  // src/migrate/nextra/index.ts
12988
- import { existsSync as existsSync24 } from "node:fs";
12989
- import { mkdir as mkdir10, readFile as readFile19, rename as rename5, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
12990
- import { basename as basename7, dirname as dirname18, extname as extname7, join as join33, relative as relative17 } from "pathe";
13664
+ import { existsSync as existsSync26 } from "node:fs";
13665
+ import { mkdir as mkdir10, readFile as readFile21, rename as rename5, rm as rm5, writeFile as writeFile14 } from "node:fs/promises";
13666
+ import { basename as basename7, dirname as dirname18, extname as extname8, join as join38, relative as relative20 } from "pathe";
12991
13667
  import { glob as glob10 } from "tinyglobby";
12992
13668
 
12993
13669
  // src/migrate/nextra/content.ts
@@ -13222,7 +13898,7 @@ var indexFolders = (base, pageFiles, metaFiles) => {
13222
13898
  }
13223
13899
  };
13224
13900
  for (const abs of pageFiles) {
13225
- const rel = relative17(base, abs);
13901
+ const rel = relative20(base, abs);
13226
13902
  const dir = normalizeDir(dirname18(rel));
13227
13903
  const slug = basename7(rel).replace(/\.mdx?$/u, "");
13228
13904
  const folder = pagesByDir.get(dir) ?? new Map;
@@ -13231,7 +13907,7 @@ var indexFolders = (base, pageFiles, metaFiles) => {
13231
13907
  registerDir(dir);
13232
13908
  }
13233
13909
  for (const abs of metaFiles) {
13234
- registerDir(normalizeDir(dirname18(relative17(base, abs))));
13910
+ registerDir(normalizeDir(dirname18(relative20(base, abs))));
13235
13911
  }
13236
13912
  const childDirs = new Map;
13237
13913
  for (const dir of allDirs) {
@@ -13269,7 +13945,7 @@ var planMetas = (metas, index) => {
13269
13945
  plan.metaByDir.set(dir, conversion.folderMeta);
13270
13946
  plan.consumedMetas.push(meta.abs);
13271
13947
  for (const [slug, title] of Object.entries(conversion.folderTitles)) {
13272
- plan.folderTitleByDir.set(normalizeDir(join33(dir, slug)), title);
13948
+ plan.folderTitleByDir.set(normalizeDir(join38(dir, slug)), title);
13273
13949
  }
13274
13950
  for (const [slug, label] of Object.entries(conversion.pageLabels)) {
13275
13951
  const pageAbs = index.pagesByDir.get(dir)?.get(slug);
@@ -13293,19 +13969,19 @@ var planMetas = (metas, index) => {
13293
13969
  return plan;
13294
13970
  };
13295
13971
  var movePage2 = async (abs, options) => {
13296
- const rel = relative17(options.base, abs);
13297
- const dest = join33(options.root, "docs", rel);
13298
- if (existsSync24(dest)) {
13972
+ const rel = relative20(options.base, abs);
13973
+ const dest = join38(options.root, "docs", rel);
13974
+ if (existsSync26(dest)) {
13299
13975
  return { moved: 0, removed: [], skipped: rel, unsupported: [] };
13300
13976
  }
13301
- const raw = await readFile19(abs, "utf-8");
13977
+ const raw = await readFile21(abs, "utf-8");
13302
13978
  const text = rewriteNextraCallouts(stripNextraImports(raw));
13303
13979
  const unsupported = unsupportedNextraComponents(text);
13304
13980
  const parsed = frontmatter_default(text);
13305
13981
  const { data, removed } = normalizeNextraPageMeta(parsed.data, options.overrides.get(abs) ?? {});
13306
13982
  const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
13307
13983
  await mkdir10(dirname18(dest), { recursive: true });
13308
- await writeFile13(dest, content, "utf-8");
13984
+ await writeFile14(dest, content, "utf-8");
13309
13985
  await rm5(abs, { force: true });
13310
13986
  return { moved: 1, removed, skipped: null, unsupported };
13311
13987
  };
@@ -13347,9 +14023,9 @@ var writeFolderMetas = async (root, plan) => {
13347
14023
  if (Object.keys(finalMeta).length === 0) {
13348
14024
  return;
13349
14025
  }
13350
- const dest = join33(root, "docs", dir, "meta.ts");
14026
+ const dest = join38(root, "docs", dir, "meta.ts");
13351
14027
  await mkdir10(dirname18(dest), { recursive: true });
13352
- await writeFile13(dest, `import { defineMeta } from "blume";
14028
+ await writeFile14(dest, `import { defineMeta } from "blume";
13353
14029
 
13354
14030
  export default defineMeta(${JSON.stringify(finalMeta, null, 2)});
13355
14031
  `, "utf-8");
@@ -13358,8 +14034,8 @@ export default defineMeta(${JSON.stringify(finalMeta, null, 2)});
13358
14034
  var relocateUnparseableMetas = async (root, metas) => {
13359
14035
  const warnings = [];
13360
14036
  await Promise.all(metas.map(async ({ abs, rel }) => {
13361
- const dest = join33(root, "docs", rel);
13362
- if (existsSync24(dest)) {
14037
+ const dest = join38(root, "docs", rel);
14038
+ if (existsSync26(dest)) {
13363
14039
  warnings.push(`Skipped ${rel} (target already exists)`);
13364
14040
  return;
13365
14041
  }
@@ -13371,7 +14047,7 @@ var relocateUnparseableMetas = async (root, metas) => {
13371
14047
  };
13372
14048
  var buildConfig = (tabs) => tabs.length > 0 ? { navigation: { tabs }, title: "Documentation" } : { title: "Documentation" };
13373
14049
  var migrateNextraProject = async (root) => {
13374
- const sourceDir = SOURCE_DIRS.find((dir) => existsSync24(join33(root, dir)));
14050
+ const sourceDir = SOURCE_DIRS.find((dir) => existsSync26(join38(root, dir)));
13375
14051
  if (!sourceDir) {
13376
14052
  await writeBlumeConfig(root, { title: "Documentation" });
13377
14053
  return {
@@ -13381,7 +14057,7 @@ var migrateNextraProject = async (root) => {
13381
14057
  ]
13382
14058
  };
13383
14059
  }
13384
- const base = join33(root, sourceDir);
14060
+ const base = join38(root, sourceDir);
13385
14061
  const pageFiles = await glob10([PAGE_GLOB2], {
13386
14062
  absolute: true,
13387
14063
  cwd: base,
@@ -13395,9 +14071,9 @@ var migrateNextraProject = async (root) => {
13395
14071
  const index = indexFolders(base, pageFiles, metaFiles);
13396
14072
  const metas = await Promise.all(metaFiles.map(async (abs) => ({
13397
14073
  abs,
13398
- ext: extname7(abs),
13399
- raw: await readFile19(abs, "utf-8"),
13400
- rel: relative17(base, abs)
14074
+ ext: extname8(abs),
14075
+ raw: await readFile21(abs, "utf-8"),
14076
+ rel: relative20(base, abs)
13401
14077
  })));
13402
14078
  const plan = planMetas(metas, index);
13403
14079
  const moves = await Promise.all(pageFiles.map((abs) => movePage2(abs, { base, overrides: plan.pageOverrides, root })));
@@ -13422,15 +14098,15 @@ var migrateNextraProject = async (root) => {
13422
14098
  };
13423
14099
 
13424
14100
  // src/migrate/starlight/index.ts
13425
- import { existsSync as existsSync26 } from "node:fs";
13426
- import { readFile as readFile21, writeFile as writeFile14 } from "node:fs/promises";
13427
- import { join as join35 } from "pathe";
14101
+ import { existsSync as existsSync28 } from "node:fs";
14102
+ import { readFile as readFile23, writeFile as writeFile15 } from "node:fs/promises";
14103
+ import { join as join40 } from "pathe";
13428
14104
  import { glob as glob11 } from "tinyglobby";
13429
14105
 
13430
14106
  // src/migrate/starlight/config.ts
13431
- import { existsSync as existsSync25 } from "node:fs";
13432
- import { readFile as readFile20 } from "node:fs/promises";
13433
- import { join as join34 } from "pathe";
14107
+ import { existsSync as existsSync27 } from "node:fs";
14108
+ import { readFile as readFile22 } from "node:fs/promises";
14109
+ import { join as join39 } from "pathe";
13434
14110
  var CONFIG_FILES = [
13435
14111
  "astro.config.mjs",
13436
14112
  "astro.config.mts",
@@ -13461,7 +14137,7 @@ var extractStarlightOptions = (source) => {
13461
14137
  return isLiteralObject(parsed) ? parsed : "unparseable";
13462
14138
  };
13463
14139
  var loadStarlightConfig = async (root) => {
13464
- const file = CONFIG_FILES.map((name) => join34(root, name)).find((path) => existsSync25(path));
14140
+ const file = CONFIG_FILES.map((name) => join39(root, name)).find((path) => existsSync27(path));
13465
14141
  if (!file) {
13466
14142
  return {
13467
14143
  options: {},
@@ -13470,7 +14146,7 @@ var loadStarlightConfig = async (root) => {
13470
14146
  ]
13471
14147
  };
13472
14148
  }
13473
- const source = await readFile20(file, "utf-8");
14149
+ const source = await readFile22(file, "utf-8");
13474
14150
  const result = extractStarlightOptions(source);
13475
14151
  if (result === "missing") {
13476
14152
  return {
@@ -13868,7 +14544,7 @@ var starlightI18n = (options) => {
13868
14544
  // src/migrate/starlight/index.ts
13869
14545
  var CONTENT_DIR = "src/content/docs";
13870
14546
  var transformPage = async (file) => {
13871
- const raw = await readFile21(file, "utf-8");
14547
+ const raw = await readFile23(file, "utf-8");
13872
14548
  let text = stripStarlightImports(raw);
13873
14549
  text = rewriteStarlightAsides(text);
13874
14550
  text = rewriteStarlightComponents(text);
@@ -13878,7 +14554,7 @@ var transformPage = async (file) => {
13878
14554
  const { data, removed } = normalizeStarlightPageMeta(parsed.data);
13879
14555
  const content = Object.keys(data).length > 0 ? frontmatter_default.stringify(parsed.content, data) : parsed.content;
13880
14556
  if (content !== raw) {
13881
- await writeFile14(file, content, "utf-8");
14557
+ await writeFile15(file, content, "utf-8");
13882
14558
  }
13883
14559
  return { aliased, removed, unsupported };
13884
14560
  };
@@ -13890,8 +14566,8 @@ var migrateStarlightProject = async (root) => {
13890
14566
  config.i18n = i18n;
13891
14567
  warnings.push(`Mapped ${i18n.locales.length} locale(s) to i18n (default: ${i18n.defaultLocale}); review the locale labels.`);
13892
14568
  }
13893
- const base = join35(root, CONTENT_DIR);
13894
- if (!existsSync26(base)) {
14569
+ const base = join40(root, CONTENT_DIR);
14570
+ if (!existsSync28(base)) {
13895
14571
  await writeBlumeConfig(root, config);
13896
14572
  return {
13897
14573
  moved: 0,
@@ -13979,10 +14655,10 @@ var migrateCommand = defineCommand8({
13979
14655
  });
13980
14656
 
13981
14657
  // src/cli/commands/preview.ts
13982
- import { existsSync as existsSync27 } from "node:fs";
14658
+ import { existsSync as existsSync29 } from "node:fs";
13983
14659
  import { preview } from "astro";
13984
14660
  import { defineCommand as defineCommand9 } from "citty";
13985
- import { join as join36 } from "pathe";
14661
+ import { join as join41 } from "pathe";
13986
14662
  var previewCommand = defineCommand9({
13987
14663
  args: {
13988
14664
  host: { description: "Network host to bind.", type: "string" },
@@ -13996,7 +14672,7 @@ var previewCommand = defineCommand9({
13996
14672
  const root = process.cwd();
13997
14673
  const { config } = await loadConfig(root);
13998
14674
  const context = resolveProjectContext(root, config);
13999
- if (!existsSync27(join36(context.outDir, "astro.config.mjs"))) {
14675
+ if (!existsSync29(join41(context.outDir, "astro.config.mjs"))) {
14000
14676
  logger.error("No build found. Run `blume build` first.");
14001
14677
  process.exit(1);
14002
14678
  }
@@ -14005,7 +14681,7 @@ var previewCommand = defineCommand9({
14005
14681
  root: context.outDir,
14006
14682
  server: {
14007
14683
  host: args.host ?? false,
14008
- port: args.port ? Number(args.port) : undefined
14684
+ port: parsePort(args.port)
14009
14685
  }
14010
14686
  });
14011
14687
  }
@@ -14014,7 +14690,7 @@ var previewCommand = defineCommand9({
14014
14690
  // src/cli/commands/sync.ts
14015
14691
  import { rm as rm6 } from "node:fs/promises";
14016
14692
  import { defineCommand as defineCommand10 } from "citty";
14017
- import { join as join37 } from "pathe";
14693
+ import { join as join42 } from "pathe";
14018
14694
  var syncCommand = defineCommand10({
14019
14695
  args: {
14020
14696
  force: {
@@ -14036,7 +14712,7 @@ var syncCommand = defineCommand10({
14036
14712
  if (args.force) {
14037
14713
  const { config } = await loadConfig(root);
14038
14714
  const context = resolveProjectContext(root, config);
14039
- await rm6(join37(context.outDir, "cache"), { force: true, recursive: true });
14715
+ await rm6(join42(context.outDir, "cache"), { force: true, recursive: true });
14040
14716
  logger.info("Cleared source cache.");
14041
14717
  }
14042
14718
  await prepareProject({
@@ -14051,13 +14727,13 @@ var syncCommand = defineCommand10({
14051
14727
  });
14052
14728
 
14053
14729
  // src/cli/commands/validate.ts
14054
- import { existsSync as existsSync29 } from "node:fs";
14730
+ import { existsSync as existsSync31 } from "node:fs";
14055
14731
  import { defineCommand as defineCommand11 } from "citty";
14056
- import { join as join39 } from "pathe";
14732
+ import { join as join44 } from "pathe";
14057
14733
 
14058
14734
  // src/core/links.ts
14059
- import { existsSync as existsSync28 } from "node:fs";
14060
- import { join as join38 } from "pathe";
14735
+ import { existsSync as existsSync30 } from "node:fs";
14736
+ import { basename as basename8, join as join43 } from "pathe";
14061
14737
  var HTTP = /^https?:\/\//iu;
14062
14738
  var PROTOCOL_RELATIVE = /^\/\//u;
14063
14739
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -14069,9 +14745,21 @@ var STATUS_NOT_FOUND = 404;
14069
14745
  var STATUS_GONE = 410;
14070
14746
  var STATUS_METHOD_NOT_ALLOWED = 405;
14071
14747
  var STATUS_NOT_IMPLEMENTED = 501;
14072
- var resolveRelative = (pageRoute, target) => {
14748
+ var assetIsPresent = (resolved, ctx) => {
14749
+ if (ctx.publicDir && existsSync30(join43(ctx.publicDir, resolved))) {
14750
+ return true;
14751
+ }
14752
+ return ctx.assetMounts.some((mount) => (resolved === mount.url || resolved.startsWith(`${mount.url}/`)) && existsSync30(join43(mount.dir, resolved.slice(mount.url.length))));
14753
+ };
14754
+ var isIndexPage = (page2) => {
14755
+ const ref = page2.source?.ref ?? page2.sourcePath ?? "";
14756
+ return /^index\.(?:md|mdx)$/iu.test(basename8(ref));
14757
+ };
14758
+ var resolveRelative = (pageRoute, target, isIndex) => {
14073
14759
  const segments = pageRoute.split("/").filter(Boolean);
14074
- segments.pop();
14760
+ if (!isIndex) {
14761
+ segments.pop();
14762
+ }
14075
14763
  for (const part of target.split("/")) {
14076
14764
  if (part === "" || part === ".") {
14077
14765
  continue;
@@ -14113,13 +14801,20 @@ var checkAnchor = (route, fragment, site, ctx) => {
14113
14801
  };
14114
14802
  };
14115
14803
  var checkPathLink = (resolved, fragment, target, site, ctx) => {
14804
+ const route = toRoute(resolved);
14805
+ if (ctx.routes.has(route)) {
14806
+ return fragment ? checkAnchor(route, fragment, site, ctx) : null;
14807
+ }
14808
+ if (ctx.redirects.has(route)) {
14809
+ return null;
14810
+ }
14116
14811
  if (FILE_EXT.test(resolved) && !DOC_EXT.test(resolved)) {
14117
- if (ctx.publicDir === null) {
14118
- return "asset-unchecked";
14119
- }
14120
- if (existsSync28(join38(ctx.publicDir, resolved))) {
14812
+ if (assetIsPresent(resolved, ctx)) {
14121
14813
  return null;
14122
14814
  }
14815
+ if (ctx.publicDir === null && ctx.assetMounts.length === 0) {
14816
+ return "asset-unchecked";
14817
+ }
14123
14818
  return {
14124
14819
  ...site,
14125
14820
  code: "BLUME_BROKEN_ASSET",
@@ -14128,13 +14823,6 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
14128
14823
  suggestion: `Add the file at public${resolved} or fix the link.`
14129
14824
  };
14130
14825
  }
14131
- const route = toRoute(resolved);
14132
- if (ctx.routes.has(route)) {
14133
- return fragment ? checkAnchor(route, fragment, site, ctx) : null;
14134
- }
14135
- if (ctx.redirects.has(route)) {
14136
- return null;
14137
- }
14138
14826
  return {
14139
14827
  ...site,
14140
14828
  code: "BLUME_BROKEN_LINK",
@@ -14243,12 +14931,13 @@ var classifyLink = (page2, link, ctx, onExternal) => {
14243
14931
  if (rawPath === "") {
14244
14932
  return fragment ? checkAnchor(page2.route, fragment, site, ctx) : null;
14245
14933
  }
14246
- const resolved = rawPath.startsWith("/") ? rawPath : resolveRelative(page2.route, rawPath);
14934
+ const resolved = rawPath.startsWith("/") ? rawPath : resolveRelative(page2.route, rawPath, isIndexPage(page2));
14247
14935
  return checkPathLink(resolved, fragment, target, site, ctx);
14248
14936
  };
14249
14937
  var validateLinks = async (graph, options) => {
14250
14938
  const ctx = {
14251
14939
  anchors: buildAnchorIndex(graph.pages),
14940
+ assetMounts: options.assetMounts ?? [],
14252
14941
  publicDir: options.publicDir,
14253
14942
  redirects: new Set((options.redirects ?? []).map((redirect) => toRoute(redirect.from))),
14254
14943
  routes: new Set(graph.routes.keys())
@@ -14305,10 +14994,11 @@ var validateCommand = defineCommand11({
14305
14994
  try {
14306
14995
  const project = await scanProject(root, { mode: "build" });
14307
14996
  diagnostics.push(...project.diagnostics);
14308
- const publicDir = join39(root, "public");
14997
+ const publicDir = join44(root, "public");
14309
14998
  diagnostics.push(...await validateLinks(project.graph, {
14999
+ assetMounts: resolveAssetMounts(root, project.config.content.assets),
14310
15000
  checkExternal: Boolean(args.external),
14311
- publicDir: existsSync29(publicDir) ? publicDir : null,
15001
+ publicDir: existsSync31(publicDir) ? publicDir : null,
14312
15002
  redirects: project.config.redirects
14313
15003
  }));
14314
15004
  } catch (error) {
@@ -14322,6 +15012,7 @@ var validateCommand = defineCommand11({
14322
15012
  if (args.json) {
14323
15013
  const hadErrors2 = reportDiagnosticsJson(diagnostics, root);
14324
15014
  if (hadErrors2 || Boolean(args.strict) && diagnostics.length > 0) {
15015
+ await flushStdout();
14325
15016
  process.exit(1);
14326
15017
  }
14327
15018
  return;
@@ -14368,5 +15059,5 @@ process.on("unhandledRejection", (error) => {
14368
15059
  });
14369
15060
  runMain(main);
14370
15061
 
14371
- //# debugId=8EA79E97DE44177D64756E2164756E21
15062
+ //# debugId=432F59E1AFBFB0A064756E2164756E21
14372
15063
  //# sourceMappingURL=index.js.map