blume 0.5.4 → 0.6.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 (41) hide show
  1. package/dist/cli/index.js +380 -157
  2. package/dist/cli/index.js.map +23 -22
  3. package/dist/types/core/data.d.ts +4 -0
  4. package/dist/types/core/i18n-ui.d.ts +50 -0
  5. package/dist/types/core/schema.d.ts +328 -39
  6. package/dist/types/core/types.d.ts +8 -0
  7. package/docs/configuration/ai.mdx +56 -0
  8. package/docs/configuration/seo.mdx +59 -1
  9. package/docs/configuration/theming.mdx +14 -9
  10. package/docs/content/meta.mdx +3 -17
  11. package/docs/content/navigation.mdx +41 -4
  12. package/package.json +3 -1
  13. package/src/ai/agent-readability.ts +97 -0
  14. package/src/ai/ask-context.ts +131 -8
  15. package/src/ai/ask-data.ts +4 -1
  16. package/src/astro/generate.ts +4 -0
  17. package/src/astro/templates.ts +24 -5
  18. package/src/cli/commands/build.ts +15 -0
  19. package/src/cli/commands/dev.ts +31 -14
  20. package/src/cli/dev-lock.ts +94 -21
  21. package/src/components/content/GithubInfo.astro +11 -10
  22. package/src/components/content/TypeTable.astro +8 -3
  23. package/src/components/islands/AskAI.astro +66 -2
  24. package/src/components/islands/ask-ai.tsx +289 -53
  25. package/src/components/layout/Header.astro +1 -1
  26. package/src/components/layout/NavTree.astro +1 -1
  27. package/src/components/layout/PageActions.astro +73 -30
  28. package/src/components/layout/RootLayout.astro +48 -2
  29. package/src/core/data.ts +4 -0
  30. package/src/core/graph.ts +7 -2
  31. package/src/core/i18n-ui.ts +5 -0
  32. package/src/core/nav-diagnostics.ts +7 -0
  33. package/src/core/navigation.ts +38 -12
  34. package/src/core/schema.ts +124 -9
  35. package/src/core/sources/filesystem.ts +5 -1
  36. package/src/core/sources/watch.ts +43 -12
  37. package/src/core/types.ts +9 -0
  38. package/src/deploy/robots.ts +37 -4
  39. package/src/openapi/scalar.ts +1 -1
  40. package/src/search/documents.ts +9 -2
  41. package/src/theme/palette.ts +21 -14
package/dist/cli/index.js CHANGED
@@ -519,6 +519,150 @@ import { build } from "astro";
519
519
  import { defineCommand as defineCommand2 } from "citty";
520
520
  import { join as join23 } from "pathe";
521
521
 
522
+ // src/deploy/xml.ts
523
+ var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
524
+
525
+ // src/deploy/rss.ts
526
+ var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
527
+ var pageDate = (page) => {
528
+ const raw = page.meta.date ?? page.meta.changelog?.date;
529
+ if (!raw) {
530
+ return;
531
+ }
532
+ const date = new Date(raw);
533
+ return Number.isNaN(date.getTime()) ? undefined : date;
534
+ };
535
+ var buildRssFeeds = (project) => {
536
+ const { config } = project;
537
+ const { rss } = config.seo;
538
+ const { site } = config.deployment;
539
+ if (!(rss.enabled && site)) {
540
+ return [];
541
+ }
542
+ const base = site.replace(/\/$/u, "");
543
+ const feeds = [];
544
+ for (const type of rss.types) {
545
+ const pages = project.graph.pages.filter((page) => page.contentType === type && !(page.meta.draft || page.meta.sidebar.hidden));
546
+ if (pages.length === 0) {
547
+ continue;
548
+ }
549
+ const items = pages.map((page) => ({
550
+ date: pageDate(page),
551
+ description: page.description,
552
+ link: `${base}${page.route}`,
553
+ title: page.title
554
+ })).toSorted((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0)).slice(0, rss.limit);
555
+ feeds.push({
556
+ description: config.description,
557
+ items,
558
+ link: base,
559
+ path: `/${type}/rss.xml`,
560
+ title: `${config.title} — ${capitalize(type)}`,
561
+ type
562
+ });
563
+ }
564
+ return feeds;
565
+ };
566
+ var renderItem = (item) => {
567
+ const parts = [
568
+ ` <title>${escapeXml(item.title)}</title>`,
569
+ ` <link>${escapeXml(item.link)}</link>`,
570
+ ` <guid isPermaLink="true">${escapeXml(item.link)}</guid>`
571
+ ];
572
+ if (item.description) {
573
+ parts.push(` <description>${escapeXml(item.description)}</description>`);
574
+ }
575
+ if (item.date) {
576
+ parts.push(` <pubDate>${item.date.toUTCString()}</pubDate>`);
577
+ }
578
+ return ` <item>
579
+ ${parts.join(`
580
+ `)}
581
+ </item>`;
582
+ };
583
+ var renderRssFeed = (feed) => {
584
+ const channel = [
585
+ ` <title>${escapeXml(feed.title)}</title>`,
586
+ ` <link>${escapeXml(feed.link)}</link>`,
587
+ ` <description>${escapeXml(feed.description ?? feed.title)}</description>`,
588
+ ` <atom:link href="${escapeXml(`${feed.link}${feed.path}`)}" rel="self" type="application/rss+xml" />`
589
+ ];
590
+ const items = feed.items.map(renderItem).join(`
591
+ `);
592
+ return `<?xml version="1.0" encoding="UTF-8"?>
593
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
594
+ <channel>
595
+ ${channel.join(`
596
+ `)}
597
+ ${items}
598
+ </channel>
599
+ </rss>
600
+ `;
601
+ };
602
+
603
+ // src/ai/agent-readability.ts
604
+ var USAGE_TOKENS = [
605
+ ["search", "search"],
606
+ ["aiInput", "ai-input"],
607
+ ["aiTrain", "ai-train"]
608
+ ];
609
+ var usagePolicy = (signals) => {
610
+ if (!signals) {
611
+ return null;
612
+ }
613
+ return Object.fromEntries(USAGE_TOKENS.map(([key, token]) => [token, signals[key]]));
614
+ };
615
+ var buildAgentReadability = (project) => {
616
+ const { config } = project;
617
+ if (!config.seo.agentReadability) {
618
+ return null;
619
+ }
620
+ const site = config.deployment.site ?? null;
621
+ const abs = (path) => site ? `${site.replace(/\/+$/u, "")}${path}` : path;
622
+ const artifacts = {
623
+ markdown: {
624
+ contentNegotiation: "text/markdown",
625
+ pattern: abs("/{route}.md")
626
+ }
627
+ };
628
+ if (config.ai.llmsTxt) {
629
+ artifacts.llmsFullTxt = abs("/llms-full.txt");
630
+ artifacts.llmsTxt = abs("/llms.txt");
631
+ }
632
+ if (config.mcp.enabled) {
633
+ artifacts.mcp = {
634
+ discovery: abs("/.well-known/mcp.json"),
635
+ url: abs(config.mcp.route)
636
+ };
637
+ }
638
+ if (config.ai.ask?.enabled) {
639
+ artifacts.askApi = abs("/api/ask");
640
+ }
641
+ if (site && config.seo.sitemap) {
642
+ artifacts.sitemap = abs("/sitemap.xml");
643
+ }
644
+ const feeds = site && config.seo.rss.enabled ? buildRssFeeds(project).map((feed) => abs(feed.path)) : [];
645
+ if (feeds.length > 0) {
646
+ artifacts.feeds = feeds;
647
+ }
648
+ const version = project.manifest?.blumeVersion;
649
+ const manifest = {
650
+ artifacts,
651
+ description: config.description,
652
+ generator: version ? `blume@${version}` : undefined,
653
+ name: config.mcp.name ?? config.title,
654
+ site
655
+ };
656
+ const usage = usagePolicy(config.seo.contentSignals);
657
+ if (usage) {
658
+ manifest.contentUsage = usage;
659
+ }
660
+ if (config.github) {
661
+ manifest.repository = `https://github.com/${config.github.owner}/${config.github.repo}`;
662
+ }
663
+ return manifest;
664
+ };
665
+
522
666
  // src/core/frontmatter.ts
523
667
  import baseMatter from "gray-matter";
524
668
  import { dump, load } from "js-yaml";
@@ -720,12 +864,29 @@ var buildRedirectManifest = (redirects) => `${JSON.stringify(redirects.map((redi
720
864
  `;
721
865
 
722
866
  // src/deploy/robots.ts
867
+ var SIGNAL_TOKENS = [
868
+ ["search", "search"],
869
+ ["aiInput", "ai-input"],
870
+ ["aiTrain", "ai-train"]
871
+ ];
872
+ var contentSignalLine = (signals) => {
873
+ if (!signals) {
874
+ return null;
875
+ }
876
+ const tokens = SIGNAL_TOKENS.map(([key, token]) => `${token}=${signals[key] ? "yes" : "no"}`);
877
+ return `Content-Signal: ${tokens.join(", ")}`;
878
+ };
723
879
  var buildRobots = (project) => {
724
880
  const { config } = project;
725
881
  if (!config.seo.robots) {
726
882
  return null;
727
883
  }
728
- const lines = ["User-agent: *", "Allow: /"];
884
+ const lines = ["User-agent: *"];
885
+ const signal = contentSignalLine(config.seo.contentSignals);
886
+ if (signal) {
887
+ lines.push(signal);
888
+ }
889
+ lines.push("Allow: /");
729
890
  const { site } = config.deployment;
730
891
  if (site && config.seo.sitemap) {
731
892
  lines.push("", `Sitemap: ${site.replace(/\/$/u, "")}/sitemap.xml`);
@@ -735,9 +896,6 @@ var buildRobots = (project) => {
735
896
  `;
736
897
  };
737
898
 
738
- // src/deploy/xml.ts
739
- var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
740
-
741
899
  // src/deploy/sitemap.ts
742
900
  var lastmodTag = (value) => {
743
901
  if (!value) {
@@ -2676,7 +2834,8 @@ var buildSearchDocuments = async (project, options) => {
2676
2834
  return await Promise.all(indexable.map(async (route) => {
2677
2835
  const page = pageById.get(route.id);
2678
2836
  const raw = page ? await readEntryText(project, page) : "";
2679
- const body = raw ? toPlainText(frontmatter_default(raw).content) : "";
2837
+ const source = raw ? frontmatter_default(raw).content : "";
2838
+ const body = options?.content === "markdown" ? source.trim() : toPlainText(source);
2680
2839
  const tags = page?.meta?.search?.tags;
2681
2840
  const crumb = crumbs.get(route.path);
2682
2841
  return {
@@ -2867,15 +3026,26 @@ var resolveProjectContext = (root, config, options) => {
2867
3026
 
2868
3027
  // src/cli/dev-lock.ts
2869
3028
  var lockPath = (outDir) => join8(outDir, "dev.lock");
2870
- var isDevLocked = (outDir) => {
2871
- const path = lockPath(outDir);
2872
- if (!existsSync5(path)) {
2873
- return false;
3029
+ var isValidPid = (pid) => typeof pid === "number" && Number.isInteger(pid) && pid > 0;
3030
+ var parseLock = (raw) => {
3031
+ let data;
3032
+ try {
3033
+ data = JSON.parse(raw.trim());
3034
+ } catch {
3035
+ return null;
2874
3036
  }
2875
- const pid = Number.parseInt(readFileSync2(path, "utf-8").trim(), 10);
2876
- if (!(Number.isInteger(pid) && pid > 0)) {
2877
- return false;
3037
+ if (isValidPid(data)) {
3038
+ return { pid: data };
3039
+ }
3040
+ if (typeof data === "object" && data !== null) {
3041
+ const { pid, port } = data;
3042
+ if (isValidPid(pid)) {
3043
+ return typeof port === "number" ? { pid, port } : { pid };
3044
+ }
2878
3045
  }
3046
+ return null;
3047
+ };
3048
+ var isProcessAlive = (pid) => {
2879
3049
  try {
2880
3050
  process.kill(pid, 0);
2881
3051
  return true;
@@ -2883,10 +3053,34 @@ var isDevLocked = (outDir) => {
2883
3053
  return error.code === "EPERM";
2884
3054
  }
2885
3055
  };
2886
- var acquireDevLock = (outDir) => {
3056
+ var readDevLock = (outDir) => {
2887
3057
  const path = lockPath(outDir);
3058
+ if (!existsSync5(path)) {
3059
+ return null;
3060
+ }
3061
+ const lock = parseLock(readFileSync2(path, "utf-8"));
3062
+ return lock && isProcessAlive(lock.pid) ? lock : null;
3063
+ };
3064
+ var writeLock = (outDir, port) => {
3065
+ writeFileSync(lockPath(outDir), JSON.stringify({
3066
+ pid: process.pid,
3067
+ ...port === undefined ? {} : { port }
3068
+ }));
3069
+ };
3070
+ var ownsLock = (outDir) => {
3071
+ const path = lockPath(outDir);
3072
+ if (!existsSync5(path)) {
3073
+ return false;
3074
+ }
3075
+ try {
3076
+ return parseLock(readFileSync2(path, "utf-8"))?.pid === process.pid;
3077
+ } catch {
3078
+ return false;
3079
+ }
3080
+ };
3081
+ var acquireDevLock = (outDir, port) => {
2888
3082
  mkdirSync(outDir, { recursive: true });
2889
- writeFileSync(path, String(process.pid));
3083
+ writeLock(outDir, port);
2890
3084
  let released = false;
2891
3085
  return () => {
2892
3086
  if (released) {
@@ -2894,15 +3088,22 @@ var acquireDevLock = (outDir) => {
2894
3088
  }
2895
3089
  released = true;
2896
3090
  try {
2897
- if (existsSync5(path) && readFileSync2(path, "utf-8").trim() === String(process.pid)) {
2898
- rmSync(path, { force: true });
3091
+ if (ownsLock(outDir)) {
3092
+ rmSync(lockPath(outDir), { force: true });
2899
3093
  }
2900
3094
  } catch {}
2901
3095
  };
2902
3096
  };
3097
+ var updateDevLockPort = (outDir, port) => {
3098
+ if (ownsLock(outDir)) {
3099
+ writeLock(outDir, port);
3100
+ }
3101
+ };
3102
+ var describeDevLock = (lock) => lock.port === undefined ? "" : ` at http://localhost:${lock.port}`;
2903
3103
  var refuseIfDevRunning = (root, action, runtimeDir) => {
2904
- if (isDevLocked(resolveRuntimeDir(root, runtimeDir))) {
2905
- 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.`);
3104
+ const lock = readDevLock(resolveRuntimeDir(root, runtimeDir));
3105
+ if (lock) {
3106
+ logger.error(`A \`blume dev\` server is running${describeDevLock(lock)}; ${action} would corrupt its .blume runtime. Reuse that server, stop it first, or re-run with --isolated to build/verify against .blume-verify without touching it.`);
2906
3107
  process.exit(1);
2907
3108
  }
2908
3109
  };
@@ -2926,6 +3127,7 @@ import { glob as glob4 } from "tinyglobby";
2926
3127
  // src/ai/ask-data.ts
2927
3128
  var buildAskData = async (project) => {
2928
3129
  const documents = await buildSearchDocuments(project, {
3130
+ content: "markdown",
2929
3131
  includeWhenDisabled: true
2930
3132
  });
2931
3133
  return {
@@ -3424,6 +3626,7 @@ var uiStringsObject = z.object({
3424
3626
  connectMcp: z.string().default("Connect to MCP"),
3425
3627
  copied: z.string().default("Copied!"),
3426
3628
  copyClaudeCode: z.string().default("Copy Claude Code command"),
3629
+ copyCodex: z.string().default("Copy Codex command"),
3427
3630
  copyMarkdown: z.string().default("Copy as Markdown"),
3428
3631
  copyServerUrl: z.string().default("Copy server URL"),
3429
3632
  edit: z.string().default("Edit on GitHub"),
@@ -3431,11 +3634,15 @@ var uiStringsObject = z.object({
3431
3634
  scrollToTop: z.string().default("Scroll to top")
3432
3635
  }).default({}),
3433
3636
  ask: z.object({
3637
+ clear: z.string().default("Clear conversation"),
3638
+ close: z.string().default("Close"),
3639
+ copy: z.string().default("Copy conversation"),
3434
3640
  empty: z.string().default("Ask a question about the docs."),
3435
3641
  error: z.string().default("Sorry, something went wrong."),
3436
3642
  label: z.string().default("Ask a question"),
3437
3643
  placeholder: z.string().default("Ask a question…"),
3438
3644
  send: z.string().default("Send"),
3645
+ tip: z.string().default("Tip: You can open and close chat with"),
3439
3646
  title: z.string().default("Ask AI")
3440
3647
  }).default({}),
3441
3648
  feedback: z.object({
@@ -3568,6 +3775,9 @@ var collectIcons = (navigation) => {
3568
3775
  push(item.icon, `selector "${item.label}"`);
3569
3776
  }
3570
3777
  }
3778
+ for (const link of navigation.featured) {
3779
+ push(link.icon, `featured link "${link.label}"`);
3780
+ }
3571
3781
  const sidebars = [navigation.sidebar];
3572
3782
  for (const sidebar of sidebars) {
3573
3783
  for (const node of flattenNodes(sidebar)) {
@@ -3597,7 +3807,11 @@ var resolvesToPages = (routes, path) => routes.has(path) || [...routes].some((ro
3597
3807
  var validateNavTargets = (navigation, routes) => {
3598
3808
  const targets = [
3599
3809
  ...navigation.tabs.map((tab) => ({ label: tab.label, path: tab.path })),
3600
- ...navigation.selectors.flatMap((selector) => selector.items.map((item) => ({ label: item.label, path: item.path })))
3810
+ ...navigation.selectors.flatMap((selector) => selector.items.map((item) => ({ label: item.label, path: item.path }))),
3811
+ ...navigation.featured.map((link) => ({
3812
+ label: link.label,
3813
+ path: link.href
3814
+ }))
3601
3815
  ];
3602
3816
  const diagnostics = [];
3603
3817
  const seen = new Set;
@@ -3813,84 +4027,6 @@ var resolveTsconfigAliases = (root) => {
3813
4027
  return aliases;
3814
4028
  };
3815
4029
 
3816
- // src/deploy/rss.ts
3817
- var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
3818
- var pageDate = (page) => {
3819
- const raw = page.meta.date ?? page.meta.changelog?.date;
3820
- if (!raw) {
3821
- return;
3822
- }
3823
- const date = new Date(raw);
3824
- return Number.isNaN(date.getTime()) ? undefined : date;
3825
- };
3826
- var buildRssFeeds = (project) => {
3827
- const { config } = project;
3828
- const { rss } = config.seo;
3829
- const { site } = config.deployment;
3830
- if (!(rss.enabled && site)) {
3831
- return [];
3832
- }
3833
- const base = site.replace(/\/$/u, "");
3834
- const feeds = [];
3835
- for (const type of rss.types) {
3836
- const pages = project.graph.pages.filter((page) => page.contentType === type && !(page.meta.draft || page.meta.sidebar.hidden));
3837
- if (pages.length === 0) {
3838
- continue;
3839
- }
3840
- const items = pages.map((page) => ({
3841
- date: pageDate(page),
3842
- description: page.description,
3843
- link: `${base}${page.route}`,
3844
- title: page.title
3845
- })).toSorted((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0)).slice(0, rss.limit);
3846
- feeds.push({
3847
- description: config.description,
3848
- items,
3849
- link: base,
3850
- path: `/${type}/rss.xml`,
3851
- title: `${config.title} — ${capitalize(type)}`,
3852
- type
3853
- });
3854
- }
3855
- return feeds;
3856
- };
3857
- var renderItem = (item) => {
3858
- const parts = [
3859
- ` <title>${escapeXml(item.title)}</title>`,
3860
- ` <link>${escapeXml(item.link)}</link>`,
3861
- ` <guid isPermaLink="true">${escapeXml(item.link)}</guid>`
3862
- ];
3863
- if (item.description) {
3864
- parts.push(` <description>${escapeXml(item.description)}</description>`);
3865
- }
3866
- if (item.date) {
3867
- parts.push(` <pubDate>${item.date.toUTCString()}</pubDate>`);
3868
- }
3869
- return ` <item>
3870
- ${parts.join(`
3871
- `)}
3872
- </item>`;
3873
- };
3874
- var renderRssFeed = (feed) => {
3875
- const channel = [
3876
- ` <title>${escapeXml(feed.title)}</title>`,
3877
- ` <link>${escapeXml(feed.link)}</link>`,
3878
- ` <description>${escapeXml(feed.description ?? feed.title)}</description>`,
3879
- ` <atom:link href="${escapeXml(`${feed.link}${feed.path}`)}" rel="self" type="application/rss+xml" />`
3880
- ];
3881
- const items = feed.items.map(renderItem).join(`
3882
- `);
3883
- return `<?xml version="1.0" encoding="UTF-8"?>
3884
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
3885
- <channel>
3886
- ${channel.join(`
3887
- `)}
3888
- ${items}
3889
- </channel>
3890
- </rss>
3891
- `;
3892
- };
3893
-
3894
4030
  // src/openapi/references.ts
3895
4031
  var NON_SLUG = /[^a-z0-9]+/gu;
3896
4032
  var SLUG_EDGES = /^-+|-+$/gu;
@@ -3977,6 +4113,30 @@ import { isAbsolute as isAbsolute5, join as join11 } from "pathe";
3977
4113
  import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
3978
4114
  import { dirname as dirname6, isAbsolute as isAbsolute4, join as join10, relative as relative4 } from "pathe";
3979
4115
 
4116
+ // src/core/sources/watch.ts
4117
+ var BLUME_IGNORE_DIRS = [
4118
+ ".blume",
4119
+ ".cache",
4120
+ ".git",
4121
+ ".next",
4122
+ ".turbo",
4123
+ ".vercel",
4124
+ "dist",
4125
+ "node_modules"
4126
+ ];
4127
+ var baselineScanIgnore = () => BLUME_IGNORE_DIRS.map((dir) => `**/${dir}/**`);
4128
+ var BLUME_WATCH_IGNORE_DIRS = BLUME_IGNORE_DIRS;
4129
+ var excludeDirSegments = (patterns) => patterns.map((pattern) => /^(?<dir>[^*/]+)\/\*\*$/u.exec(pattern)?.groups?.dir).filter((dir) => dir !== undefined);
4130
+ var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS) => {
4131
+ const ignore = new Set(ignoreDirs);
4132
+ return (_event, filename) => {
4133
+ if (typeof filename === "string" && filename.split(/[/\\]/u).some((segment) => ignore.has(segment))) {
4134
+ return;
4135
+ }
4136
+ onChange();
4137
+ };
4138
+ };
4139
+
3980
4140
  // src/theme/fonts.ts
3981
4141
  var FALLBACKS = {
3982
4142
  mono: ["ui-monospace", "SF Mono", "Menlo", "monospace"],
@@ -4314,9 +4474,17 @@ export default defineConfig({
4314
4474
  // native bindings resolve at runtime and isolated linkers don't bundle
4315
4475
  // symlinked store copies (which would surface their children as unresolvable
4316
4476
  // imports). See RENDER_EXTERNAL_DEPS / prerenderDepsPlugin.
4477
+ //
4478
+ // The SSR externals go through the legacy \`ssr.external\` key rather than
4479
+ // \`environments.ssr\`: defining a user-owned \`environments.ssr\` block
4480
+ // collides with the internal environment Astro 7 builds the server under and
4481
+ // detaches the adapter's server entrypoint from the rolldown input, so the
4482
+ // SSR entry is emitted as \`index.mjs\` instead of the \`entry.mjs\` the
4483
+ // Vercel adapter's \`astro:build:done\` hook then fails to find. \`prerender\`
4484
+ // is Astro-only and has no legacy equivalent, so it stays under \`environments\`.
4485
+ ssr: { external: ${JSON.stringify(RENDER_EXTERNAL_DEPS)} },
4317
4486
  environments: {
4318
4487
  prerender: { resolve: { external: ${JSON.stringify(RENDER_EXTERNAL_DEPS)} } },
4319
- ssr: { resolve: { external: ${JSON.stringify(RENDER_EXTERNAL_DEPS)} } },
4320
4488
  },
4321
4489
  resolve: {
4322
4490
  alias: {
@@ -4355,7 +4523,7 @@ var contentConfigTemplate = (options) => {
4355
4523
  const docsPattern = filesystem ? [
4356
4524
  ...config.content.include,
4357
4525
  ...(config.content.exclude ?? []).map((pattern) => `!${pattern}`),
4358
- "!**/node_modules/**",
4526
+ ...BLUME_IGNORE_DIRS.filter((dir) => dir !== ".blume").map((dir) => `!**/${dir}/**`),
4359
4527
  ...outDirIgnore
4360
4528
  ] : [];
4361
4529
  const stagedBlock = options.staged ? `
@@ -4691,7 +4859,7 @@ const siteHost = (() => {
4691
4859
 
4692
4860
  export async function GET({ props }) {
4693
4861
  const png = await renderOgImage({
4694
- accent: data.config.theme.accent,
4862
+ accent: data.config.theme.accent.light,
4695
4863
  brand: data.config.title,
4696
4864
  description: data.config.description,
4697
4865
  logo: data.config.logo?.svg,
@@ -4739,7 +4907,7 @@ var catchAllPageTemplate = (options) => {
4739
4907
  const askImport = options.askEnabled ? `import AskAI from "blume/components/islands/AskAI.astro";
4740
4908
  ` : "";
4741
4909
  const askSlot = options.askEnabled ? `
4742
- <AskAI slot="ask" strings={ui.ask} />` : "";
4910
+ <AskAI slot="ask" strings={ui.ask} suggestions={data.config.ask?.suggestions ?? []} />` : "";
4743
4911
  const mathImport = options.mathEnabled ? `import Math from "blume/components/content/Math.astro";
4744
4912
  ` : "";
4745
4913
  const mathEntry = options.mathEnabled ? `Math,
@@ -4991,7 +5159,7 @@ var changelogIndexTemplate = (options) => {
4991
5159
  const askImport = options.askEnabled ? `import AskAI from "blume/components/islands/AskAI.astro";
4992
5160
  ` : "";
4993
5161
  const askSlot = options.askEnabled ? `
4994
- <AskAI slot="ask" />` : "";
5162
+ <AskAI slot="ask" strings={data.ui.ask} suggestions={data.config.ask?.suggestions ?? []} />` : "";
4995
5163
  const clientData = options.needsReact ? `
4996
5164
  clientData={{ config: data.config, navigation: data.navigation, page: { route: "/changelog", title: data.config.title + " changelog" } }}` : "";
4997
5165
  const stagedSpread = options.staged ? `
@@ -5281,8 +5449,8 @@ var themeRootCss = (theme, options) => [
5281
5449
  ` --blume-accent: ${options.accent};`,
5282
5450
  ...cssToken("--blume-action", options.action),
5283
5451
  ...cssToken("--blume-action-foreground", options.action ? "oklch(1 0 0)" : null),
5284
- ...cssToken("--blume-background", safeColorOrNull(theme.background)),
5285
- ...cssToken("--blume-background-image", theme.backgroundImage ? backgroundImageCss(theme.backgroundImage) : null),
5452
+ ...cssToken("--blume-background", safeColorOrNull(theme.background?.light)),
5453
+ ...cssToken("--blume-background-image", theme.backgroundImage?.light ? backgroundImageCss(theme.backgroundImage.light) : null),
5286
5454
  ` --blume-radius: ${options.radius};`
5287
5455
  ].filter(Boolean).join(`
5288
5456
  `);
@@ -5292,8 +5460,8 @@ var themeDarkCss = (theme, options) => {
5292
5460
  " --blume-accent-foreground: oklch(1 0 0);",
5293
5461
  ...cssToken("--blume-action", options.action),
5294
5462
  ...cssToken("--blume-action-foreground", options.action ? "oklch(1 0 0)" : null),
5295
- ...cssToken("--blume-background", safeColorOrNull(theme.backgroundDark)),
5296
- ...cssToken("--blume-background-image", theme.backgroundImageDark ? backgroundImageCss(theme.backgroundImageDark) : null)
5463
+ ...cssToken("--blume-background", safeColorOrNull(theme.background?.dark)),
5464
+ ...cssToken("--blume-background-image", theme.backgroundImage?.dark ? backgroundImageCss(theme.backgroundImage.dark) : null)
5297
5465
  ].filter(Boolean);
5298
5466
  return `:root[data-theme="dark"] {
5299
5467
  ${tokens.join(`
@@ -5301,20 +5469,22 @@ ${tokens.join(`
5301
5469
  }
5302
5470
  `;
5303
5471
  };
5304
- var resolveAccent = (theme) => presetOrColor(theme.accent);
5472
+ var resolveAccent = (theme) => ({
5473
+ dark: presetOrColor(theme.accent.dark),
5474
+ light: presetOrColor(theme.accent.light)
5475
+ });
5305
5476
  var resolveRadius = (theme) => RADII[theme.radius];
5306
5477
  var buildThemeCss = (theme) => {
5307
- const accent = presetOrColor(theme.accent);
5308
- const accentDark = theme.accentDark ? presetOrColor(theme.accentDark) : null;
5478
+ const accent = resolveAccent(theme);
5309
5479
  const action = theme.action ? presetOrColor(theme.action) : null;
5310
5480
  const radius = RADII[theme.radius];
5311
5481
  const root = themeRootCss(theme, {
5312
- accent,
5482
+ accent: accent.light,
5313
5483
  action,
5314
5484
  radius
5315
5485
  });
5316
5486
  const dark = themeDarkCss(theme, {
5317
- accent: accentDark ?? accent,
5487
+ accent: accent.dark,
5318
5488
  action
5319
5489
  });
5320
5490
  return `/* Generated by Blume from theme config. */
@@ -5347,7 +5517,7 @@ var themeConfiguration = (config, override) => {
5347
5517
  const accent = resolveAccent(config.theme);
5348
5518
  const radius = resolveRadius(config.theme);
5349
5519
  return {
5350
- customCss: `:root,.light-mode,.dark-mode{--scalar-color-accent:${accent};--scalar-radius:${radius};}`,
5520
+ customCss: `:root,.light-mode,.dark-mode{--scalar-color-accent:${accent.light};--scalar-radius:${radius};}.dark-mode{--scalar-color-accent:${accent.dark};}`,
5351
5521
  ...darkModeConfig(config.theme.mode)
5352
5522
  };
5353
5523
  };
@@ -7097,6 +7267,7 @@ var buildRuntimeData = (project) => {
7097
7267
  const navigationByLocale = i18n ? Object.fromEntries(i18n.locales.map(({ code }) => [
7098
7268
  code,
7099
7269
  withReferenceTabs(graph.navigationByLocale[code] ?? {
7270
+ featured: [],
7100
7271
  selectors: [],
7101
7272
  sidebar: [],
7102
7273
  tabs: []
@@ -7106,6 +7277,7 @@ var buildRuntimeData = (project) => {
7106
7277
  config: {
7107
7278
  analytics: config.analytics ?? null,
7108
7279
  appleIcon: resolveAppleIcon(project),
7280
+ ask: config.ai.ask?.enabled ? { suggestions: config.ai.ask.suggestions } : null,
7109
7281
  banner: resolveBanner(config),
7110
7282
  codeWrap: config.markdown.code.wrap,
7111
7283
  description: config.description,
@@ -7545,7 +7717,6 @@ var pageMetaSchema = pageMetaBaseSchema;
7545
7717
  var sidebarDisplaySchema = z2.enum(["flat", "group", "page"]);
7546
7718
  var folderMetaSchema = z2.object({
7547
7719
  collapsed: z2.boolean().optional(),
7548
- display: sidebarDisplaySchema.optional(),
7549
7720
  icon: iconName.optional(),
7550
7721
  order: z2.number().optional(),
7551
7722
  pages: z2.array(z2.string()).optional(),
@@ -7699,14 +7870,18 @@ var sidebarItemSchema = z2.lazy(() => z2.union([
7699
7870
  var fontSlug = z2.string().refine(isFontSlug, (value) => ({
7700
7871
  message: `Unknown font "${value}". Supported fonts: ${FONT_SLUGS.join(", ")}.`
7701
7872
  }));
7873
+ var perModeValueSchema = z2.union([
7874
+ z2.string(),
7875
+ z2.object({ dark: z2.string().optional(), light: z2.string().optional() }).strict()
7876
+ ]).optional().transform((value) => typeof value === "string" ? { dark: value, light: value } : value);
7702
7877
  var themeConfigSchema = z2.object({
7703
- accent: z2.string().default("blue"),
7704
- accentDark: z2.string().optional(),
7878
+ accent: z2.union([
7879
+ z2.string(),
7880
+ z2.object({ dark: z2.string(), light: z2.string() }).strict()
7881
+ ]).default("blue").transform((value) => typeof value === "string" ? { dark: value, light: value } : value),
7705
7882
  action: z2.string().optional(),
7706
- background: z2.string().optional(),
7707
- backgroundDark: z2.string().optional(),
7708
- backgroundImage: z2.string().optional(),
7709
- backgroundImageDark: z2.string().optional(),
7883
+ background: perModeValueSchema,
7884
+ backgroundImage: perModeValueSchema,
7710
7885
  fonts: z2.object({
7711
7886
  body: fontSlug.default("inter"),
7712
7887
  display: fontSlug.default("inter-tight"),
@@ -7785,7 +7960,11 @@ var aiConfigSchema = z2.object({
7785
7960
  baseUrl: z2.string().url().optional(),
7786
7961
  enabled: z2.boolean().default(false),
7787
7962
  model: z2.string().default("openai/gpt-5.5"),
7788
- provider: z2.enum(askAiProviders).default("gateway")
7963
+ provider: z2.enum(askAiProviders).default("gateway"),
7964
+ suggestions: z2.array(z2.object({
7965
+ icon: iconName.optional(),
7966
+ label: z2.string().min(1)
7967
+ }).strict()).default([])
7789
7968
  }).strict().superRefine((value, ctx) => {
7790
7969
  if (value.provider === "openai-compatible" && !value.baseUrl) {
7791
7970
  ctx.addIssue({
@@ -7797,10 +7976,22 @@ var aiConfigSchema = z2.object({
7797
7976
  }).optional(),
7798
7977
  llmsTxt: z2.boolean().default(false)
7799
7978
  }).strict();
7979
+ var featuredLinkSchema = z2.object({
7980
+ href: z2.string(),
7981
+ icon: iconName.optional(),
7982
+ label: z2.string()
7983
+ }).strict();
7800
7984
  var navigationConfigSchema = z2.object({
7985
+ featured: z2.array(featuredLinkSchema).default([]),
7801
7986
  repo: z2.boolean().default(true),
7802
7987
  selectors: z2.array(navSelectorSchema).default([]),
7803
- sidebar: z2.array(sidebarItemSchema).optional(),
7988
+ sidebar: z2.union([
7989
+ z2.array(sidebarItemSchema),
7990
+ z2.object({
7991
+ display: sidebarDisplaySchema.default("flat"),
7992
+ items: z2.array(sidebarItemSchema).optional()
7993
+ }).strict()
7994
+ ]).default({}).transform((value) => Array.isArray(value) ? { display: "flat", items: value } : value),
7804
7995
  tabs: z2.array(navTabSchema).optional()
7805
7996
  }).strict();
7806
7997
  var exportConfigSchema = z2.union([
@@ -7880,7 +8071,23 @@ var rssConfigSchema = z2.object({
7880
8071
  limit: z2.number().int().positive().default(50),
7881
8072
  types: z2.array(z2.string()).default(["blog", "changelog"])
7882
8073
  }).strict();
8074
+ var contentSignalsObjectSchema = z2.object({
8075
+ aiInput: z2.boolean().default(true),
8076
+ aiTrain: z2.boolean().default(true),
8077
+ search: z2.boolean().default(true)
8078
+ }).strict();
8079
+ var contentSignalsSchema = z2.union([z2.boolean(), contentSignalsObjectSchema]).transform((value) => {
8080
+ if (value === true) {
8081
+ return contentSignalsObjectSchema.parse({});
8082
+ }
8083
+ if (value === false) {
8084
+ return null;
8085
+ }
8086
+ return value;
8087
+ });
7883
8088
  var seoConfigSchema = z2.object({
8089
+ agentReadability: z2.boolean().default(true),
8090
+ contentSignals: contentSignalsSchema.default(true),
7884
8091
  og: ogConfigSchema.default({}),
7885
8092
  robots: z2.boolean().default(true),
7886
8093
  rss: rssConfigSchema.default({}),
@@ -8091,7 +8298,6 @@ var applyFolderMeta = (group, folderMeta, sharedMeta, metaPrefix) => {
8091
8298
  group.icon = meta.icon ?? group.icon;
8092
8299
  group.order = meta.order ?? group.order;
8093
8300
  group.collapsed = meta.collapsed ?? group.collapsed;
8094
- group.display = meta.display ?? group.display;
8095
8301
  if (meta.pages) {
8096
8302
  const rank = new Map(meta.pages.map((key, i) => [key, i]));
8097
8303
  for (const child of group.children) {
@@ -8121,7 +8327,15 @@ var sortNodes = (nodes) => {
8121
8327
  }
8122
8328
  }
8123
8329
  };
8124
- var toNavNode = (node) => {
8330
+ var hoistPages = (nodes) => {
8331
+ const pages = nodes.filter((node) => node.kind === "page");
8332
+ const groups = nodes.filter((node) => node.kind === "group");
8333
+ nodes.splice(0, nodes.length, ...pages, ...groups);
8334
+ for (const group of groups) {
8335
+ hoistPages(group.children);
8336
+ }
8337
+ };
8338
+ var toNavNode = (node, display) => {
8125
8339
  if (node.kind === "page") {
8126
8340
  return {
8127
8341
  badge: node.badge,
@@ -8135,16 +8349,16 @@ var toNavNode = (node) => {
8135
8349
  };
8136
8350
  }
8137
8351
  return {
8138
- children: node.children.map(toNavNode),
8352
+ children: node.children.map((child) => toNavNode(child, display)),
8139
8353
  collapsed: node.collapsed,
8140
- display: node.display,
8354
+ display,
8141
8355
  icon: node.icon,
8142
8356
  kind: "group",
8143
8357
  label: node.label,
8144
8358
  path: node.routePath
8145
8359
  };
8146
8360
  };
8147
- var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix) => {
8361
+ var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix, display) => {
8148
8362
  const root = createGroup("", "", "", 0);
8149
8363
  for (const page of pages) {
8150
8364
  if (page.meta.sidebar.hidden) {
@@ -8175,7 +8389,10 @@ var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix) => {
8175
8389
  }
8176
8390
  applyFolderMeta(root, folderMeta, sharedMeta, metaPrefix);
8177
8391
  sortNodes(root.children);
8178
- return root.children.map(toNavNode);
8392
+ if (display === "flat") {
8393
+ hoistPages(root.children);
8394
+ }
8395
+ return root.children.map((child) => toNavNode(child, display));
8179
8396
  };
8180
8397
  var normalizeRef = (ref) => {
8181
8398
  if (ref === "index") {
@@ -8192,7 +8409,7 @@ var routeForRef = (ref, byRoute) => {
8192
8409
  const normalized = normalizeRef(ref);
8193
8410
  return byRoute.get(normalized)?.route ?? normalized;
8194
8411
  };
8195
- var buildConfigSidebar = (items, byRoute) => {
8412
+ var buildConfigSidebar = (items, byRoute, display) => {
8196
8413
  const nodes = [];
8197
8414
  for (const item of items) {
8198
8415
  if (typeof item === "string") {
@@ -8214,10 +8431,10 @@ var buildConfigSidebar = (items, byRoute) => {
8214
8431
  if (item.items) {
8215
8432
  nodes.push({
8216
8433
  badge: item.badge,
8217
- children: buildConfigSidebar(item.items, byRoute),
8434
+ children: buildConfigSidebar(item.items, byRoute, display),
8218
8435
  collapsed: item.collapsed,
8219
8436
  directory: item.directory,
8220
- display: item.display,
8437
+ display: item.display ?? display,
8221
8438
  icon: item.icon,
8222
8439
  kind: "group",
8223
8440
  label: item.label,
@@ -8252,8 +8469,10 @@ var buildConfigSidebar = (items, byRoute) => {
8252
8469
  return nodes;
8253
8470
  };
8254
8471
  var buildNavigation = (pages, options) => {
8472
+ const featured = options.featured ?? [];
8255
8473
  const selectors = options.selectors ?? [];
8256
8474
  const tabs = options.tabs ?? [];
8475
+ const display = options.display ?? "flat";
8257
8476
  const metaPrefix = options.metaPrefix ?? "";
8258
8477
  const sharedFolderMeta = options.sharedFolderMeta ?? new Map;
8259
8478
  const byRoute = new Map(pages.map((page) => [
@@ -8262,14 +8481,16 @@ var buildNavigation = (pages, options) => {
8262
8481
  ]));
8263
8482
  if (options.sidebar) {
8264
8483
  return {
8484
+ featured,
8265
8485
  selectors,
8266
- sidebar: buildConfigSidebar(options.sidebar, byRoute),
8486
+ sidebar: buildConfigSidebar(options.sidebar, byRoute, display),
8267
8487
  tabs
8268
8488
  };
8269
8489
  }
8270
8490
  return {
8491
+ featured,
8271
8492
  selectors,
8272
- sidebar: buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix),
8493
+ sidebar: buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, display),
8273
8494
  tabs
8274
8495
  };
8275
8496
  };
@@ -8327,26 +8548,31 @@ var buildContentGraph = (pages, options) => {
8327
8548
  localePages = [...real, ...filled];
8328
8549
  }
8329
8550
  navigationByLocale[code] = buildNavigation(localePages, {
8551
+ display: options.navigation.sidebar.display,
8552
+ featured: options.navigation.featured,
8330
8553
  folderMeta: options.folderMeta,
8331
8554
  metaPrefix: i18n.parser === "dir" && code !== i18n.defaultLocale ? code : "",
8332
8555
  refByLogical: true,
8333
8556
  selectors: options.navigation.selectors,
8334
8557
  sharedFolderMeta: options.sharedFolderMeta,
8335
- sidebar: options.navigation.sidebar,
8558
+ sidebar: options.navigation.sidebar.items,
8336
8559
  tabs
8337
8560
  });
8338
8561
  }
8339
8562
  navigation = navigationByLocale[i18n.defaultLocale] ?? {
8563
+ featured: [],
8340
8564
  selectors: [],
8341
8565
  sidebar: [],
8342
8566
  tabs: []
8343
8567
  };
8344
8568
  } else {
8345
8569
  navigation = buildNavigation(pages, {
8570
+ display: options.navigation.sidebar.display,
8571
+ featured: options.navigation.featured,
8346
8572
  folderMeta: options.folderMeta,
8347
8573
  selectors: options.navigation.selectors,
8348
8574
  sharedFolderMeta: options.sharedFolderMeta,
8349
- sidebar: options.navigation.sidebar,
8575
+ sidebar: options.navigation.sidebar.items,
8350
8576
  tabs: options.navigation.tabs
8351
8577
  });
8352
8578
  }
@@ -8662,28 +8888,13 @@ import { existsSync as existsSync12, watch as fsWatch } from "node:fs";
8662
8888
  import { readFile as readFile11 } from "node:fs/promises";
8663
8889
  import { extname as extname5, isAbsolute as isAbsolute7, join as join17, relative as relative10, resolve as resolve5 } from "pathe";
8664
8890
  import { glob as glob6 } from "tinyglobby";
8665
-
8666
- // src/core/sources/watch.ts
8667
- var BLUME_WATCH_IGNORE_DIRS = [".blume", ".git", "node_modules"];
8668
- var excludeDirSegments = (patterns) => patterns.map((pattern) => /^(?<dir>[^*/]+)\/\*\*$/u.exec(pattern)?.groups?.dir).filter((dir) => dir !== undefined);
8669
- var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS) => {
8670
- const ignore = new Set(ignoreDirs);
8671
- return (_event, filename) => {
8672
- if (typeof filename === "string" && filename.split(/[/\\]/u).some((segment) => ignore.has(segment))) {
8673
- return;
8674
- }
8675
- onChange();
8676
- };
8677
- };
8678
-
8679
- // src/core/sources/filesystem.ts
8680
8891
  var filesystemSource = (options) => {
8681
8892
  const contentRoot = isAbsolute7(options.root) ? options.root : join17(resolve5(options.projectRoot), options.root);
8682
8893
  const load2 = async () => {
8683
8894
  const files = await glob6(options.include, {
8684
8895
  absolute: true,
8685
8896
  cwd: contentRoot,
8686
- ignore: options.exclude,
8897
+ ignore: [...options.exclude, ...baselineScanIgnore()],
8687
8898
  onlyFiles: true
8688
8899
  });
8689
8900
  files.sort();
@@ -10010,6 +10221,12 @@ var publishBuildArtifacts = async (project, distDir, args) => {
10010
10221
  await writeFile7(join23(distDir, "robots.txt"), robots, "utf-8");
10011
10222
  logger.success("Generated robots.txt");
10012
10223
  }
10224
+ const agentReadability = buildAgentReadability(project);
10225
+ if (agentReadability && !existsSync14(join23(distDir, "agent-readability.json"))) {
10226
+ await writeFile7(join23(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
10227
+ `, "utf-8");
10228
+ logger.success("Generated agent-readability.json");
10229
+ }
10013
10230
  await emitRedirectFiles(project.config, distDir);
10014
10231
  const { config } = project;
10015
10232
  const features = serverFeatures(config);
@@ -10021,6 +10238,7 @@ var publishBuildArtifacts = async (project, distDir, args) => {
10021
10238
  `Redirects ${config.redirects.length}`,
10022
10239
  `Sitemap ${sitemap ? "yes" : "no (set deployment.site)"}`,
10023
10240
  `Robots ${robots ? "yes" : "no"}`,
10241
+ `Agent JSON ${agentReadability ? "yes" : "no"}`,
10024
10242
  `LLM files ${config.ai.llmsTxt ? "yes" : "no"}`,
10025
10243
  `Server features ${features.length > 0 ? features.join(", ") : "none"}`
10026
10244
  ].join(`
@@ -10279,6 +10497,14 @@ var devCommand = defineCommand4({
10279
10497
  const explicitPort = parsePort(args.port);
10280
10498
  const port = explicitPort ?? 4321;
10281
10499
  const devServerUrl = `http://localhost:${port}`;
10500
+ const outDir = resolveRuntimeDir(root);
10501
+ const running = readDevLock(outDir);
10502
+ if (running) {
10503
+ logger.error(`A \`blume dev\` server is already running${describeDevLock(running)} in this project. Reuse that server instead of starting a second one — two dev servers would corrupt the shared .blume dir. If it crashed, delete .blume/dev.lock.`);
10504
+ process.exit(1);
10505
+ }
10506
+ const releaseLock = acquireDevLock(outDir, port);
10507
+ process.on("exit", releaseLock);
10282
10508
  const project = await prepareProject({
10283
10509
  devServerUrl,
10284
10510
  mode: "dev",
@@ -10287,12 +10513,6 @@ var devCommand = defineCommand4({
10287
10513
  root,
10288
10514
  strict: args.strict
10289
10515
  });
10290
- if (isDevLocked(project.context.outDir)) {
10291
- logger.error("Another `blume dev` is already running in this project; two dev servers would corrupt the shared .blume dir. Stop the other one first (or delete .blume/dev.lock if it crashed).");
10292
- process.exit(1);
10293
- }
10294
- const releaseLock = acquireDevLock(project.context.outDir);
10295
- process.on("exit", releaseLock);
10296
10516
  const server = await dev({
10297
10517
  logLevel: args.debug ? "debug" : "info",
10298
10518
  root: project.context.outDir,
@@ -10302,6 +10522,9 @@ var devCommand = defineCommand4({
10302
10522
  port: explicitPort
10303
10523
  }
10304
10524
  });
10525
+ if (server.address.port !== port) {
10526
+ updateDevLockPort(outDir, server.address.port);
10527
+ }
10305
10528
  showBlumeErrorOverlay(project.diagnostics);
10306
10529
  const runRegenerate = coalescedRunner(async () => {
10307
10530
  try {
@@ -11330,5 +11553,5 @@ process.on("unhandledRejection", (error) => {
11330
11553
  });
11331
11554
  runMain(main);
11332
11555
 
11333
- //# debugId=59F90AF7D601CF0764756E2164756E21
11556
+ //# debugId=0E9EF5D2749C00F464756E2164756E21
11334
11557
  //# sourceMappingURL=index.js.map