blume 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cli/index.js +340 -132
  3. package/dist/cli/index.js.map +21 -20
  4. package/dist/types/ai/ask-context.d.ts +78 -0
  5. package/dist/types/core/config-input.d.ts +53 -1
  6. package/dist/types/core/data.d.ts +16 -0
  7. package/dist/types/core/open-in-chat.d.ts +9 -0
  8. package/dist/types/core/schema.d.ts +48 -1
  9. package/dist/types/core/types.d.ts +10 -3
  10. package/dist/types/openapi/references.d.ts +9 -0
  11. package/dist/types/search/orama-index.d.ts +70 -0
  12. package/docs/advanced/api-reference.mdx +67 -5
  13. package/docs/configuration/ai.mdx +35 -0
  14. package/docs/configuration/index.mdx +13 -1
  15. package/docs/configuration/search.mdx +4 -4
  16. package/docs/reference/cli.mdx +1 -1
  17. package/package.json +1 -1
  18. package/skills/blume-migrate/SKILL.md +1 -1
  19. package/skills/blume-migrate/references/mintlify.md +1 -1
  20. package/src/ai/ask-context.ts +51 -11
  21. package/src/ai/mcp/data.ts +3 -2
  22. package/src/ai/mcp/server.ts +3 -2
  23. package/src/assets/icon-dark.png +0 -0
  24. package/src/astro/generate.ts +162 -13
  25. package/src/astro/templates.ts +63 -11
  26. package/src/components/content/AccordionItem.astro +4 -0
  27. package/src/components/content/Update.astro +3 -0
  28. package/src/components/islands/AskAI.astro +6 -0
  29. package/src/components/islands/ask-ai.tsx +39 -9
  30. package/src/components/layout/Analytics.astro +9 -1
  31. package/src/components/layout/Favicon.astro +29 -8
  32. package/src/components/layout/Header.astro +2 -2
  33. package/src/components/layout/NavSelector.astro +1 -1
  34. package/src/components/layout/PageActions.astro +120 -78
  35. package/src/components/layout/PageFeedback.astro +12 -3
  36. package/src/components/layout/PageLayout.astro +7 -2
  37. package/src/components/layout/ReferenceLayout.astro +10 -8
  38. package/src/components/layout/RootLayout.astro +151 -120
  39. package/src/components/layout/Search.astro +41 -26
  40. package/src/components/layout/drawer-inert.ts +10 -5
  41. package/src/components/layout/head-scripts.ts +34 -16
  42. package/src/components/layout/nav-utils.ts +34 -15
  43. package/src/components/layout/search/orama.ts +3 -2
  44. package/src/components/openapi/AsyncApiOperation.astro +22 -7
  45. package/src/components/openapi/MessageComposer.astro +238 -0
  46. package/src/components/openapi/Operation.astro +26 -12
  47. package/src/components/openapi/PanelTabs.astro +7 -0
  48. package/src/components/openapi/Playground.astro +320 -0
  49. package/src/components/openapi/RequestPanel.astro +1 -0
  50. package/src/components/openapi/async-snippets.ts +20 -7
  51. package/src/components/openapi/async.ts +13 -2
  52. package/src/components/openapi/message-composer.ts +242 -0
  53. package/src/components/openapi/message-model.ts +108 -0
  54. package/src/components/openapi/message.ts +153 -0
  55. package/src/components/openapi/operation-model.ts +260 -0
  56. package/src/components/openapi/playground-client.ts +486 -0
  57. package/src/components/openapi/playground-schema.ts +109 -0
  58. package/src/components/openapi/request.ts +287 -0
  59. package/src/components/openapi/security.ts +0 -56
  60. package/src/components/openapi/snippets.ts +23 -136
  61. package/src/components/openapi/validate-json.ts +144 -0
  62. package/src/components/openapi/ws-client.ts +194 -0
  63. package/src/core/config-input.ts +66 -0
  64. package/src/core/content-assets.ts +66 -15
  65. package/src/core/data.ts +13 -0
  66. package/src/core/last-modified.ts +28 -3
  67. package/src/core/links.ts +30 -4
  68. package/src/core/navigation.ts +26 -1
  69. package/src/core/open-in-chat.ts +17 -0
  70. package/src/core/schema.ts +59 -0
  71. package/src/core/server-features.ts +11 -0
  72. package/src/core/sources/normalize.ts +10 -2
  73. package/src/core/types.ts +10 -3
  74. package/src/openapi/model.ts +7 -0
  75. package/src/openapi/proxy.ts +217 -0
  76. package/src/openapi/references.ts +8 -0
  77. package/src/openapi/source.ts +13 -0
  78. package/src/registry/eject.ts +4 -5
  79. package/src/search/orama-index.ts +109 -36
  80. package/src/theme/entry.ts +6 -15
package/dist/cli/index.js CHANGED
@@ -3179,11 +3179,13 @@ var referencesFor = (kind, block, defaultLabel, renderer, display, basePath) =>
3179
3179
  var resolveReferences = (config) => [
3180
3180
  ...referencesFor("openapi", config.openapi, "API Reference", config.openapi.renderer, {
3181
3181
  codeSamples: config.openapi.codeSamples,
3182
- expandSchemas: config.openapi.expandSchemas
3182
+ expandSchemas: config.openapi.expandSchemas,
3183
+ playground: config.openapi.playground
3183
3184
  }, config.basePath),
3184
3185
  ...referencesFor("asyncapi", config.asyncapi, "Events", config.asyncapi.renderer, {
3185
3186
  codeSamples: config.asyncapi.codeSamples,
3186
- expandSchemas: config.asyncapi.expandSchemas
3187
+ expandSchemas: config.asyncapi.expandSchemas,
3188
+ playground: config.asyncapi.playground
3187
3189
  }, config.basePath)
3188
3190
  ];
3189
3191
  var referenceRoutes = (config) => resolveReferences(config).map((ref) => ref.renderer === "blume" ? withBasePath(config.basePath, ref.route) : ref.route);
@@ -3976,10 +3978,10 @@ ${userConfigSetup}export default defineConfig({
3976
3978
  },
3977
3979
  },
3978
3980
  devToolbar: { enabled: false },
3979
- // Navigations are full document loads (no client router), so the next page's
3980
- // HTML is fetched on hover/viewport to hide the request latency behind the
3981
- // user's intent. Pairs with the cross-document view-transition rule in the
3982
- // theme sheet, which smooths the swap itself.
3981
+ // The layouts render Astro's <ClientRouter />, and its in-place swaps read
3982
+ // from the prefetch cache fetching every link on hover/viewport hides the
3983
+ // request latency behind the user's intent, so most navigations swap
3984
+ // instantly.
3983
3985
  prefetch: { prefetchAll: true },
3984
3986
  vite: {
3985
3987
  plugins: [tailwindcss(), prerenderDepsPlugin(), serverAppResolvePlugin()],
@@ -4092,7 +4094,8 @@ export const collections = { docs${options.staged ? ", staged" : ""} };
4092
4094
  `;
4093
4095
  };
4094
4096
  var ASK_FALLBACK_PROMPT = "You are a helpful documentation assistant. Answer using the project's documentation.";
4095
- var askEndpointTemplate = (backend, grounded, instructions) => {
4097
+ var askEndpointTemplate = (backend, grounded, options) => {
4098
+ const instructions = options?.instructions;
4096
4099
  const fallbackPrompt = instructions ? `${ASK_FALLBACK_PROMPT}
4097
4100
 
4098
4101
  ${instructions}` : ASK_FALLBACK_PROMPT;
@@ -4121,7 +4124,14 @@ const provider = createOpenAICompatible({
4121
4124
  }
4122
4125
  if (grounded) {
4123
4126
  imports.push('import { createAskContext } from "blume/ai/ask-context.ts";', 'import askData from "../../generated/ask-data.json";');
4124
- const groundOptions = instructions ? `, { instructions: ${JSON.stringify(instructions)} }` : "";
4127
+ const groundFields = [];
4128
+ if (instructions) {
4129
+ groundFields.push(`instructions: ${JSON.stringify(instructions)}`);
4130
+ }
4131
+ if (options?.retrieval) {
4132
+ groundFields.push(`retrieval: ${JSON.stringify(options.retrieval)}`);
4133
+ }
4134
+ const groundOptions = groundFields.length > 0 ? `, { ${groundFields.join(", ")} }` : "";
4125
4135
  setup += `
4126
4136
  const ground = createAskContext(askData${groundOptions});
4127
4137
  `;
@@ -4459,6 +4469,16 @@ const handler = createMcpFetchHandler(data as McpData);
4459
4469
  export const ALL: APIRoute = ({ request }) => handler(request);
4460
4470
  `;
4461
4471
  };
4472
+ var playgroundProxyTemplate = (origins) => `// Generated by Blume. Do not edit.
4473
+ import type { APIRoute } from "astro";
4474
+ import { createPlaygroundProxyHandler } from "blume/openapi/proxy.ts";
4475
+
4476
+ export const prerender = false;
4477
+
4478
+ const handler = createPlaygroundProxyHandler(${JSON.stringify(origins)});
4479
+
4480
+ export const ALL: APIRoute = ({ request }) => handler(request);
4481
+ `;
4462
4482
  var staticJsonEndpointTemplate = (payload) => `// Generated by Blume. Do not edit.
4463
4483
  export const prerender = true;
4464
4484
 
@@ -4949,6 +4969,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
4949
4969
  feedback={data.config.feedback}
4950
4970
  exportPdf={${options.exportPdf}}
4951
4971
  exportEpub={${options.exportEpub}}
4972
+ openInChat={data.config.openInChat}
4952
4973
  feeds={data.feeds}
4953
4974
  discovery={data.config.discovery}
4954
4975
  siteUrl={data.config.site}
@@ -5162,6 +5183,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
5162
5183
  canonical={canonical}
5163
5184
  exportPdf={${options.exportPdf}}
5164
5185
  exportEpub={${options.exportEpub}}
5186
+ openInChat={data.config.openInChat}
5165
5187
  feeds={data.feeds}
5166
5188
  discovery={data.config.discovery}
5167
5189
  siteUrl={data.config.site}
@@ -9623,6 +9645,16 @@ var resolveUIStrings = (locale, options) => {
9623
9645
  return dict;
9624
9646
  };
9625
9647
 
9648
+ // src/core/open-in-chat.ts
9649
+ var openInChatProviders = [
9650
+ "v0",
9651
+ "chatgpt",
9652
+ "claude",
9653
+ "t3",
9654
+ "scira",
9655
+ "cursor"
9656
+ ];
9657
+
9626
9658
  // src/core/standard-schema.ts
9627
9659
  var isStandardSchema = (value) => typeof value === "object" && value !== null && typeof value["~standard"]?.validate === "function";
9628
9660
 
@@ -10037,6 +10069,11 @@ var aiConfigSchema = z2.strictObject({
10037
10069
  instructions: z2.string().trim().min(1).optional(),
10038
10070
  model: z2.string().default("openai/gpt-5.5"),
10039
10071
  provider: z2.enum(askAiProviders).default("gateway"),
10072
+ retrieval: z2.strictObject({
10073
+ contextBudget: z2.number().int().positive().optional(),
10074
+ excerptChars: z2.number().int().positive().optional(),
10075
+ maxResults: z2.number().int().positive().optional()
10076
+ }).optional(),
10040
10077
  suggestions: z2.array(z2.strictObject({
10041
10078
  icon: iconName.optional(),
10042
10079
  label: z2.string().min(1)
@@ -10061,6 +10098,17 @@ var aiConfigSchema = z2.strictObject({
10061
10098
  message: "Expected a serializer function."
10062
10099
  })).default({}),
10063
10100
  mcp: mcpConfigSchema.prefault({}),
10101
+ openInChat: z2.union([
10102
+ z2.boolean(),
10103
+ z2.array(z2.enum(openInChatProviders)).refine((value) => new Set(value).size === value.length, {
10104
+ message: "ai.openInChat must not repeat a provider."
10105
+ })
10106
+ ]).default(true).transform((value) => {
10107
+ if (isBoolean(value)) {
10108
+ return value ? [...openInChatProviders] : [];
10109
+ }
10110
+ return value;
10111
+ }),
10064
10112
  skills: z2.string().min(1).optional(),
10065
10113
  webBotAuth: z2.strictObject({
10066
10114
  keys: z2.array(publicJwkSchema).default([])
@@ -10336,6 +10384,13 @@ var referenceConfigSchema = (defaults) => z2.strictObject({
10336
10384
  codeSamples: z2.array(z2.string()).default(defaults.codeSamples),
10337
10385
  enabled: z2.boolean().default(false),
10338
10386
  expandSchemas: z2.boolean().default(false),
10387
+ playground: z2.union([
10388
+ z2.boolean(),
10389
+ z2.strictObject({
10390
+ enabled: z2.boolean().default(true),
10391
+ proxy: z2.union([z2.boolean(), z2.string()]).default(false)
10392
+ })
10393
+ ]).default(true).transform((value) => isBoolean(value) ? { enabled: value, proxy: false } : value),
10339
10394
  renderer: z2.enum(["blume", "scalar"]).default("blume"),
10340
10395
  route: z2.string().default(defaults.route),
10341
10396
  scalar: scalarConfigSchema,
@@ -10775,6 +10830,8 @@ import { extname } from "pathe";
10775
10830
  var NUMERIC_PREFIX = /^(?<order>\d+)[-_.]/u;
10776
10831
  var GROUP_FOLDER = /^\((?<label>.+)\)$/u;
10777
10832
  var WORD_SPLIT = /[-_]/u;
10833
+ var isUnderPath = (route, base) => base === "/" || route === base || route.startsWith(`${base}/`);
10834
+ var isRootTab = (tab, root) => isUnderPath(root, tab.path);
10778
10835
  var humanize = (segment) => segment.replace(NUMERIC_PREFIX, "").split(WORD_SPLIT).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
10779
10836
  var numericOrder = (segment) => {
10780
10837
  const value = segment.match(NUMERIC_PREFIX)?.groups?.order;
@@ -11231,7 +11288,7 @@ var buildNavigation = (pages, options) => {
11231
11288
  tabs: withTabHrefs(tabs, sidebar2)
11232
11289
  };
11233
11290
  }
11234
- const sidebar = buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, sharedMetaPrefix, display, new Set(tabs.flatMap((tab) => tab.path === rootTabPath ? [] : [tab.path])), diagnostics);
11291
+ const sidebar = buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, sharedMetaPrefix, display, new Set(tabs.flatMap((tab) => isRootTab(tab, rootTabPath) ? [] : [tab.path])), diagnostics);
11235
11292
  return {
11236
11293
  featured,
11237
11294
  root: rootTabPath,
@@ -11463,6 +11520,15 @@ var buildContentGraph = (pages, options) => {
11463
11520
  // src/core/last-modified.ts
11464
11521
  import { execFileSync } from "node:child_process";
11465
11522
  import { relative as relative7 } from "pathe";
11523
+ var GIT_LOCATION_VARS = new Set([
11524
+ "GIT_COMMON_DIR",
11525
+ "GIT_DIR",
11526
+ "GIT_INDEX_FILE",
11527
+ "GIT_OBJECT_DIRECTORY",
11528
+ "GIT_PREFIX",
11529
+ "GIT_WORK_TREE"
11530
+ ]);
11531
+ var gitEnv = () => Object.fromEntries(Object.entries(process.env).filter(([key]) => !GIT_LOCATION_VARS.has(key)));
11466
11532
  var resolveLastModifiedConfig = (value) => {
11467
11533
  if (value === false) {
11468
11534
  return { enabled: false, source: "git" };
@@ -11490,7 +11556,7 @@ var gitLastModifiedTimes = (root, contentRoots, sourcePaths) => {
11490
11556
  return new Map;
11491
11557
  }
11492
11558
  try {
11493
- const gitRoot = execFileSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
11559
+ const gitRoot = execFileSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf-8", env: gitEnv() }).trim();
11494
11560
  const output = execFileSync("git", [
11495
11561
  "-C",
11496
11562
  root,
@@ -11501,7 +11567,7 @@ var gitLastModifiedTimes = (root, contentRoots, sourcePaths) => {
11501
11567
  "--name-only",
11502
11568
  "--",
11503
11569
  ...contentRoots
11504
- ], { encoding: "utf-8", maxBuffer: 256 * 1024 * 1024 });
11570
+ ], { encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 });
11505
11571
  const byRepoPath = parseGitLog(output);
11506
11572
  const result = new Map;
11507
11573
  for (const sourcePath of sourcePaths) {
@@ -11517,7 +11583,11 @@ var gitLastModifiedTimes = (root, contentRoots, sourcePaths) => {
11517
11583
  };
11518
11584
  var isShallowGitRepository = (root) => {
11519
11585
  try {
11520
- return execFileSync("git", ["-C", root, "rev-parse", "--is-shallow-repository"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim() === "true";
11586
+ return execFileSync("git", ["-C", root, "rev-parse", "--is-shallow-repository"], {
11587
+ encoding: "utf-8",
11588
+ env: gitEnv(),
11589
+ stdio: ["ignore", "pipe", "ignore"]
11590
+ }).trim() === "true";
11521
11591
  } catch {
11522
11592
  return false;
11523
11593
  }
@@ -11906,11 +11976,15 @@ var scanLinkLine = (line, lineNumber, fence, links) => {
11906
11976
  continue;
11907
11977
  }
11908
11978
  const targetOffset = targetOffsetIn(match[0], target, match.groups?.title);
11909
- links.push({
11979
+ const entry = {
11910
11980
  column: match.index + targetOffset + 1,
11911
11981
  line: lineNumber,
11912
11982
  target
11913
- });
11983
+ };
11984
+ if (masked[match.index - 1] === "!") {
11985
+ entry.image = true;
11986
+ }
11987
+ links.push(entry);
11914
11988
  const label = match[0].slice(0, targetOffset - "](".length);
11915
11989
  for (const image of label.matchAll(MD_IMAGE)) {
11916
11990
  const imageTarget = image.groups?.target;
@@ -11919,6 +11993,7 @@ var scanLinkLine = (line, lineNumber, fence, links) => {
11919
11993
  }
11920
11994
  links.push({
11921
11995
  column: match.index + image.index + targetOffsetIn(image[0], imageTarget, image.groups?.title) + 1,
11996
+ image: true,
11922
11997
  line: lineNumber,
11923
11998
  target: imageTarget
11924
11999
  });
@@ -12867,6 +12942,13 @@ var openApiSource = (references, ctx) => {
12867
12942
  try {
12868
12943
  const { document, warnings, operations, tags, extractWarnings } = await parseReference(reference, ctx);
12869
12944
  const info = document.info ?? { title: reference.label, version: "" };
12945
+ const { proxy: configuredProxy } = reference.display.playground;
12946
+ let proxy = false;
12947
+ if (configuredProxy === true) {
12948
+ proxy = withBasePath(reference.basePath, "/_api-proxy");
12949
+ } else if (configuredProxy !== false && configuredProxy !== "") {
12950
+ proxy = configuredProxy;
12951
+ }
12870
12952
  const spec = {
12871
12953
  codeSamples: reference.display.codeSamples,
12872
12954
  description: info.description ?? "",
@@ -12881,6 +12963,7 @@ var openApiSource = (references, ctx) => {
12881
12963
  route: withBasePath(reference.basePath, operation.route)
12882
12964
  }
12883
12965
  ])),
12966
+ playground: { enabled: reference.display.playground.enabled, proxy },
12884
12967
  route: reference.route,
12885
12968
  slug: reference.slug,
12886
12969
  tags,
@@ -14331,7 +14414,7 @@ var auditCommand = defineCommand2({
14331
14414
  });
14332
14415
 
14333
14416
  // src/cli/commands/build.ts
14334
- import { existsSync as existsSync18 } from "node:fs";
14417
+ import { existsSync as existsSync17 } from "node:fs";
14335
14418
  import { mkdir as mkdir8, readdir as readdir2, readFile as readFile17, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
14336
14419
  import { build } from "astro";
14337
14420
  import { defineCommand as defineCommand3 } from "citty";
@@ -14504,9 +14587,16 @@ var buildAgentReadability = (project) => {
14504
14587
  };
14505
14588
 
14506
14589
  // src/core/content-assets.ts
14507
- import { existsSync as existsSync9 } from "node:fs";
14590
+ import { readdirSync, statSync } from "node:fs";
14508
14591
  import { readFile as readFile8 } from "node:fs/promises";
14509
- import { dirname as dirname7, extname as extname5, relative as relative11, resolve as resolve5 } from "pathe";
14592
+ import {
14593
+ basename as basename2,
14594
+ dirname as dirname7,
14595
+ extname as extname5,
14596
+ normalize as normalize3,
14597
+ relative as relative11,
14598
+ resolve as resolve5
14599
+ } from "pathe";
14510
14600
  var CONTENT_ASSETS_PREFIX = "/blume-assets/content";
14511
14601
  var IMAGE_EXTENSIONS = new Set([
14512
14602
  ".apng",
@@ -14529,19 +14619,40 @@ var contentAssetParam = (projectRoot, absPath) => {
14529
14619
  }
14530
14620
  return rel;
14531
14621
  };
14532
- var resolveRelativeImage = (sourceDir, target) => {
14533
- if (!isRelativeTarget(target)) {
14534
- return null;
14535
- }
14536
- let decoded = target;
14622
+ var decodeTarget = (target) => {
14537
14623
  try {
14538
- decoded = decodeURI(target);
14539
- } catch {}
14540
- if (!IMAGE_EXTENSIONS.has(extname5(decoded).toLowerCase())) {
14624
+ return decodeURI(target);
14625
+ } catch {
14626
+ return target;
14627
+ }
14628
+ };
14629
+ var isRelativeImageTarget = (target) => isRelativeTarget(target) && IMAGE_EXTENSIONS.has(extname5(decodeTarget(target)).toLowerCase());
14630
+ var existsAsWritten = (abs, segments) => {
14631
+ let current = abs;
14632
+ for (let i = 0;i < segments; i += 1) {
14633
+ const parent = dirname7(current);
14634
+ let entries;
14635
+ try {
14636
+ entries = readdirSync(parent);
14637
+ } catch {
14638
+ return false;
14639
+ }
14640
+ if (!entries.includes(basename2(current))) {
14641
+ return false;
14642
+ }
14643
+ current = parent;
14644
+ }
14645
+ const stat2 = statSync(abs, { throwIfNoEntry: false });
14646
+ return stat2 !== undefined && stat2.isFile();
14647
+ };
14648
+ var resolveRelativeImage = (sourceDir, target) => {
14649
+ if (!isRelativeImageTarget(target)) {
14541
14650
  return null;
14542
14651
  }
14652
+ const decoded = decodeTarget(target);
14653
+ const segments = normalize3(decoded).split("/").filter((part) => part !== "" && part !== "..").length;
14543
14654
  const abs = resolve5(sourceDir, decoded);
14544
- return existsSync9(abs) ? abs : null;
14655
+ return existsAsWritten(abs, segments) ? abs : null;
14545
14656
  };
14546
14657
  var encodedParam = (param) => param.split("/").map(encodeURIComponent).join("/");
14547
14658
  var rewriteLine = (line, toUrl2) => {
@@ -15259,13 +15370,13 @@ var buildSignaturesDirectory = (config) => {
15259
15370
  };
15260
15371
 
15261
15372
  // src/core/gitignore.ts
15262
- import { existsSync as existsSync10 } from "node:fs";
15373
+ import { existsSync as existsSync9 } from "node:fs";
15263
15374
  import { readFile as readFile12, writeFile as writeFile6 } from "node:fs/promises";
15264
15375
  import { join as join19 } from "pathe";
15265
15376
  var gitignoreKey = (line) => line.trim().replace(/\/+$/u, "");
15266
15377
  var ensureGitignore = async (root, entries) => {
15267
15378
  const path = join19(root, ".gitignore");
15268
- const existing = existsSync10(path) ? await readFile12(path, "utf-8") : "";
15379
+ const existing = existsSync9(path) ? await readFile12(path, "utf-8") : "";
15269
15380
  const present = new Set(existing.split(`
15270
15381
  `).flatMap((line) => {
15271
15382
  const key = gitignoreKey(line);
@@ -15293,6 +15404,9 @@ var serverFeatures = (config) => {
15293
15404
  if (config.ai.mcp.enabled) {
15294
15405
  features.push("MCP server");
15295
15406
  }
15407
+ if (config.openapi.enabled && config.openapi.renderer === "blume" && config.openapi.playground.enabled && config.openapi.playground.proxy === true) {
15408
+ features.push("API playground proxy");
15409
+ }
15296
15410
  if (searchProviderMeta(config.search.provider).requiresServer) {
15297
15411
  features.push(`Search (${config.search.provider})`);
15298
15412
  }
@@ -16241,7 +16355,7 @@ var syncSearchProvider = async (project, reporter) => {
16241
16355
 
16242
16356
  // src/cli/dev-lock.ts
16243
16357
  import {
16244
- existsSync as existsSync11,
16358
+ existsSync as existsSync10,
16245
16359
  mkdirSync,
16246
16360
  readFileSync as readFileSync5,
16247
16361
  rmSync,
@@ -16280,7 +16394,7 @@ var isProcessAlive = (pid) => {
16280
16394
  };
16281
16395
  var readDevLock = (outDir) => {
16282
16396
  const path = lockPath(outDir);
16283
- if (!existsSync11(path)) {
16397
+ if (!existsSync10(path)) {
16284
16398
  return null;
16285
16399
  }
16286
16400
  const lock = parseLock(readFileSync5(path, "utf-8"));
@@ -16307,7 +16421,7 @@ class DevLockHeldError extends Error {
16307
16421
  }
16308
16422
  var ownsLock = (outDir) => {
16309
16423
  const path = lockPath(outDir);
16310
- if (!existsSync11(path)) {
16424
+ if (!existsSync10(path)) {
16311
16425
  return false;
16312
16426
  }
16313
16427
  try {
@@ -16365,7 +16479,7 @@ var refuseIfDevRunning = (root, action, options = {}) => {
16365
16479
 
16366
16480
  // src/astro/generate.ts
16367
16481
  import { createHash as createHash4 } from "node:crypto";
16368
- import { existsSync as existsSync16, readFileSync as readFileSync8, realpathSync } from "node:fs";
16482
+ import { existsSync as existsSync15, readFileSync as readFileSync8, realpathSync } from "node:fs";
16369
16483
  import {
16370
16484
  lstat,
16371
16485
  mkdir as mkdir7,
@@ -16379,7 +16493,7 @@ import { createRequire as createRequire4 } from "node:module";
16379
16493
  import { pathToFileURL as pathToFileURL2 } from "node:url";
16380
16494
  import { imageSize as imageSize2 } from "image-size";
16381
16495
  import pMap6 from "p-map";
16382
- import { basename as basename3, dirname as dirname11, join as join28, normalize as normalize3, relative as relative14, resolve as resolve8 } from "pathe";
16496
+ import { basename as basename4, dirname as dirname11, join as join28, normalize as normalize4, relative as relative14, resolve as resolve8 } from "pathe";
16383
16497
  import { glob as glob7 } from "tinyglobby";
16384
16498
 
16385
16499
  // src/ai/ask-data.ts
@@ -16628,7 +16742,7 @@ var validateUsedComponents = (pages, extraTags, registryNames) => {
16628
16742
  };
16629
16743
 
16630
16744
  // src/core/component-overrides.ts
16631
- import { existsSync as existsSync12 } from "node:fs";
16745
+ import { existsSync as existsSync11 } from "node:fs";
16632
16746
  import { dirname as dirname8, extname as extname7, isAbsolute as isAbsolute6, resolve as resolve6 } from "pathe";
16633
16747
  import ts from "typescript";
16634
16748
  var GROUPS = ["mdx", "layout", "islands"];
@@ -16723,7 +16837,7 @@ var findDefaultExportObject = (sourceFile) => {
16723
16837
  var probeExtension = (base) => {
16724
16838
  for (const extension of COMPONENT_EXTS) {
16725
16839
  const candidate = `${base}.${extension}`;
16726
- if (existsSync12(candidate)) {
16840
+ if (existsSync11(candidate)) {
16727
16841
  return candidate;
16728
16842
  }
16729
16843
  }
@@ -16902,7 +17016,7 @@ var writeTextAtomic = async (path, text) => {
16902
17016
  };
16903
17017
 
16904
17018
  // src/core/tsconfig-aliases.ts
16905
- import { existsSync as existsSync13 } from "node:fs";
17019
+ import { existsSync as existsSync12 } from "node:fs";
16906
17020
  import { parseTsconfig } from "get-tsconfig";
16907
17021
  import { dirname as dirname10, join as join22, resolve as resolve7 } from "pathe";
16908
17022
  var CONFIG_DIR_TEMPLATE = "${configDir}";
@@ -16924,7 +17038,7 @@ var toAlias = (key, value, baseDir, configDir) => {
16924
17038
  };
16925
17039
  };
16926
17040
  var resolveTsconfigAliases = (root) => {
16927
- const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join22(root, name)).find((file) => existsSync13(file));
17041
+ const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join22(root, name)).find((file) => existsSync12(file));
16928
17042
  if (!entry) {
16929
17043
  return {};
16930
17044
  }
@@ -16951,7 +17065,7 @@ var resolveTsconfigAliases = (root) => {
16951
17065
  };
16952
17066
 
16953
17067
  // src/og/derive.ts
16954
- import { existsSync as existsSync14 } from "node:fs";
17068
+ import { existsSync as existsSync13 } from "node:fs";
16955
17069
  import { isAbsolute as isAbsolute7, join as join23 } from "pathe";
16956
17070
  var CARD_WEIGHTS = [400, 600];
16957
17071
  var absoluteSrc = (root, src) => isAbsolute7(src) ? src : join23(root, src);
@@ -17055,11 +17169,11 @@ var missingFontFiles = (options, root) => {
17055
17169
  sources.push(absoluteSrc(root, font.src));
17056
17170
  }
17057
17171
  }
17058
- return [...new Set(sources)].filter((path) => !existsSync14(path));
17172
+ return [...new Set(sources)].filter((path) => !existsSync13(path));
17059
17173
  };
17060
17174
 
17061
17175
  // src/og/logo.ts
17062
- import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
17176
+ import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
17063
17177
  import { join as join24 } from "pathe";
17064
17178
  var resolveOgLogo = (project, source) => {
17065
17179
  if (!source?.toLowerCase().endsWith(".svg")) {
@@ -17069,7 +17183,7 @@ var resolveOgLogo = (project, source) => {
17069
17183
  const file = [
17070
17184
  join24(project.context.root, "public", relative13),
17071
17185
  join24(project.context.root, relative13)
17072
- ].find((path) => existsSync15(path));
17186
+ ].find((path) => existsSync14(path));
17073
17187
  return file ? readFileSync6(file, "utf-8") : undefined;
17074
17188
  };
17075
17189
 
@@ -17435,21 +17549,12 @@ ${THEME_MAPPING}
17435
17549
  }
17436
17550
  }
17437
17551
 
17438
- /* Same-origin navigations are full document loads (no client router); opting
17439
- into cross-document view transitions has the browser crossfade between the
17440
- old and new page instead of hard-swapping, in browsers that support it.
17441
- Pairs with the prefetch option in the generated Astro config. */
17442
- @view-transition {
17443
- navigation: auto;
17444
- }
17445
-
17446
- @media (prefers-reduced-motion: reduce) {
17447
- ::view-transition-group(*),
17448
- ::view-transition-old(*),
17449
- ::view-transition-new(*) {
17450
- animation: none !important;
17451
- }
17452
- }
17552
+ /* Same-origin navigations ride Astro's client router (the layouts render
17553
+ ClientRouter), which owns the page-transition styling including its own
17554
+ prefers-reduced-motion guard so the theme declares nothing about it. The
17555
+ cross-document view-transition opt-in that briefly lived here is
17556
+ deliberately gone: mixing it with the client router double-animates the
17557
+ full-load navigations the router hands back to the browser. */
17453
17558
 
17454
17559
  /* Code reads left-to-right regardless of page direction; only the surrounding
17455
17560
  chrome mirrors for RTL. Inline code is isolated so LTR identifiers don't
@@ -18230,7 +18335,7 @@ import { glob as glob6 } from "tinyglobby";
18230
18335
  // src/astro/islands.ts
18231
18336
  import { readFile as readFile14 } from "node:fs/promises";
18232
18337
  import pMap4 from "p-map";
18233
- import { basename as basename2, join as join26 } from "pathe";
18338
+ import { basename as basename3, join as join26 } from "pathe";
18234
18339
  import { glob as glob5 } from "tinyglobby";
18235
18340
  var DEFAULT_CLIENT = "visible";
18236
18341
  var READ_CONCURRENCY2 = 16;
@@ -18275,7 +18380,7 @@ var discoverIslands = async (root) => {
18275
18380
  const warnings = [];
18276
18381
  const seen = new Map;
18277
18382
  const collectIsland = (file, source) => {
18278
- const base = basename2(file);
18383
+ const base = basename3(file);
18279
18384
  const ext = base.match(ISLAND_FILE)?.groups?.ext;
18280
18385
  const framework = ext ? FRAMEWORK_BY_EXT2.get(ext) : undefined;
18281
18386
  if (!framework) {
@@ -18403,7 +18508,7 @@ var resolveAstroPackageJson = (modulesDir) => {
18403
18508
  }
18404
18509
  };
18405
18510
  var resolvedAstroHit = (fromDir) => {
18406
- let dir = normalize3(fromDir);
18511
+ let dir = normalize4(fromDir);
18407
18512
  while (true) {
18408
18513
  const modulesDir = join28(dir, "node_modules");
18409
18514
  const pkg = resolveAstroPackageJson(modulesDir);
@@ -18428,7 +18533,7 @@ var depsCandidates = (pkgDir) => [
18428
18533
  join28(pkgDir, "node_modules"),
18429
18534
  dirname11(pkgDir)
18430
18535
  ];
18431
- var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync16(join28(dir, ...segments))) ?? null;
18536
+ var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync15(join28(dir, ...segments))) ?? null;
18432
18537
  var linkDepsJunction = async (link, depsDir) => {
18433
18538
  let existing;
18434
18539
  try {
@@ -18564,7 +18669,7 @@ var writeExamplesPreview = async (options) => {
18564
18669
  await write(join28(srcDir, "pages", ...config.basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro"), examplesPageTemplate());
18565
18670
  }
18566
18671
  const cssFile = examplesCssFile(root, config);
18567
- return cssFile && !existsSync16(cssFile) ? [
18672
+ return cssFile && !existsSync15(cssFile) ? [
18568
18673
  `examples.css points at "${config.examples.css}", which doesn't exist; previews render without it.`
18569
18674
  ] : [];
18570
18675
  };
@@ -18630,7 +18735,7 @@ var pruneOrphans = async (srcDir, written) => {
18630
18735
  });
18631
18736
  const removals = [];
18632
18737
  for (const path of existing) {
18633
- const normalized = normalize3(path);
18738
+ const normalized = normalize4(path);
18634
18739
  if (!written.has(normalized)) {
18635
18740
  removals.push(rm2(normalized, { force: true }));
18636
18741
  }
@@ -18651,10 +18756,10 @@ var writeStagedContent = async (out, staged) => {
18651
18756
  const written = new Set;
18652
18757
  await Promise.all([...staged].map(async ([entryId, text]) => {
18653
18758
  const path = join28(contentDir, entryId);
18654
- written.add(normalize3(path));
18759
+ written.add(normalize4(path));
18655
18760
  await writeIfChanged(path, text);
18656
18761
  }));
18657
- if (existsSync16(contentDir)) {
18762
+ if (existsSync15(contentDir)) {
18658
18763
  await pruneOrphans(contentDir, written);
18659
18764
  }
18660
18765
  };
@@ -18677,7 +18782,7 @@ var readLogoSvg = (project, source) => {
18677
18782
  const file = [
18678
18783
  join28(project.context.root, "public", rel),
18679
18784
  join28(project.context.root, rel)
18680
- ].find((path) => existsSync16(path));
18785
+ ].find((path) => existsSync15(path));
18681
18786
  return file ? readFileSync8(file, "utf-8") : undefined;
18682
18787
  };
18683
18788
  var isStringShorthand = (value) => typeof value === "string";
@@ -18711,6 +18816,7 @@ var FAVICON_CANDIDATES = [
18711
18816
  "favicon.ico",
18712
18817
  "icon.ico"
18713
18818
  ];
18819
+ var darkSibling = (name) => name.replace(/\.(?=[^.]+$)/u, "-dark.");
18714
18820
  var FAVICON_TYPES = new Map([
18715
18821
  ["ico", "image/x-icon"],
18716
18822
  ["jpeg", "image/jpeg"],
@@ -18724,6 +18830,10 @@ var faviconType = (name) => {
18724
18830
  };
18725
18831
  var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync8(file).toString("base64")}`;
18726
18832
  var defaultFavicon = () => ({
18833
+ dark: {
18834
+ href: inlineDataUri(join28(BLUME_SRC, "assets", "icon-dark.png"), "image/png"),
18835
+ type: "image/png"
18836
+ },
18727
18837
  href: inlineDataUri(join28(BLUME_SRC, "assets", "icon.png"), "image/png"),
18728
18838
  type: "image/png"
18729
18839
  });
@@ -18736,20 +18846,43 @@ var APPLE_ICON_CANDIDATES = [
18736
18846
  var resolveIconFile = (project, candidates) => {
18737
18847
  const { root } = project.context;
18738
18848
  for (const name of candidates) {
18739
- if (existsSync16(join28(root, "public", name))) {
18849
+ if (existsSync15(join28(root, "public", name))) {
18740
18850
  return { href: `/${name}`, type: faviconType(name) };
18741
18851
  }
18742
18852
  }
18743
18853
  for (const name of candidates) {
18744
18854
  const file = join28(root, name);
18745
- if (existsSync16(file)) {
18855
+ if (existsSync15(file)) {
18746
18856
  const type = faviconType(name);
18747
18857
  return { href: inlineDataUri(file, type ?? "image/x-icon"), type };
18748
18858
  }
18749
18859
  }
18750
18860
  return null;
18751
18861
  };
18752
- var resolveFavicon = (project) => resolveIconFile(project, FAVICON_CANDIDATES) ?? defaultFavicon();
18862
+ var resolveFavicon = (project) => {
18863
+ const { root } = project.context;
18864
+ for (const name of FAVICON_CANDIDATES) {
18865
+ if (existsSync15(join28(root, "public", name))) {
18866
+ const type = faviconType(name);
18867
+ const sibling = darkSibling(name);
18868
+ return existsSync15(join28(root, "public", sibling)) ? { dark: { href: `/${sibling}`, type }, href: `/${name}`, type } : { href: `/${name}`, type };
18869
+ }
18870
+ }
18871
+ for (const name of FAVICON_CANDIDATES) {
18872
+ const file = join28(root, name);
18873
+ if (existsSync15(file)) {
18874
+ const type = faviconType(name);
18875
+ const mime = type ?? "image/x-icon";
18876
+ const siblingFile = join28(root, darkSibling(name));
18877
+ return existsSync15(siblingFile) ? {
18878
+ dark: { href: inlineDataUri(siblingFile, mime), type },
18879
+ href: inlineDataUri(file, mime),
18880
+ type
18881
+ } : { href: inlineDataUri(file, mime), type };
18882
+ }
18883
+ }
18884
+ return resolveIconFile(project, FAVICON_CANDIDATES.map(darkSibling)) ?? defaultFavicon();
18885
+ };
18753
18886
  var resolveAppleIcon = (project) => resolveIconFile(project, APPLE_ICON_CANDIDATES);
18754
18887
  var resolveBanner = (config) => {
18755
18888
  const { banner } = config;
@@ -18876,6 +19009,7 @@ var buildRuntimeData = (project) => {
18876
19009
  palette: config.seo.og.palette,
18877
19010
  site: resolveOgSite(config)
18878
19011
  },
19012
+ openInChat: config.ai.openInChat,
18879
19013
  repoUrl,
18880
19014
  search: {
18881
19015
  enabled: config.search.provider !== "none",
@@ -18989,6 +19123,30 @@ var writeMcpFiles = async (project, plan, write) => {
18989
19123
  write(join28(plan.dir, "server-card.ts"), staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)))
18990
19124
  ]);
18991
19125
  };
19126
+ var planPlaygroundProxy = (config, srcDir) => ({
19127
+ enabled: config.openapi.enabled && config.openapi.renderer === "blume" && config.openapi.playground.enabled && config.openapi.playground.proxy === true,
19128
+ entrypoint: join28(srcDir, "blume-openapi", "api-proxy.ts"),
19129
+ pattern: "/_api-proxy"
19130
+ });
19131
+ var specOrigins = (data) => {
19132
+ const origins = new Set;
19133
+ for (const spec of Object.values(data)) {
19134
+ const { servers } = spec.document;
19135
+ for (const server of Array.isArray(servers) ? servers : []) {
19136
+ const url = server.url ?? "";
19137
+ if (url.includes("{")) {
19138
+ continue;
19139
+ }
19140
+ try {
19141
+ origins.add(new URL(url).origin);
19142
+ } catch {}
19143
+ }
19144
+ }
19145
+ return [...origins].toSorted();
19146
+ };
19147
+ var proxyAllowlistWarnings = (enabled, origins) => enabled && origins.length === 0 ? [
19148
+ "openapi.playground.proxy is enabled, but no spec declares an absolute servers[].url (relative and templated URLs carry no origin), so the proxy's allowlist is empty and it will refuse every request. Add an absolute server URL to the spec, or point playground.proxy at an external proxy URL."
19149
+ ] : [];
18992
19150
  var writeAskFiles = async (project, srcDir, write) => {
18993
19151
  const { ask } = project.config.ai;
18994
19152
  if (!(ask?.enabled && !ask.endpoint)) {
@@ -18999,7 +19157,10 @@ var writeAskFiles = async (project, srcDir, write) => {
18999
19157
  await write(join28(srcDir, "generated", "ask-data.json"), `${JSON.stringify(await buildAskData(project))}
19000
19158
  `);
19001
19159
  }
19002
- await write(join28(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded, ask.instructions));
19160
+ await write(join28(srcDir, "pages", "api", "ask.ts"), askEndpointTemplate(resolveAskBackend(ask), grounded, {
19161
+ instructions: ask.instructions,
19162
+ retrieval: ask.retrieval
19163
+ }));
19003
19164
  };
19004
19165
  var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
19005
19166
  if (routeIsTaken(pages, contentPages, "/404")) {
@@ -19047,7 +19208,7 @@ var generateRuntime = async (project) => {
19047
19208
  const openapiPath = join28(srcDir, "generated", "openapi.json");
19048
19209
  const written = new Set;
19049
19210
  const write = (path, content) => {
19050
- written.add(normalize3(path));
19211
+ written.add(normalize4(path));
19051
19212
  return writeIfChanged(path, content);
19052
19213
  };
19053
19214
  const depsLinkWarning = await ensureDepsLink(out);
@@ -19095,6 +19256,16 @@ var generateRuntime = async (project) => {
19095
19256
  const changelogIndex = hasGeneratedChangelog(project, pages);
19096
19257
  const mcp = planMcp(project, srcDir, pages);
19097
19258
  pages.push(...mcp.discoveryPages);
19259
+ const openApiSource2 = project.sources.find(isOpenApiSource);
19260
+ const openApiData = openApiSource2 ? openApiSource2.openApiData() : {};
19261
+ const playgroundProxy = planPlaygroundProxy(config, srcDir);
19262
+ if (playgroundProxy.enabled) {
19263
+ pages.push({
19264
+ entrypoint: playgroundProxy.entrypoint,
19265
+ pattern: playgroundProxy.pattern
19266
+ });
19267
+ }
19268
+ const proxyOrigins = specOrigins(openApiData);
19098
19269
  const hasStaged = staged.size > 0;
19099
19270
  const hasFilesystemSource = project.sources.some((source) => !source.staged);
19100
19271
  const docsCollection = resolveDocsCollection(config, context);
@@ -19159,7 +19330,8 @@ var generateRuntime = async (project) => {
19159
19330
  Promise.all(slotPlan.wrappers.map((wrapper) => write(join28(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
19160
19331
  Promise.all(exampleDiscovery.examples.map((example) => write(join28(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
19161
19332
  writeAskFiles(project, srcDir, write),
19162
- writeMcpFiles(project, mcp, write)
19333
+ writeMcpFiles(project, mcp, write),
19334
+ playgroundProxy.enabled ? write(playgroundProxy.entrypoint, playgroundProxyTemplate(proxyOrigins)) : Promise.resolve(false)
19163
19335
  ]);
19164
19336
  if (config.seo.og.enabled) {
19165
19337
  await write(join28(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes, projectOgFonts(project), changelogIndex));
@@ -19214,6 +19386,7 @@ var generateRuntime = async (project) => {
19214
19386
  }
19215
19387
  const warnings = [
19216
19388
  ...depsLinkWarning ? [depsLinkWarning] : [],
19389
+ ...proxyAllowlistWarnings(playgroundProxy.enabled, proxyOrigins),
19217
19390
  ...reactCompilerWarnings(config, needsReact, reactCompilerPath),
19218
19391
  ...mcp.warnings,
19219
19392
  ...islandDiscovery.warnings,
@@ -19247,10 +19420,9 @@ var generateRuntime = async (project) => {
19247
19420
  warnings.push(...references.warnings);
19248
19421
  await Promise.all(references.files.map((file) => write(join28(srcDir, "pages", file.pagePath), file.content)));
19249
19422
  }
19250
- const openApiSource2 = project.sources.find(isOpenApiSource);
19251
19423
  await Promise.all([
19252
19424
  write(join28(srcDir, "generated", "data.json"), buildRuntimeData(project)),
19253
- write(openapiPath, `${JSON.stringify(openApiSource2 ? openApiSource2.openApiData() : {})}
19425
+ write(openapiPath, `${JSON.stringify(openApiData)}
19254
19426
  `),
19255
19427
  write(join28(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
19256
19428
  `),
@@ -19261,7 +19433,7 @@ var generateRuntime = async (project) => {
19261
19433
  };
19262
19434
 
19263
19435
  // src/cli/env.ts
19264
- import { existsSync as existsSync17 } from "node:fs";
19436
+ import { existsSync as existsSync16 } from "node:fs";
19265
19437
  import { config } from "dotenv";
19266
19438
  import { dirname as dirname12, join as join29, resolve as resolve9 } from "pathe";
19267
19439
  var loadEnvFiles = (startDir) => {
@@ -19271,7 +19443,7 @@ var loadEnvFiles = (startDir) => {
19271
19443
  while (!done) {
19272
19444
  paths.push(join29(dir, ".env.local"), join29(dir, ".env"));
19273
19445
  const parent = dirname12(dir);
19274
- done = existsSync17(join29(dir, ".git")) || parent === dir;
19446
+ done = existsSync16(join29(dir, ".git")) || parent === dir;
19275
19447
  dir = parent;
19276
19448
  }
19277
19449
  config({ path: paths, quiet: true });
@@ -19383,17 +19555,17 @@ var emitRedirectFiles = async (config2, distDir) => {
19383
19555
  { content: buildNetlifyRedirects(redirects), name: "_redirects" },
19384
19556
  { content: buildVercelConfig(redirects), name: "vercel.json" }
19385
19557
  ];
19386
- await Promise.all(platformFiles.map((file) => existsSync18(join30(distDir, file.name)) ? Promise.resolve() : writeFile7(join30(distDir, file.name), file.content, "utf-8")));
19558
+ await Promise.all(platformFiles.map((file) => existsSync17(join30(distDir, file.name)) ? Promise.resolve() : writeFile7(join30(distDir, file.name), file.content, "utf-8")));
19387
19559
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
19388
19560
  };
19389
19561
  var emitHeaderFiles = async (project, distDir) => {
19390
19562
  const { config: config2 } = project;
19391
- if (!readsHeaderFiles(config2.deployment) || existsSync18(join30(project.context.root, "public", "_headers"))) {
19563
+ if (!readsHeaderFiles(config2.deployment) || existsSync17(join30(project.context.root, "public", "_headers"))) {
19392
19564
  return;
19393
19565
  }
19394
19566
  const ours = buildNetlifyHeaders(config2, buildHomeLinkHeader(config2, markdownRoutePaths(project)));
19395
19567
  const target = join30(distDir, "_headers");
19396
- const existing = existsSync18(target) ? await readFile17(target, "utf-8") : "";
19568
+ const existing = existsSync17(target) ? await readFile17(target, "utf-8") : "";
19397
19569
  await writeFile7(target, existing ? `${existing.trimEnd()}
19398
19570
  ${ours}` : ours, "utf-8");
19399
19571
  logger.success("Emitted _headers (UTF-8 Content-Type + homepage Link header)");
@@ -19404,12 +19576,12 @@ var emitAgentSkills = async (project, distDir) => {
19404
19576
  return;
19405
19577
  }
19406
19578
  const dir = resolve10(project.context.root, configured);
19407
- if (!existsSync18(dir)) {
19579
+ if (!existsSync17(dir)) {
19408
19580
  logger.warn(`ai.skills points at "${configured}" (${dir}), which does not exist; no skills published.`);
19409
19581
  return;
19410
19582
  }
19411
19583
  const outDir = join30(distDir, AGENT_SKILLS_DIR.slice(1));
19412
- if (existsSync18(join30(outDir, "index.json"))) {
19584
+ if (existsSync17(join30(outDir, "index.json"))) {
19413
19585
  return;
19414
19586
  }
19415
19587
  const { skills, warnings } = await collectSkills(dir);
@@ -19443,7 +19615,7 @@ var emitWellKnownFiles = async (config2, distDir) => {
19443
19615
  ];
19444
19616
  for (const file of files) {
19445
19617
  const target = join30(distDir, file.path.slice(1));
19446
- if (!file.content || existsSync18(target)) {
19618
+ if (!file.content || existsSync17(target)) {
19447
19619
  continue;
19448
19620
  }
19449
19621
  await mkdir8(join30(distDir, ".well-known"), { recursive: true });
@@ -19454,7 +19626,7 @@ var emitWellKnownFiles = async (config2, distDir) => {
19454
19626
  var emitVercelNegotiation = async (project, routePaths, root) => {
19455
19627
  const { config: config2 } = project;
19456
19628
  const configPath = join30(root, ".vercel", "output", "config.json");
19457
- if (!existsSync18(configPath)) {
19629
+ if (!existsSync17(configPath)) {
19458
19630
  return;
19459
19631
  }
19460
19632
  const overrides = {};
@@ -19479,7 +19651,7 @@ var emitCloudflareNegotiation = async (project, routePaths) => {
19479
19651
  const { config: config2, context } = project;
19480
19652
  const serverDir = join30(context.distDir ?? join30(context.root, "dist"), "server");
19481
19653
  const wranglerPath = join30(serverDir, "wrangler.json");
19482
- if (!existsSync18(wranglerPath)) {
19654
+ if (!existsSync17(wranglerPath)) {
19483
19655
  warnCloudflareNegotiationSkipped();
19484
19656
  return;
19485
19657
  }
@@ -19510,7 +19682,7 @@ var formatBytes2 = (bytes) => {
19510
19682
  };
19511
19683
  var astroAssets = async (distDir, ext) => {
19512
19684
  const astroDir = join30(distDir, "_astro");
19513
- if (!existsSync18(astroDir)) {
19685
+ if (!existsSync17(astroDir)) {
19514
19686
  return [];
19515
19687
  }
19516
19688
  const entries = await readdir2(astroDir);
@@ -19587,8 +19759,8 @@ var isolatedStaticDir = (config2, context) => {
19587
19759
  var publishLlmsFiles = async (project, distDir) => {
19588
19760
  const indexPath = join30(distDir, "llms.txt");
19589
19761
  const fullPath = join30(distDir, "llms-full.txt");
19590
- const writeIndex = !existsSync18(indexPath);
19591
- const writeFull = !existsSync18(fullPath);
19762
+ const writeIndex = !existsSync17(indexPath);
19763
+ const writeFull = !existsSync17(fullPath);
19592
19764
  if (!(writeIndex || writeFull)) {
19593
19765
  return;
19594
19766
  }
@@ -19621,17 +19793,17 @@ var publishBuildArtifacts = async (project, distDir, args) => {
19621
19793
  await publishLlmsFiles(project, distDir);
19622
19794
  }
19623
19795
  const sitemapFiles = buildSitemapFiles(project);
19624
- if (sitemapFiles && !existsSync18(join30(distDir, "sitemap.xml"))) {
19796
+ if (sitemapFiles && !existsSync17(join30(distDir, "sitemap.xml"))) {
19625
19797
  await Promise.all(sitemapFiles.map((file) => writeFile7(join30(distDir, file.name), file.xml, "utf-8")));
19626
19798
  logger.success(sitemapFiles.length === 1 ? "Generated sitemap.xml" : `Generated sitemap.xml (index of ${sitemapFiles.length - 1} sitemap files)`);
19627
19799
  }
19628
19800
  const robots = buildRobots(project);
19629
- if (robots && !existsSync18(join30(distDir, "robots.txt"))) {
19801
+ if (robots && !existsSync17(join30(distDir, "robots.txt"))) {
19630
19802
  await writeFile7(join30(distDir, "robots.txt"), robots, "utf-8");
19631
19803
  logger.success("Generated robots.txt");
19632
19804
  }
19633
19805
  const agentReadability = buildAgentReadability(project);
19634
- if (agentReadability && !existsSync18(join30(distDir, "agent-readability.json"))) {
19806
+ if (agentReadability && !existsSync17(join30(distDir, "agent-readability.json"))) {
19635
19807
  await writeFile7(join30(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
19636
19808
  `, "utf-8");
19637
19809
  logger.success("Generated agent-readability.json");
@@ -19764,7 +19936,7 @@ var buildCommand = defineCommand3({
19764
19936
  });
19765
19937
 
19766
19938
  // src/cli/commands/check.ts
19767
- import { existsSync as existsSync19 } from "node:fs";
19939
+ import { existsSync as existsSync18 } from "node:fs";
19768
19940
  import { check } from "@astrojs/check";
19769
19941
  import { sync } from "astro";
19770
19942
  import { defineCommand as defineCommand4 } from "citty";
@@ -19810,7 +19982,7 @@ var checkCommand = defineCommand4({
19810
19982
  minimumFailingSeverity: "error",
19811
19983
  minimumSeverity: "hint",
19812
19984
  root: outDir,
19813
- tsconfig: existsSync19(tsconfig) ? tsconfig : undefined,
19985
+ tsconfig: existsSync18(tsconfig) ? tsconfig : undefined,
19814
19986
  watch: false
19815
19987
  });
19816
19988
  if (failed) {
@@ -20092,13 +20264,13 @@ import { defineCommand as defineCommand7 } from "citty";
20092
20264
  import { relative as relative17 } from "pathe";
20093
20265
 
20094
20266
  // src/registry/eject.ts
20095
- import { existsSync as existsSync20 } from "node:fs";
20267
+ import { existsSync as existsSync19 } from "node:fs";
20096
20268
  import { cp as cp2, mkdir as mkdir9, readFile as readFile18, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
20097
20269
  import { join as join33, relative as relative15 } from "pathe";
20098
20270
  var toPosix = (path) => path.split("\\").join("/");
20099
20271
  var LOCAL_BLUME_SOURCE = "../../node_modules/blume/src/**/*.{astro,ts,tsx}";
20100
20272
  var blumeSourceGlob = (root, genDir, resolveBlumeRoot = packageRoot) => {
20101
- if (existsSync20(join33(root, "node_modules", "blume"))) {
20273
+ if (existsSync19(join33(root, "node_modules", "blume"))) {
20102
20274
  return LOCAL_BLUME_SOURCE;
20103
20275
  }
20104
20276
  try {
@@ -20117,7 +20289,7 @@ var askFiles = async (project, srcDir, genDir) => {
20117
20289
  const { ask } = project.config.ai;
20118
20290
  if (!(ask?.enabled && !ask.endpoint)) {
20119
20291
  const endpointPath = join33(srcDir, "pages", "api", "ask.ts");
20120
- if (existsSync20(endpointPath)) {
20292
+ if (existsSync19(endpointPath)) {
20121
20293
  const content = await readFile18(endpointPath, "utf-8");
20122
20294
  if (content.startsWith("// Generated by Blume. Do not edit.")) {
20123
20295
  await rm3(endpointPath, { force: true });
@@ -20129,7 +20301,10 @@ var askFiles = async (project, srcDir, genDir) => {
20129
20301
  const grounded = ask.provider !== "inkeep";
20130
20302
  const files = [
20131
20303
  {
20132
- content: askEndpointTemplate(resolveAskBackend(ask), grounded, ask.instructions),
20304
+ content: askEndpointTemplate(resolveAskBackend(ask), grounded, {
20305
+ instructions: ask.instructions,
20306
+ retrieval: ask.retrieval
20307
+ }),
20133
20308
  path: join33(srcDir, "pages", "api", "ask.ts")
20134
20309
  }
20135
20310
  ];
@@ -20199,7 +20374,7 @@ var changelogFiles = (project, userPages, srcDir, options) => {
20199
20374
  }
20200
20375
  ];
20201
20376
  };
20202
- var readExamplesCss = (root, css) => css && existsSync20(join33(root, css)) ? readFile18(join33(root, css), "utf-8") : Promise.resolve("");
20377
+ var readExamplesCss = (root, css) => css && existsSync19(join33(root, css)) ? readFile18(join33(root, css), "utf-8") : Promise.resolve("");
20203
20378
  var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
20204
20379
  {
20205
20380
  content: examplesPageTemplate(),
@@ -20438,13 +20613,13 @@ var eject = async (root) => {
20438
20613
  for (const [entryId, content] of staged) {
20439
20614
  files.push({ content, path: join33(root, stagedDir, entryId) });
20440
20615
  }
20441
- const written = files.filter((file) => !(file.skipIfExists && existsSync20(file.path)));
20616
+ const written = files.filter((file) => !(file.skipIfExists && existsSync19(file.path)));
20442
20617
  await Promise.all(written.map(async (file) => {
20443
20618
  await mkdir9(join33(file.path, ".."), { recursive: true });
20444
20619
  await writeFile8(file.path, file.content, "utf-8");
20445
20620
  }));
20446
20621
  const assetsSrc = join33(context.outDir, "public", "blume-assets");
20447
- if (existsSync20(assetsSrc)) {
20622
+ if (existsSync19(assetsSrc)) {
20448
20623
  await cp2(assetsSrc, join33(root, "public", "blume-assets"), {
20449
20624
  recursive: true
20450
20625
  });
@@ -20501,10 +20676,10 @@ var updatePackageScripts = async (root) => {
20501
20676
  };
20502
20677
 
20503
20678
  // src/cli/init/scaffold.ts
20504
- import { existsSync as existsSync21 } from "node:fs";
20679
+ import { existsSync as existsSync20 } from "node:fs";
20505
20680
  import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
20506
20681
  import { detect } from "package-manager-detector/detect";
20507
- import { basename as basename4, dirname as dirname14, isAbsolute as isAbsolute9, join as join35, relative as relative16 } from "pathe";
20682
+ import { basename as basename5, dirname as dirname14, isAbsolute as isAbsolute9, join as join35, relative as relative16 } from "pathe";
20508
20683
 
20509
20684
  // src/core/package-json.ts
20510
20685
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
@@ -20713,7 +20888,7 @@ var extraDepsFor = (sources) => {
20713
20888
  var buildPlan = (root, answers) => {
20714
20889
  const files = [
20715
20890
  {
20716
- content: blumePackageJson(toPackageName(basename4(root)), extraDepsFor(answers.sources)),
20891
+ content: blumePackageJson(toPackageName(basename5(root)), extraDepsFor(answers.sources)),
20717
20892
  path: join35(root, "package.json")
20718
20893
  },
20719
20894
  { content: buildConfig(answers), path: join35(root, "blume.config.ts") }
@@ -20724,7 +20899,7 @@ var buildPlan = (root, answers) => {
20724
20899
  return files;
20725
20900
  };
20726
20901
  var writeFileSafe = async (file, log) => {
20727
- if (existsSync21(file.path)) {
20902
+ if (existsSync20(file.path)) {
20728
20903
  log.info(`Skipped existing ${file.path}`);
20729
20904
  return false;
20730
20905
  }
@@ -20735,7 +20910,7 @@ var writeFileSafe = async (file, log) => {
20735
20910
  };
20736
20911
  var applyPlan = async (files, log) => {
20737
20912
  const created = await Promise.all(files.map((file) => writeFileSafe(file, log)));
20738
- const createdPackage = files.some((file, index) => created[index] && basename4(file.path) === "package.json");
20913
+ const createdPackage = files.some((file, index) => created[index] && basename5(file.path) === "package.json");
20739
20914
  return { createdPackage };
20740
20915
  };
20741
20916
  var envVarsFor = (sources) => [
@@ -20820,7 +20995,7 @@ The blume package remains importable.`);
20820
20995
  });
20821
20996
 
20822
20997
  // src/cli/commands/eval.ts
20823
- import { existsSync as existsSync22 } from "node:fs";
20998
+ import { existsSync as existsSync21 } from "node:fs";
20824
20999
  import { defineCommand as defineCommand8 } from "citty";
20825
21000
  import { join as join39 } from "pathe";
20826
21001
 
@@ -21544,7 +21719,7 @@ var runFixHandoff = async (agent, result, root, threshold) => {
21544
21719
  };
21545
21720
  var runInit = async (agent, file) => {
21546
21721
  const path = join39(process.cwd(), file);
21547
- if (existsSync22(path)) {
21722
+ if (existsSync21(path)) {
21548
21723
  logger.error(`${file} already exists — edit it directly, or pass --file to draft elsewhere.`);
21549
21724
  process.exit(1);
21550
21725
  }
@@ -21682,7 +21857,7 @@ import { defineCommand as defineCommand9 } from "citty";
21682
21857
  import { resolve as resolve12 } from "pathe";
21683
21858
 
21684
21859
  // src/cli/init/questions.ts
21685
- import { basename as basename5, resolve as resolve11 } from "pathe";
21860
+ import { basename as basename6, resolve as resolve11 } from "pathe";
21686
21861
  var cancelled = (value) => typeof value === "symbol";
21687
21862
  var collectAnswers = async (prompter, flags, defaults) => {
21688
21863
  const directory = flags.directory ?? await prompter.text({
@@ -21695,7 +21870,7 @@ var collectAnswers = async (prompter, flags, defaults) => {
21695
21870
  }
21696
21871
  const root = resolve11(defaults.cwd, directory);
21697
21872
  const title = await prompter.text({
21698
- initialValue: titleize(basename5(root)),
21873
+ initialValue: titleize(basename6(root)),
21699
21874
  message: "What's your docs site called?",
21700
21875
  validate: (value) => value?.trim() ? undefined : "Give your docs site a name."
21701
21876
  });
@@ -21922,10 +22097,30 @@ var SCHEMA = {
21922
22097
  };
21923
22098
  var toFacetTerms = (facets) => Object.entries(facets).map(([key, value]) => `${key}:${value}`);
21924
22099
  var BOOST = { description: 2, title: 3 };
21925
- var SEGMENTED_LANGUAGES = new Set(["ja", "ko", "th", "zh"]);
21926
- var BIGRAM_LANGUAGES = new Set(["ja", "zh"]);
22100
+ var LATIN_SCRIPT = "Latn";
22101
+ var parseLocale = (tag) => {
22102
+ const hyphenated = tag.replaceAll("_", "-");
22103
+ try {
22104
+ return new Intl.Locale(hyphenated);
22105
+ } catch {}
22106
+ const [primary = ""] = hyphenated.split("-");
22107
+ try {
22108
+ return new Intl.Locale(primary);
22109
+ } catch {
22110
+ return;
22111
+ }
22112
+ };
22113
+ var resolveLocale = (tag) => parseLocale(tag)?.maximize();
22114
+ var BIGRAM_INDEX_SCRIPTS = new Set(["Hans", "Hant", "Jpan"]);
22115
+ var isBigramLanguage = (language) => {
22116
+ const script = resolveLocale(language)?.script;
22117
+ return script !== undefined && BIGRAM_INDEX_SCRIPTS.has(script);
22118
+ };
21927
22119
  var BIGRAM_SCRIPTS = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}々〆〇ー゙゚]+$/u;
21928
22120
  var TERM = /[\p{L}\p{M}\p{N}]+(?:(?:['’](?=\p{L})|(?<=\p{N})[.,](?=\p{N}))[\p{L}\p{M}\p{N}]+)*/gu;
22121
+ var LATIN_TERM = /^[\p{Script=Latin}\p{N}'’.,]+$/u;
22122
+ var MARKS = /\p{M}+/gu;
22123
+ var foldDiacritics = (term) => LATIN_TERM.test(term) ? term.normalize("NFD").replace(MARKS, "") : term;
21929
22124
  var addBigrams = (run, tokens) => {
21930
22125
  const characters = [...run];
21931
22126
  if (characters.length === 1) {
@@ -21942,15 +22137,16 @@ var addBigrams = (run, tokens) => {
21942
22137
  };
21943
22138
  var hasSegmenter = (segmenter) => typeof segmenter === "function";
21944
22139
  var segmentingTokenizer = (locale) => {
21945
- const language = locale?.toLowerCase().split(/[-_]/u)[0] ?? "";
21946
- if (!SEGMENTED_LANGUAGES.has(language)) {
22140
+ const resolved = resolveLocale(locale ?? "");
22141
+ if (!resolved?.script || resolved.script === LATIN_SCRIPT) {
21947
22142
  return;
21948
22143
  }
21949
22144
  if (!hasSegmenter(Intl.Segmenter)) {
21950
22145
  return;
21951
22146
  }
22147
+ const { language } = resolved;
21952
22148
  const segmenter = new Intl.Segmenter(language, { granularity: "word" });
21953
- const bigram = BIGRAM_LANGUAGES.has(language);
22149
+ const bigram = isBigramLanguage(language);
21954
22150
  return {
21955
22151
  language,
21956
22152
  normalizationCache: new Map,
@@ -21969,7 +22165,7 @@ var segmentingTokenizer = (locale) => {
21969
22165
  return;
21970
22166
  }
21971
22167
  flush();
21972
- tokens.add(term);
22168
+ tokens.add(foldDiacritics(term));
21973
22169
  };
21974
22170
  for (const segment of segmenter.segment(raw.normalize("NFC").toLowerCase())) {
21975
22171
  if (!segment.isWordLike) {
@@ -22022,7 +22218,7 @@ var queryOramaIndex = async (db, term, limit, filters) => {
22022
22218
  term
22023
22219
  };
22024
22220
  const params = Object.keys(where).length > 0 ? { ...unfiltered, where } : unfiltered;
22025
- const bigrammed = BIGRAM_LANGUAGES.has(db.tokenizer?.language ?? "");
22221
+ const bigrammed = isBigramLanguage(db.tokenizer?.language ?? "");
22026
22222
  const strict = bigrammed ? await search(db, { ...params, threshold: ALL_TOKENS }) : undefined;
22027
22223
  const found = strict && strict.hits.length > 0 ? strict : await search(db, params);
22028
22224
  return found.hits.map((hit) => hit.document);
@@ -22269,7 +22465,7 @@ var mcpStdioCommand = defineCommand10({
22269
22465
  });
22270
22466
 
22271
22467
  // src/cli/commands/preview.ts
22272
- import { existsSync as existsSync23 } from "node:fs";
22468
+ import { existsSync as existsSync22 } from "node:fs";
22273
22469
  import { preview } from "astro";
22274
22470
  import { defineCommand as defineCommand11 } from "citty";
22275
22471
  import { join as join40 } from "pathe";
@@ -22286,7 +22482,7 @@ var previewCommand = defineCommand11({
22286
22482
  const root = process.cwd();
22287
22483
  const { config: config2 } = await loadConfig(root);
22288
22484
  const context = resolveProjectContext(root, config2);
22289
- if (!existsSync23(join40(context.outDir, "astro.config.mjs"))) {
22485
+ if (!existsSync22(join40(context.outDir, "astro.config.mjs"))) {
22290
22486
  logger.error("No build found. Run `blume build` first.");
22291
22487
  process.exit(1);
22292
22488
  }
@@ -22637,7 +22833,7 @@ var createProgressRenderer = (options) => {
22637
22833
  };
22638
22834
 
22639
22835
  // src/translate/run.ts
22640
- import { existsSync as existsSync24 } from "node:fs";
22836
+ import { existsSync as existsSync23 } from "node:fs";
22641
22837
  import { mkdtemp as mkdtemp4, readFile as readFile25 } from "node:fs/promises";
22642
22838
  import { tmpdir as tmpdir4 } from "node:os";
22643
22839
  import pLimit2 from "p-limit";
@@ -22882,7 +23078,7 @@ var runPageItem = async (item, index, context, ledger) => {
22882
23078
  });
22883
23079
  const sourceText = await readFile25(item.sourcePath, "utf-8");
22884
23080
  const target = context.targets.get(item.locale);
22885
- const previousTranslation = existsSync24(item.targetPath) ? await readFile25(item.targetPath, "utf-8") : undefined;
23081
+ const previousTranslation = existsSync23(item.targetPath) ? await readFile25(item.targetPath, "utf-8") : undefined;
22886
23082
  const output = await invokeAgent(context, pagePrompt(sourceText, target, context.source, previousTranslation), index);
22887
23083
  if (output.isError) {
22888
23084
  return done("failed", output.detail ?? "agent failed", output.costUsd);
@@ -23003,7 +23199,7 @@ var runTranslate = async (options) => {
23003
23199
  };
23004
23200
 
23005
23201
  // src/translate/work-list.ts
23006
- import { existsSync as existsSync25 } from "node:fs";
23202
+ import { existsSync as existsSync24 } from "node:fs";
23007
23203
  import { readFile as readFile26 } from "node:fs/promises";
23008
23204
  import { dirname as dirname16, extname as extname8, join as join45, relative as relative20 } from "pathe";
23009
23205
  var PAGE_EXTENSIONS = new Set([".md", ".mdx"]);
@@ -23085,7 +23281,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
23085
23281
  for (const source of meta.metas) {
23086
23282
  knownSources.add(source.sourceRel);
23087
23283
  const targetDir = dirname16(metaTargetPath(source, locale));
23088
- const exists = ["meta.ts", "meta.js", "meta.mjs"].some((name) => existsSync25(join45(targetDir, name)));
23284
+ const exists = ["meta.ts", "meta.js", "meta.mjs"].some((name) => existsSync24(join45(targetDir, name)));
23089
23285
  const hash = hashSource(source.raw);
23090
23286
  const stamp = ledger.files[source.sourceRel]?.[locale];
23091
23287
  const entry = (status) => ({
@@ -23317,13 +23513,13 @@ var translateCommand = defineCommand13({
23317
23513
  });
23318
23514
 
23319
23515
  // src/cli/commands/validate.ts
23320
- import { existsSync as existsSync27 } from "node:fs";
23516
+ import { existsSync as existsSync26 } from "node:fs";
23321
23517
  import { defineCommand as defineCommand14 } from "citty";
23322
23518
  import { join as join47 } from "pathe";
23323
23519
 
23324
23520
  // src/core/links.ts
23325
- import { existsSync as existsSync26 } from "node:fs";
23326
- import { basename as basename6, join as join46 } from "pathe";
23521
+ import { existsSync as existsSync25 } from "node:fs";
23522
+ import { basename as basename7, dirname as dirname17, join as join46 } from "pathe";
23327
23523
  var HTTP = /^https?:\/\//iu;
23328
23524
  var PROTOCOL_RELATIVE = /^\/\//u;
23329
23525
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -23336,9 +23532,9 @@ var decodePercent = (value) => {
23336
23532
  };
23337
23533
  var DOC_EXT = /\.(?:md|mdx)$/iu;
23338
23534
  var FILE_EXT = /\.[a-z0-9]+$/iu;
23339
- var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync26(join46(ctx.publicDir, resolved));
23535
+ var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync25(join46(ctx.publicDir, resolved));
23340
23536
  var NUMERIC_PREFIX3 = /^\d+[-_.]/u;
23341
- var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename6(page2.navPath).replace(NUMERIC_PREFIX3, ""));
23537
+ var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename7(page2.navPath).replace(NUMERIC_PREFIX3, ""));
23342
23538
  var applyRelativePart = (segments, part) => {
23343
23539
  if (part === "" || part === ".") {
23344
23540
  return;
@@ -23387,7 +23583,7 @@ var checkAnchor = (route, fragment, site, ctx) => {
23387
23583
  severity: "warning"
23388
23584
  };
23389
23585
  };
23390
- var checkPathLink = (resolved, fragment, target, site, ctx) => {
23586
+ var checkPathLink = (resolved, fragment, page2, link, site, ctx) => {
23391
23587
  const route = toRoute(withBasePath(ctx.basePath, resolved));
23392
23588
  if (ctx.routes.has(route)) {
23393
23589
  return fragment ? checkAnchor(route, fragment, site, ctx) : null;
@@ -23403,6 +23599,18 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
23403
23599
  if (assetIsPresent(assetPath, ctx)) {
23404
23600
  return null;
23405
23601
  }
23602
+ if (link.image && page2.sourcePath && isRelativeImageTarget(link.target)) {
23603
+ if (resolveRelativeImage(dirname17(page2.sourcePath), link.target)) {
23604
+ return null;
23605
+ }
23606
+ return {
23607
+ ...site,
23608
+ code: "BLUME_BROKEN_ASSET",
23609
+ message: `Image ${link.target} was not found next to ${basename7(page2.sourcePath)}.`,
23610
+ severity: "warning",
23611
+ suggestion: "Add the file next to the page source or fix the reference."
23612
+ };
23613
+ }
23406
23614
  if (ctx.publicDir === null) {
23407
23615
  return "asset-unchecked";
23408
23616
  }
@@ -23417,7 +23625,7 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
23417
23625
  return {
23418
23626
  ...site,
23419
23627
  code: "BLUME_BROKEN_LINK",
23420
- message: `Broken link to ${target}: no page resolves to ${route}.`,
23628
+ message: `Broken link to ${link.target}: no page resolves to ${route}.`,
23421
23629
  severity: "error",
23422
23630
  suggestion: "Check the path, or create the target page."
23423
23631
  };
@@ -23470,7 +23678,7 @@ var classifyLink = (page2, link, ctx, onExternal) => {
23470
23678
  return fragment ? checkAnchor(page2.route, fragment, site, ctx) : null;
23471
23679
  }
23472
23680
  const resolved = rawPath.startsWith("/") ? rawPath : resolveRelative(page2.route, rawPath, isIndexPage(page2));
23473
- return checkPathLink(resolved, fragment, target, site, ctx);
23681
+ return checkPathLink(resolved, fragment, page2, link, site, ctx);
23474
23682
  };
23475
23683
  var validateLinks = async (graph, options) => {
23476
23684
  const basePath = options.basePath ?? "";
@@ -23552,7 +23760,7 @@ var validateCommand = defineCommand14({
23552
23760
  basePath: project.config.basePath,
23553
23761
  checkExternal: Boolean(args.external),
23554
23762
  extraRoutes,
23555
- publicDir: existsSync27(publicDir) ? publicDir : null,
23763
+ publicDir: existsSync26(publicDir) ? publicDir : null,
23556
23764
  redirects: project.config.redirects
23557
23765
  }));
23558
23766
  } catch (error) {
@@ -23586,7 +23794,7 @@ var validateCommand = defineCommand14({
23586
23794
  import { defineCommand as defineCommand15 } from "citty";
23587
23795
 
23588
23796
  // src/core/version-cut.ts
23589
- import { existsSync as existsSync28 } from "node:fs";
23797
+ import { existsSync as existsSync27 } from "node:fs";
23590
23798
  import { cp as cp3, readdir as readdir3, readFile as readFile27, rm as rm5 } from "node:fs/promises";
23591
23799
  import { join as join48, relative as relative21 } from "pathe";
23592
23800
  class CutError extends Error {
@@ -23706,7 +23914,7 @@ var cutVersion = async (root, id2, options = {}) => {
23706
23914
  throw new CutError(`Version "${id2}" is already registered in versions.archived.`);
23707
23915
  }
23708
23916
  const dir = join48(contentRoot2, id2);
23709
- if (existsSync28(dir)) {
23917
+ if (existsSync27(dir)) {
23710
23918
  if (!options.force) {
23711
23919
  throw new CutError(`${dir} already exists — pass --force to overwrite the snapshot.`);
23712
23920
  }
@@ -23859,5 +24067,5 @@ process.on("unhandledRejection", (error) => {
23859
24067
  });
23860
24068
  runMain(main);
23861
24069
 
23862
- //# debugId=A01DCE54DD00393164756E2164756E21
24070
+ //# debugId=94497B7DFC770E9664756E2164756E21
23863
24071
  //# sourceMappingURL=index.js.map