blume 1.4.0 → 1.4.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 (50) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/cli/index.js +328 -644
  3. package/dist/cli/index.js.map +35 -35
  4. package/dist/types/core/data.d.ts +10 -0
  5. package/docs/configuration/ai.mdx +15 -1
  6. package/package.json +28 -7
  7. package/src/ai/component-markdown.ts +7 -6
  8. package/src/ai/link-headers.ts +7 -2
  9. package/src/astro/generate.ts +8 -13
  10. package/src/astro/islands.ts +4 -1
  11. package/src/astro/templates.ts +5 -4
  12. package/src/audit/checks/indexability.ts +3 -6
  13. package/src/audit/checks/robots.ts +18 -37
  14. package/src/audit/crawl.ts +49 -49
  15. package/src/audit/image-size.ts +13 -53
  16. package/src/audit/report.ts +22 -33
  17. package/src/audit/types.ts +6 -2
  18. package/src/cli/commands/dev.ts +9 -21
  19. package/src/cli/commands/doctor.ts +9 -22
  20. package/src/cli/env.ts +6 -52
  21. package/src/cli/init/scaffold.ts +15 -28
  22. package/src/cli/internal-error.ts +11 -11
  23. package/src/components/islands/ask-ai.tsx +25 -100
  24. package/src/components/islands/hooks.ts +10 -3
  25. package/src/components/layout/RootLayout.astro +78 -109
  26. package/src/components/layout/Search.astro +3 -5
  27. package/src/components/layout/search/types.ts +4 -16
  28. package/src/components/openapi/helpers.ts +21 -75
  29. package/src/core/component-overrides.ts +0 -7
  30. package/src/core/config.ts +3 -3
  31. package/src/core/data.ts +7 -0
  32. package/src/core/diagnostics.ts +10 -20
  33. package/src/core/fs-atomic.ts +22 -0
  34. package/src/core/sources/github-releases.ts +29 -26
  35. package/src/core/sources/mdx-remote.ts +10 -57
  36. package/src/core/sources/notion.ts +17 -23
  37. package/src/core/tsconfig-aliases.ts +39 -172
  38. package/src/deploy/rss.ts +4 -1
  39. package/src/deploy/sitemap.ts +3 -1
  40. package/src/eval/report.ts +20 -28
  41. package/src/markdown/directives.ts +6 -18
  42. package/src/markdown/index.ts +1 -6
  43. package/src/markdown/package-commands.ts +0 -4
  44. package/src/openapi/parse.ts +11 -9
  45. package/src/search/popular-icon.ts +3 -3
  46. package/src/translate/ledger.ts +5 -11
  47. package/src/translate/report.ts +22 -28
  48. package/src/translate/run.ts +5 -24
  49. package/src/translate/work-list.ts +0 -0
  50. package/src/deploy/xml.ts +0 -8
package/dist/cli/index.js CHANGED
@@ -281,6 +281,7 @@ import { consola } from "consola";
281
281
  import { relative as relative3 } from "pathe";
282
282
 
283
283
  // src/core/diagnostics.ts
284
+ import { colors } from "consola/utils";
284
285
  import { relative as relative2 } from "pathe";
285
286
 
286
287
  class BlumeError extends Error {
@@ -394,45 +395,35 @@ var diagnosticsFromZod = (error, options) => diagnosticsFromIssues(error.issues.
394
395
  message: issue.message,
395
396
  path: issue.path.filter((segment) => typeof segment !== "symbol")
396
397
  })), options);
397
- var ESC = String.fromCodePoint(27);
398
- var COLORS = {
399
- blue: `${ESC}[34m`,
400
- bold: `${ESC}[1m`,
401
- cyan: `${ESC}[36m`,
402
- dim: `${ESC}[2m`,
403
- red: `${ESC}[31m`,
404
- reset: `${ESC}[0m`,
405
- yellow: `${ESC}[33m`
406
- };
407
398
  var severityColor = (severity) => {
408
399
  if (severity === "error") {
409
- return COLORS.red;
400
+ return colors.red;
410
401
  }
411
402
  if (severity === "warning") {
412
- return COLORS.yellow;
403
+ return colors.yellow;
413
404
  }
414
- return COLORS.blue;
405
+ return colors.blue;
415
406
  };
416
407
  var formatDiagnostic = (diagnostic, root) => {
417
408
  const color = severityColor(diagnostic.severity);
418
409
  const lines = [
419
- `${color}${COLORS.bold}${diagnostic.code}${COLORS.reset} ${diagnostic.message}`
410
+ `${color(colors.bold(diagnostic.code))} ${diagnostic.message}`
420
411
  ];
421
412
  if (diagnostic.url) {
422
- lines.push(` ${COLORS.dim}at ${diagnostic.url}${COLORS.reset}`);
413
+ lines.push(` ${colors.dim(`at ${diagnostic.url}`)}`);
423
414
  }
424
415
  if (diagnostic.file) {
425
416
  const location = root ? relative2(root, diagnostic.file) : diagnostic.file;
426
417
  const column = diagnostic.column === undefined ? "" : `:${diagnostic.column}`;
427
418
  const position = diagnostic.line === undefined ? "" : `:${diagnostic.line}${column}`;
428
419
  const label = diagnostic.url ? "in" : "at";
429
- lines.push(` ${COLORS.dim}${label} ${location}${position}${COLORS.reset}`);
420
+ lines.push(` ${colors.dim(`${label} ${location}${position}`)}`);
430
421
  }
431
422
  if (diagnostic.suggestion) {
432
- lines.push(` ${COLORS.cyan}fix: ${diagnostic.suggestion}${COLORS.reset}`);
423
+ lines.push(` ${colors.cyan(`fix: ${diagnostic.suggestion}`)}`);
433
424
  }
434
425
  if (diagnostic.docsUrl) {
435
- lines.push(` ${COLORS.dim}docs: ${diagnostic.docsUrl}${COLORS.reset}`);
426
+ lines.push(` ${colors.dim(`docs: ${diagnostic.docsUrl}`)}`);
436
427
  }
437
428
  return lines.join(`
438
429
  `);
@@ -552,6 +543,7 @@ import { tmpdir } from "node:os";
552
543
  import { join as join5 } from "pathe";
553
544
 
554
545
  // src/audit/report.ts
546
+ import { colors as colors2 } from "consola/utils";
555
547
  import { relative as relative4 } from "pathe";
556
548
 
557
549
  // src/audit/catalog.ts
@@ -1295,20 +1287,10 @@ var finding = (id, site, detail, fix) => {
1295
1287
  };
1296
1288
 
1297
1289
  // src/audit/report.ts
1298
- var ESC2 = String.fromCodePoint(27);
1299
- var COLORS2 = {
1300
- bold: `${ESC2}[1m`,
1301
- cyan: `${ESC2}[36m`,
1302
- dim: `${ESC2}[2m`,
1303
- green: `${ESC2}[32m`,
1304
- red: `${ESC2}[31m`,
1305
- reset: `${ESC2}[0m`,
1306
- yellow: `${ESC2}[33m`
1307
- };
1308
1290
  var SEVERITY_COLOR = {
1309
- error: COLORS2.red,
1310
- info: `${ESC2}[34m`,
1311
- warning: COLORS2.yellow
1291
+ error: colors2.red,
1292
+ info: colors2.blue,
1293
+ warning: colors2.yellow
1312
1294
  };
1313
1295
  var GLYPH = {
1314
1296
  error: "✖",
@@ -1355,7 +1337,7 @@ var rollup = (diagnostics) => {
1355
1337
  };
1356
1338
  var skippedTiers = (tiers) => Object.keys(TIER_FLAG).filter((tier) => !tiers[tier]).map((tier) => {
1357
1339
  const label = CHECKS.filter((check) => check.tier === tier).length;
1358
- return ` ${COLORS2.dim}⊘ ${tier.padEnd(12)} skipped — pass ${TIER_FLAG[tier]} (${label} checks)${COLORS2.reset}`;
1340
+ return ` ${colors2.dim(`⊘ ${tier.padEnd(12)} skipped — pass ${TIER_FLAG[tier]} (${label} checks)`)}`;
1359
1341
  });
1360
1342
  var activeChecks = (tiers) => CHECKS.filter((check) => tiers[check.tier]).length;
1361
1343
  var auditCount = (result) => activeChecks(result.tiers) * result.pages;
@@ -1367,7 +1349,7 @@ var summaryLine = (counts, audits) => [
1367
1349
  ].join(" · ");
1368
1350
  var findingLine = (diagnostic, root) => {
1369
1351
  const url = diagnostic.url ?? "";
1370
- const source = diagnostic.file ? `${COLORS2.dim}${relative4(root, diagnostic.file)}${diagnostic.line === undefined ? "" : `:${diagnostic.line}`}${COLORS2.reset}` : "";
1352
+ const source = diagnostic.file ? colors2.dim(`${relative4(root, diagnostic.file)}${diagnostic.line === undefined ? "" : `:${diagnostic.line}`}`) : "";
1371
1353
  return ` ${url.padEnd(34)} ${source}`.trimEnd();
1372
1354
  };
1373
1355
  var formatReport = (result, root, options = {}) => {
@@ -1375,35 +1357,35 @@ var formatReport = (result, root, options = {}) => {
1375
1357
  const groups = rollup(result.diagnostics);
1376
1358
  const lines = [];
1377
1359
  const where = result.origin ? `${relative4(root, result.staticDir) || "dist"} + ${result.origin}` : `${relative4(root, result.staticDir) || "dist"} · offline`;
1378
- lines.push("", ` ${COLORS2.bold}blume audit${COLORS2.reset} ${COLORS2.dim}${result.pages} pages · ${where}${COLORS2.reset}`, ` ${summaryLine(counts, auditCount(result))}`, "");
1360
+ lines.push("", ` ${colors2.bold("blume audit")} ${colors2.dim(`${result.pages} pages · ${where}`)}`, ` ${summaryLine(counts, auditCount(result))}`, "");
1379
1361
  if (groups.length === 0) {
1380
- lines.push(` ${COLORS2.green}✔ No issues found.${COLORS2.reset}`, "");
1362
+ lines.push(` ${colors2.green("✔ No issues found.")}`, "");
1381
1363
  }
1382
1364
  let category = null;
1383
1365
  for (const group of groups) {
1384
1366
  const { category: next } = group;
1385
1367
  if (next !== category) {
1386
1368
  category = next;
1387
- lines.push(` ${COLORS2.bold}${category}${COLORS2.reset}`, "");
1369
+ lines.push(` ${colors2.bold(category)}`, "");
1388
1370
  }
1389
1371
  const color = SEVERITY_COLOR[group.severity];
1390
1372
  const pages = `${group.count} page${group.count === 1 ? "" : "s"}`;
1391
- lines.push(` ${color}${GLYPH[group.severity]} ${group.title}${COLORS2.reset} ${COLORS2.dim}${pages}${COLORS2.reset}`);
1373
+ lines.push(` ${color(`${GLYPH[group.severity]} ${group.title}`)} ${colors2.dim(pages)}`);
1392
1374
  const shown = options.verbose ? group.findings : group.findings.slice(0, PREVIEW);
1393
1375
  for (const diagnostic of shown) {
1394
1376
  lines.push(findingLine(diagnostic, root));
1395
1377
  if (options.verbose) {
1396
- lines.push(` ${COLORS2.dim}${diagnostic.message}${COLORS2.reset}`);
1378
+ lines.push(` ${colors2.dim(diagnostic.message)}`);
1397
1379
  }
1398
1380
  }
1399
1381
  const hidden = group.count - shown.length;
1400
1382
  if (hidden > 0) {
1401
- lines.push(` ${COLORS2.dim}… and ${hidden} more (--verbose)${COLORS2.reset}`);
1383
+ lines.push(` ${colors2.dim(`… and ${hidden} more (--verbose)`)}`);
1402
1384
  }
1403
1385
  const [first] = group.findings;
1404
1386
  const fix = first?.suggestion;
1405
1387
  if (fix) {
1406
- lines.push(` ${COLORS2.cyan}fix: ${fix}${COLORS2.reset}`);
1388
+ lines.push(` ${colors2.cyan(`fix: ${fix}`)}`);
1407
1389
  }
1408
1390
  lines.push("");
1409
1391
  }
@@ -1442,10 +1424,10 @@ var formatCatalog = () => {
1442
1424
  const { category: next } = check;
1443
1425
  if (next !== category) {
1444
1426
  category = next;
1445
- lines.push(` ${COLORS2.bold}${category}${COLORS2.reset}`);
1427
+ lines.push(` ${colors2.bold(category)}`);
1446
1428
  }
1447
- const tier = check.tier === "static" ? "" : ` ${COLORS2.dim}[${check.tier}]${COLORS2.reset}`;
1448
- lines.push(` ${SEVERITY_COLOR[check.severity]}${GLYPH[check.severity]}${COLORS2.reset} ${check.id.replace("BLUME_AUDIT_", "").toLowerCase().padEnd(34)} ${COLORS2.dim}${check.title}${COLORS2.reset}${tier}`);
1429
+ const tier = check.tier === "static" ? "" : ` ${colors2.dim(`[${check.tier}]`)}`;
1430
+ lines.push(` ${SEVERITY_COLOR[check.severity](GLYPH[check.severity])} ${check.id.replace("BLUME_AUDIT_", "").toLowerCase().padEnd(34)} ${colors2.dim(check.title)}${tier}`);
1449
1431
  }
1450
1432
  lines.push("", ` ${CHECKS.length} checks.`, "");
1451
1433
  return lines.join(`
@@ -2167,12 +2149,9 @@ var applyDeploymentEnv = (config, env = process.env) => {
2167
2149
  };
2168
2150
 
2169
2151
  // src/audit/checks/indexability.ts
2170
- var parseCanonical = (page) => {
2171
- if (!page.canonical) {
2172
- return null;
2173
- }
2152
+ var parseCanonical = (canonical) => {
2174
2153
  try {
2175
- return new URL(page.canonical);
2154
+ return new URL(canonical);
2176
2155
  } catch {
2177
2156
  return null;
2178
2157
  }
@@ -2188,7 +2167,7 @@ var canonicalChecks = (context, page) => {
2188
2167
  finding("BLUME_AUDIT_CANONICAL_MISSING", pageSite(context, page), "Page has no canonical URL.")
2189
2168
  ] : [];
2190
2169
  }
2191
- const canonical = parseCanonical(page);
2170
+ const canonical = parseCanonical(page.canonical);
2192
2171
  if (!canonical) {
2193
2172
  return [
2194
2173
  finding("BLUME_AUDIT_CANONICAL_BAD_TARGET", pageSite(context, page, ["seo", "canonical"]), `Canonical "${page.canonical}" is not a valid absolute URL.`)
@@ -2658,41 +2637,15 @@ import { readFile as readFile2 } from "node:fs/promises";
2658
2637
  import { join as join7 } from "pathe";
2659
2638
 
2660
2639
  // src/audit/image-size.ts
2661
- var PNG_SIGNATURE = Buffer.from([137, 80, 78, 71]);
2662
- var pngSize = (bytes) => {
2663
- if (bytes.length < 24 || !bytes.subarray(0, 4).equals(PNG_SIGNATURE)) {
2664
- return null;
2665
- }
2666
- return { height: bytes.readUInt32BE(20), width: bytes.readUInt32BE(16) };
2667
- };
2668
- var isSof = (marker) => marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
2669
- var jpegSize = (bytes) => {
2670
- if (bytes.length < 4 || bytes[0] !== 255 || bytes[1] !== 216) {
2671
- return null;
2672
- }
2673
- let offset = 2;
2674
- while (offset + 9 < bytes.length) {
2675
- if (bytes[offset] !== 255) {
2676
- return null;
2677
- }
2678
- const marker = bytes[offset + 1] ?? 0;
2679
- if (isSof(marker)) {
2680
- return {
2681
- height: bytes.readUInt16BE(offset + 5),
2682
- width: bytes.readUInt16BE(offset + 7)
2683
- };
2684
- }
2685
- offset += 2 + bytes.readUInt16BE(offset + 2);
2686
- }
2687
- return null;
2688
- };
2689
- var gifSize = (bytes) => {
2690
- if (bytes.length < 10 || bytes.subarray(0, 4).toString("latin1") !== "GIF8") {
2640
+ import { imageSize as measureImage } from "image-size";
2641
+ var imageSize = (bytes) => {
2642
+ try {
2643
+ const { width, height } = measureImage(bytes);
2644
+ return width > 0 && height > 0 ? { height, width } : null;
2645
+ } catch {
2691
2646
  return null;
2692
2647
  }
2693
- return { height: bytes.readUInt16LE(8), width: bytes.readUInt16LE(6) };
2694
2648
  };
2695
- var imageSize = (bytes) => pngSize(bytes) ?? jpegSize(bytes) ?? gifSize(bytes);
2696
2649
 
2697
2650
  // src/audit/checks/og-image.ts
2698
2651
  var MIN_WIDTH = 600;
@@ -2785,28 +2738,8 @@ var redirectChecks = {
2785
2738
  };
2786
2739
 
2787
2740
  // src/audit/checks/robots.ts
2788
- var disallowMatches = (rule, path) => {
2789
- const anchored = rule.endsWith("$");
2790
- const pattern = anchored ? rule.slice(0, -1) : rule;
2791
- const parts = pattern.split("*");
2792
- let cursor = 0;
2793
- for (const [index, part] of parts.entries()) {
2794
- if (part === "") {
2795
- continue;
2796
- }
2797
- let at;
2798
- if (index === 0) {
2799
- at = path.startsWith(part) ? 0 : -1;
2800
- } else {
2801
- at = path.indexOf(part, cursor);
2802
- }
2803
- if (at === -1) {
2804
- return false;
2805
- }
2806
- cursor = at + part.length;
2807
- }
2808
- return anchored && !pattern.endsWith("*") ? cursor === path.length : true;
2809
- };
2741
+ import robotsParser from "robots-parser";
2742
+ var MATCH_ORIGIN = "https://robots-audit.invalid";
2810
2743
  var robotsChecks = {
2811
2744
  category: "robots",
2812
2745
  run(context) {
@@ -2824,6 +2757,8 @@ var robotsChecks = {
2824
2757
  if (site && robots.sitemaps.length === 0) {
2825
2758
  found.push(finding("BLUME_AUDIT_ROBOTS_SITEMAP_MISSING", { file: robots.file, url: "/robots.txt" }, "robots.txt does not declare a Sitemap."));
2826
2759
  }
2760
+ const parser = robotsParser(`${MATCH_ORIGIN}/robots.txt`, robots.raw);
2761
+ const lines = robots.raw.split(/\r?\n/u);
2827
2762
  for (const loc of context.sitemap?.urls ?? []) {
2828
2763
  let pathname;
2829
2764
  try {
@@ -2832,9 +2767,11 @@ var robotsChecks = {
2832
2767
  continue;
2833
2768
  }
2834
2769
  const path = normalizePath(pathname);
2835
- const rule = robots.disallow.find((entry) => disallowMatches(entry, pathname));
2836
- if (rule) {
2837
- found.push(finding("BLUME_AUDIT_ROBOTS_DISALLOWS_INDEXABLE", { file: robots.file, url: path }, `robots.txt "Disallow: ${rule}" blocks ${path}, which sitemap.xml advertises.`));
2770
+ const url = `${MATCH_ORIGIN}${pathname}`;
2771
+ if (parser.isDisallowed(url, "*")) {
2772
+ const line = parser.getMatchingLineNumber(url, "*");
2773
+ const rule = line > 0 ? lines[line - 1]?.trim() : undefined;
2774
+ found.push(finding("BLUME_AUDIT_ROBOTS_DISALLOWS_INDEXABLE", { file: robots.file, url: path }, `robots.txt "${rule ?? "Disallow"}" blocks ${path}, which sitemap.xml advertises.`));
2838
2775
  }
2839
2776
  }
2840
2777
  return found;
@@ -3058,6 +2995,7 @@ var urlChecks = {
3058
2995
 
3059
2996
  // src/audit/crawl.ts
3060
2997
  import { readFile as readFile3, stat } from "node:fs/promises";
2998
+ import { XMLParser } from "fast-xml-parser";
3061
2999
  import { join as join9, relative as relative6 } from "pathe";
3062
3000
  import { glob } from "tinyglobby";
3063
3001
 
@@ -4225,7 +4163,8 @@ var hostedSearchOptions = (search) => {
4225
4163
  var searchClientTemplate = (config) => {
4226
4164
  const { search } = config;
4227
4165
  if (search.provider === "orama" || search.provider === "flexsearch") {
4228
- return staticSearchClient(search.provider, search.provider === "orama" ? config.i18n?.defaultLocale : undefined);
4166
+ const locale = search.provider === "orama" ? config.i18n?.defaultLocale : undefined;
4167
+ return staticSearchClient(search.provider, locale);
4229
4168
  }
4230
4169
  const hosted = hostedSearchOptions(search);
4231
4170
  if (hosted) {
@@ -4799,6 +4738,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
4799
4738
  exportPdf={${options.exportPdf}}
4800
4739
  exportEpub={${options.exportEpub}}
4801
4740
  feeds={data.feeds}
4741
+ discovery={data.config.discovery}
4802
4742
  siteUrl={data.config.site}
4803
4743
  pageType={frontmatter.type}
4804
4744
  published={frontmatter.date ?? frontmatter.changelog?.date ?? null}
@@ -5001,6 +4941,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
5001
4941
  exportPdf={${options.exportPdf}}
5002
4942
  exportEpub={${options.exportEpub}}
5003
4943
  feeds={data.feeds}
4944
+ discovery={data.config.discovery}
5004
4945
  siteUrl={data.config.site}
5005
4946
  noindex={false}
5006
4947
  structuredDataEnabled={data.config.structuredData}
@@ -5464,35 +5405,39 @@ var routeIndex = (manifest, basePath) => {
5464
5405
  }
5465
5406
  return index;
5466
5407
  };
5467
- var SITEMAP_URL = /<url>(?<block>[\s\S]*?)<\/url>/gu;
5468
- var SITEMAP_LOC = /<loc>(?<loc>[\s\S]*?)<\/loc>/gu;
5469
- var SITEMAP_LASTMOD = /<lastmod>(?<date>[\s\S]*?)<\/lastmod>/u;
5470
- var XML_ENTITIES = {
5471
- "&amp;": "&",
5472
- "&apos;": "'",
5473
- "&gt;": ">",
5474
- "&lt;": "<",
5475
- "&quot;": '"'
5476
- };
5477
- var unescapeXml = (value) => value.replaceAll(/&(?:amp|apos|gt|lt|quot);/gu, (entity) => XML_ENTITIES[entity] ?? entity);
5408
+ var sitemapParser = new XMLParser({
5409
+ htmlEntities: true,
5410
+ ignoreAttributes: true,
5411
+ parseTagValue: false,
5412
+ removeNSPrefix: true
5413
+ });
5478
5414
  var parseSitemap = (file, xml, bytes) => {
5479
5415
  const doc = { bytes, file, lastmod: new Map, urls: [] };
5480
- if (!xml.includes("<urlset")) {
5481
- doc.error = xml.includes("<sitemapindex") ? "sitemap is an index, not a urlset" : "no <urlset> element";
5416
+ let parsed;
5417
+ try {
5418
+ parsed = sitemapParser.parse(xml);
5419
+ } catch {
5420
+ doc.error = "no <urlset> element";
5482
5421
  return doc;
5483
5422
  }
5484
- for (const match of xml.matchAll(SITEMAP_LOC)) {
5485
- const loc = unescapeXml((match.groups?.loc ?? "").trim());
5486
- if (loc) {
5487
- doc.urls.push(loc);
5488
- }
5423
+ if (!Object.hasOwn(parsed, "urlset")) {
5424
+ doc.error = Object.hasOwn(parsed, "sitemapindex") ? "sitemap is an index, not a urlset" : "no <urlset> element";
5425
+ return doc;
5489
5426
  }
5490
- for (const match of xml.matchAll(SITEMAP_URL)) {
5491
- const block = match.groups?.block ?? "";
5492
- const loc = unescapeXml((new RegExp(SITEMAP_LOC.source, "u").exec(block)?.groups?.loc ?? "").trim());
5493
- const lastmod = SITEMAP_LASTMOD.exec(block)?.groups?.date?.trim();
5494
- if (loc && lastmod) {
5495
- doc.lastmod?.set(loc, lastmod);
5427
+ const urlset = parsed.urlset;
5428
+ const entries = typeof urlset === "object" && urlset !== null ? [urlset.url].flat() : [];
5429
+ for (const entry of entries) {
5430
+ if (typeof entry !== "object" || entry === null) {
5431
+ continue;
5432
+ }
5433
+ const { loc, lastmod } = entry;
5434
+ const locText = typeof loc === "string" ? loc.trim() : "";
5435
+ if (!locText) {
5436
+ continue;
5437
+ }
5438
+ doc.urls.push(locText);
5439
+ if (typeof lastmod === "string" && lastmod.trim() !== "") {
5440
+ doc.lastmod?.set(locText, lastmod.trim());
5496
5441
  }
5497
5442
  }
5498
5443
  return doc;
@@ -5512,8 +5457,7 @@ var parseLlms = (file, text) => {
5512
5457
  };
5513
5458
  var ROBOTS_DIRECTIVE = /^(?<field>[a-z-]+)\s*:\s*(?<value>.*)$/iu;
5514
5459
  var parseRobots = (file, text) => {
5515
- const doc = { disallow: [], file, invalid: [], sitemaps: [] };
5516
- let appliesToAll = false;
5460
+ const doc = { file, invalid: [], raw: text, sitemaps: [] };
5517
5461
  for (const [index, raw] of text.split(/\r?\n/u).entries()) {
5518
5462
  const line = raw.trim();
5519
5463
  if (line === "" || line.startsWith("#")) {
@@ -5526,11 +5470,7 @@ var parseRobots = (file, text) => {
5526
5470
  }
5527
5471
  const field = (match.groups?.field ?? "").toLowerCase();
5528
5472
  const value = (match.groups?.value ?? "").trim();
5529
- if (field === "user-agent") {
5530
- appliesToAll = value === "*";
5531
- } else if (field === "disallow" && appliesToAll && value) {
5532
- doc.disallow.push(value);
5533
- } else if (field === "sitemap" && value) {
5473
+ if (field === "sitemap" && value) {
5534
5474
  doc.sitemaps.push(value);
5535
5475
  }
5536
5476
  }
@@ -10230,12 +10170,13 @@ var loadConfig = async (root, options = {}) => {
10230
10170
  };
10231
10171
  const moreIssues = rest.map((d) => ` - ${d.message}`).join(`
10232
10172
  `);
10233
- throw new BlumeError(rest.length > 0 ? {
10173
+ const detail = rest.length > 0 ? {
10234
10174
  ...primary,
10235
10175
  message: `${primary.message}
10236
10176
  ${rest.length} more config issue(s):
10237
10177
  ${moreIssues}`
10238
- } : primary);
10178
+ } : primary;
10179
+ throw new BlumeError(detail);
10239
10180
  }
10240
10181
  const config = applyDeploymentEnv(parsed.data);
10241
10182
  const site = config.deployment.site ?? options.devServerUrl;
@@ -11894,10 +11835,12 @@ var fetchSpecText = async (spec) => {
11894
11835
  if ("text" in last) {
11895
11836
  return last.text;
11896
11837
  }
11897
- if (!last.retryable || attempt === MAX_ATTEMPTS - 1) {
11898
- throw last.error;
11838
+ if (!last.retryable) {
11839
+ break;
11840
+ }
11841
+ if (attempt < MAX_ATTEMPTS - 1) {
11842
+ await sleep(Math.min(last.retryAfter ?? BASE_BACKOFF_MS * 2 ** attempt, MAX_RETRY_WAIT_MS));
11899
11843
  }
11900
- await sleep(Math.min(last.retryAfter ?? BASE_BACKOFF_MS * 2 ** attempt, MAX_RETRY_WAIT_MS));
11901
11844
  }
11902
11845
  throw last.error;
11903
11846
  };
@@ -12263,6 +12206,10 @@ var filesystemSource = (options) => {
12263
12206
  };
12264
12207
 
12265
12208
  // src/core/sources/github-releases.ts
12209
+ import { fromMarkdown } from "mdast-util-from-markdown";
12210
+ import { gfmFromMarkdown } from "mdast-util-gfm";
12211
+ import { toString as mdastToString } from "mdast-util-to-string";
12212
+ import { gfm } from "micromark-extension-gfm";
12266
12213
  var DEFAULT_BASE_URL = "https://api.github.com";
12267
12214
  var DEFAULT_LIMIT = 100;
12268
12215
  var PER_PAGE = 100;
@@ -12271,21 +12218,16 @@ var NON_SLUG3 = /[^a-z0-9]+/gu;
12271
12218
  var EDGE_DASHES = /^-+|-+$/gu;
12272
12219
  var DESCRIPTION_MAX = 160;
12273
12220
  var DESCRIPTION_MIN = 110;
12274
- var CODE_FENCE2 = /```[\s\S]*?```/gu;
12275
- var HEADING_LINE = /^#{1,6}\s.*$/gmu;
12276
- var LIST_MARK = /^\s*(?:[-*+]|\d+[.)])\s+/u;
12277
- var CHANGESET_HASH = /^[0-9a-f]{7,40}:\s+/u;
12278
- var IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
12279
- var LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
12280
- var INLINE_CODE2 = /`(?<code>[^`]+)`/gu;
12281
- var HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
12282
- var MARKDOWN_PUNCT = /[*_~>]+/gu;
12221
+ var CHANGESET_HASH = /^(?<mark>\s*(?:[-*+]|\d+[.)])\s+)[0-9a-f]{7,40}:\s+/gmu;
12283
12222
  var WHITESPACE2 = /\s+/gu;
12284
12223
  var TRAILING_FRAGMENT = /[\s,;:.—–-]+$/u;
12224
+ var NON_PROSE = new Set(["code", "heading", "html", "thematicBreak"]);
12285
12225
  var releaseDescription = (body) => {
12286
- const text = body.replaceAll(CODE_FENCE2, " ").replaceAll(HEADING_LINE, "").split(`
12287
- `).map((line) => line.replace(LIST_MARK, "").replace(CHANGESET_HASH, "")).join(`
12288
- `).replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>").replaceAll(INLINE_CODE2, "$<code>").replaceAll(HTML_OR_JSX, " ").replaceAll(MARKDOWN_PUNCT, " ").replaceAll(WHITESPACE2, " ").trim();
12226
+ const tree = fromMarkdown(body.replaceAll(CHANGESET_HASH, "$<mark>"), {
12227
+ extensions: [gfm()],
12228
+ mdastExtensions: [gfmFromMarkdown()]
12229
+ });
12230
+ const text = tree.children.filter((node) => !NON_PROSE.has(node.type)).map((node) => mdastToString(node, { includeHtml: false, includeImageAlt: false })).join(" ").replaceAll(WHITESPACE2, " ").trim();
12289
12231
  if (!text) {
12290
12232
  return;
12291
12233
  }
@@ -12406,42 +12348,7 @@ var githubReleasesSource = (options, ctx) => {
12406
12348
  };
12407
12349
 
12408
12350
  // src/core/sources/mdx-remote.ts
12409
- var REGEX_SPECIAL = /[.*+?^${}()|[\]\\]/u;
12410
- var escapeChar = (char) => REGEX_SPECIAL.test(char) ? `\\${char}` : char;
12411
- var globToken = (pattern, i) => {
12412
- const char = pattern[i] ?? "";
12413
- if (char === "*") {
12414
- if (pattern[i + 1] === "*") {
12415
- if (pattern[i + 2] === "/") {
12416
- return { next: i + 3, source: "(?:.*/)?" };
12417
- }
12418
- return { next: i + 2, source: ".*" };
12419
- }
12420
- return { next: i + 1, source: "[^/]*" };
12421
- }
12422
- if (char === "?") {
12423
- return { next: i + 1, source: "[^/]" };
12424
- }
12425
- if (char === "{") {
12426
- const end = pattern.indexOf("}", i);
12427
- if (end !== -1) {
12428
- const options = pattern.slice(i + 1, end).split(",").map((part) => [...part].map(escapeChar).join("")).join("|");
12429
- return { next: end + 1, source: `(?:${options})` };
12430
- }
12431
- }
12432
- return { next: i + 1, source: escapeChar(char) };
12433
- };
12434
- var globToRegExp = (pattern) => {
12435
- let source = "";
12436
- let i = 0;
12437
- while (i < pattern.length) {
12438
- const token = globToken(pattern, i);
12439
- source += token.source;
12440
- i = token.next;
12441
- }
12442
- return new RegExp(`^${source}$`, "u");
12443
- };
12444
- var matchesInclude = (ref, patterns) => patterns.some((pattern) => globToRegExp(pattern).test(ref));
12351
+ import picomatch from "picomatch";
12445
12352
  var GITHUB_HOSTS = new Set(["api.github.com", "raw.githubusercontent.com"]);
12446
12353
  var githubHeaders2 = (url) => {
12447
12354
  const token = process.env.GITHUB_TOKEN;
@@ -12466,12 +12373,13 @@ var enumerateGithub = async (github, include, doFetch) => {
12466
12373
  }
12467
12374
  const body = await res.json();
12468
12375
  const prefix = base ? `${base}/` : "";
12376
+ const included = picomatch(include);
12469
12377
  const refs = (body.tree ?? []).flatMap((node) => {
12470
12378
  if (!(node.type === "blob" && node.path.startsWith(prefix))) {
12471
12379
  return [];
12472
12380
  }
12473
12381
  const rel = node.path.slice(prefix.length);
12474
- if (!matchesInclude(rel, include)) {
12382
+ if (!included(rel)) {
12475
12383
  return [];
12476
12384
  }
12477
12385
  return [
@@ -12503,7 +12411,8 @@ var mdxRemoteSource = (options, ctx) => {
12503
12411
  return await enumerateGithub(options.github, options.include, doFetch);
12504
12412
  }
12505
12413
  const base = (options.url ?? "").replace(/\/$/u, "");
12506
- const refs = (options.files ?? []).flatMap((ref) => matchesInclude(ref, options.include) ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }] : []);
12414
+ const included = picomatch(options.include);
12415
+ const refs = (options.files ?? []).flatMap((ref) => included(ref) ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }] : []);
12507
12416
  return { refs, truncated: false };
12508
12417
  };
12509
12418
  const fetchEntry = async (item) => {
@@ -12662,23 +12571,19 @@ var RATE_LIMITED = 429;
12662
12571
  var MAX_RETRIES = 4;
12663
12572
  var BASE_DELAY_MS = 500;
12664
12573
  var SECOND_MS2 = 1000;
12665
- var withNotionRetry = async (call) => {
12666
- let lastError;
12667
- for (let attempt = 0;attempt <= MAX_RETRIES; attempt += 1) {
12668
- try {
12669
- return await call();
12670
- } catch (error) {
12671
- lastError = error;
12672
- const { status } = error;
12673
- if (status !== RATE_LIMITED || attempt === MAX_RETRIES) {
12674
- throw error;
12675
- }
12676
- const retryAfter = Number(error.headers?.["retry-after"]);
12677
- const wait = retryAfter > 0 ? retryAfter * SECOND_MS2 : BASE_DELAY_MS * 2 ** attempt;
12678
- await sleep2(wait);
12574
+ var withNotionRetry = async (call, attempt = 0) => {
12575
+ try {
12576
+ return await call();
12577
+ } catch (error) {
12578
+ const { status } = error;
12579
+ if (status !== RATE_LIMITED || attempt === MAX_RETRIES) {
12580
+ throw error;
12679
12581
  }
12582
+ const retryAfter = Number(error.headers?.["retry-after"]);
12583
+ const wait = retryAfter > 0 ? retryAfter * SECOND_MS2 : BASE_DELAY_MS * 2 ** attempt;
12584
+ await sleep2(wait);
12585
+ return withNotionRetry(call, attempt + 1);
12680
12586
  }
12681
- throw lastError instanceof Error ? lastError : new Error("Notion request failed after retries.");
12682
12587
  };
12683
12588
  var collectAll = async (page, cursor, acc = []) => {
12684
12589
  const res = await page(cursor);
@@ -13401,11 +13306,7 @@ var scanProject = async (root, options = {}) => {
13401
13306
  };
13402
13307
 
13403
13308
  // src/cli/internal-error.ts
13404
- var ESC3 = String.fromCodePoint(27);
13405
- var DIM = `${ESC3}[2m`;
13406
- var RED = `${ESC3}[31m`;
13407
- var BOLD = `${ESC3}[1m`;
13408
- var RESET = `${ESC3}[0m`;
13309
+ import { colors as colors3 } from "consola/utils";
13409
13310
  var ISSUES_URL = "https://github.com/haydenbleasel/blume/issues";
13410
13311
  var BLUME_FRAME = /(?<abs>(?:\/[^\s()]*\/|[A-Za-z]:\\[^\s()]*\\)\.blume[/\\][^\s()]*)/gu;
13411
13312
  var BLUME_MARKER = /[/\\]\.blume[/\\]/u;
@@ -13416,16 +13317,21 @@ var remapBlumeStack = (stack) => stack.replaceAll(BLUME_FRAME, (match) => {
13416
13317
  var reportInternalError = (error) => {
13417
13318
  const err = error instanceof Error ? error : new Error(String(error));
13418
13319
  const lines = [
13419
- `${RED}${BOLD}BLUME_INTERNAL${RESET} An unexpected error occurred.`,
13320
+ `${colors3.red(colors3.bold("BLUME_INTERNAL"))} An unexpected error occurred.`,
13420
13321
  ` ${err.message}`
13421
13322
  ];
13422
13323
  const stack = remapBlumeStack(err.stack ?? "").split(`
13423
13324
  `).slice(1, 5).map((line) => line.trim()).filter(Boolean);
13424
13325
  if (stack.length > 0) {
13425
- lines.push("", `${DIM}${stack.join(`
13426
- `)}${RESET}`);
13326
+ lines.push("", colors3.dim(stack.join(`
13327
+ `)));
13427
13328
  }
13428
- lines.push("", "This is likely a bug in Blume. Please report it with the details below:", ` ${DIM}Blume: ${getBlumeVersion()}`, ` Node: ${process.version}`, ` Platform: ${process.platform} ${process.arch}${RESET}`, ` ${ISSUES_URL}`);
13329
+ lines.push("", "This is likely a bug in Blume. Please report it with the details below:", colors3.dim([
13330
+ ` Blume: ${getBlumeVersion()}`,
13331
+ ` Node: ${process.version}`,
13332
+ ` Platform: ${process.platform} ${process.arch}`
13333
+ ].join(`
13334
+ `)), ` ${ISSUES_URL}`);
13429
13335
  process.stderr.write(`${lines.join(`
13430
13336
  `)}
13431
13337
  `);
@@ -13581,15 +13487,13 @@ var auditCommand = defineCommand2({
13581
13487
 
13582
13488
  // src/cli/commands/build.ts
13583
13489
  import { existsSync as existsSync18 } from "node:fs";
13584
- import { mkdir as mkdir7, readdir as readdir2, readFile as readFile17, stat as stat3, writeFile as writeFile8 } from "node:fs/promises";
13490
+ import { mkdir as mkdir8, readdir as readdir2, readFile as readFile17, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
13585
13491
  import { build } from "astro";
13586
13492
  import { defineCommand as defineCommand3 } from "citty";
13587
- import { dirname as dirname12, join as join30, resolve as resolve10 } from "pathe";
13588
-
13589
- // src/deploy/xml.ts
13590
- var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
13493
+ import { dirname as dirname13, join as join30, resolve as resolve10 } from "pathe";
13591
13494
 
13592
13495
  // src/deploy/rss.ts
13496
+ import { escape as escapeXml } from "html-escaper";
13593
13497
  var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
13594
13498
  var pageDate = (page) => {
13595
13499
  const raw = page.meta.date ?? page.meta.changelog?.date;
@@ -13881,6 +13785,7 @@ var readEntryText = async (ctx, page) => {
13881
13785
  };
13882
13786
 
13883
13787
  // src/ai/component-markdown.ts
13788
+ import { markdownTable } from "markdown-table";
13884
13789
  import { mdxToMdast } from "satteri";
13885
13790
 
13886
13791
  // src/components/content/youtube.ts
@@ -13978,14 +13883,11 @@ var typeTable = ({ children, props }) => {
13978
13883
  const typeCell = info.typeDescriptionLink ? `[${cellCode(info.type)}](${cellText(info.typeDescriptionLink)})` : cellCode(info.type);
13979
13884
  const defaultCell = info.default === undefined ? "-" : cellCode(info.default);
13980
13885
  const description = cellText([info.description, info.typeDescription].filter((part) => typeof part === "string" && part !== "").join(" "));
13981
- return `| ${prop} | ${typeCell} | ${defaultCell} | ${description} |`;
13886
+ return [prop, typeCell, defaultCell, description];
13982
13887
  });
13983
- const table = rows.length > 0 ? [
13984
- "| Prop | Type | Default | Description |",
13985
- "| --- | --- | --- | --- |",
13986
- ...rows
13987
- ].join(`
13988
- `) : "";
13888
+ const table = rows.length > 0 ? markdownTable([["Prop", "Type", "Default", "Description"], ...rows], {
13889
+ alignDelimiters: false
13890
+ }) : "";
13989
13891
  return [table, children].filter(Boolean).join(`
13990
13892
 
13991
13893
  `);
@@ -14944,6 +14846,9 @@ var buildRobots = (project) => {
14944
14846
  `;
14945
14847
  };
14946
14848
 
14849
+ // src/deploy/sitemap.ts
14850
+ import { escape as escapeXml2 } from "html-escaper";
14851
+
14947
14852
  // src/astro/pages.ts
14948
14853
  import { extname as extname6, relative as relative12 } from "pathe";
14949
14854
  import { glob as glob4, globSync } from "tinyglobby";
@@ -15039,7 +14944,7 @@ var buildSitemap = (project) => {
15039
14944
  const seen = new Set;
15040
14945
  const urls = [];
15041
14946
  const pushUrl = (route, lastModified) => {
15042
- const loc = escapeXml(encodeURI(`${base}${route}`));
14947
+ const loc = escapeXml2(encodeURI(`${base}${route}`));
15043
14948
  if (seen.has(loc)) {
15044
14949
  return;
15045
14950
  }
@@ -15193,25 +15098,25 @@ var pageFacets = (page, config) => {
15193
15098
  };
15194
15099
 
15195
15100
  // src/search/documents.ts
15196
- var CODE_FENCE3 = /```[\s\S]*?```/gu;
15197
- var INLINE_CODE3 = /`(?<code>[^`]+)`/gu;
15198
- var HTML_OR_JSX2 = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
15199
- var IMAGE2 = /!\[[^\]]*\]\([^)]*\)/gu;
15200
- var LINK2 = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
15101
+ var CODE_FENCE2 = /```[\s\S]*?```/gu;
15102
+ var INLINE_CODE2 = /`(?<code>[^`]+)`/gu;
15103
+ var HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
15104
+ var IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
15105
+ var LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
15201
15106
  var HEADING_MARK = /^#{1,6}\s+/gmu;
15202
- var MARKDOWN_PUNCT2 = /[*_~>]+/gu;
15107
+ var MARKDOWN_PUNCT = /[*_~>]+/gu;
15203
15108
  var WHITESPACE3 = /\s+/gu;
15204
15109
  var toPlainText = (markdown) => {
15205
- const withoutBlocks = markdown.replaceAll(CODE_FENCE3, " ").replaceAll(IMAGE2, " ").replaceAll(LINK2, "$<text>");
15110
+ const withoutBlocks = markdown.replaceAll(CODE_FENCE2, " ").replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>");
15206
15111
  const pieces = [];
15207
15112
  let cursor = 0;
15208
- for (const match of withoutBlocks.matchAll(INLINE_CODE3)) {
15113
+ for (const match of withoutBlocks.matchAll(INLINE_CODE2)) {
15209
15114
  const start = match.index ?? 0;
15210
- pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX2, " "), match.groups?.code ?? "");
15115
+ pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "), match.groups?.code ?? "");
15211
15116
  cursor = start + match[0].length;
15212
15117
  }
15213
- pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX2, " "));
15214
- return pieces.join("").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT2, " ").replaceAll(WHITESPACE3, " ").trim();
15118
+ pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
15119
+ return pieces.join("").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT, " ").replaceAll(WHITESPACE3, " ").trim();
15215
15120
  };
15216
15121
  var buildCrumbIndex = (sidebar) => {
15217
15122
  const index = new Map;
@@ -15532,21 +15437,19 @@ var refuseIfDevRunning = (root, action, options = {}) => {
15532
15437
 
15533
15438
  // src/astro/generate.ts
15534
15439
  import { createHash as createHash3 } from "node:crypto";
15535
- import { existsSync as existsSync16, readFileSync as readFileSync9, realpathSync } from "node:fs";
15440
+ import { existsSync as existsSync16, readFileSync as readFileSync8, realpathSync } from "node:fs";
15536
15441
  import {
15537
15442
  lstat,
15538
- mkdir as mkdir6,
15443
+ mkdir as mkdir7,
15539
15444
  readFile as readFile16,
15540
15445
  readlink,
15541
15446
  realpath,
15542
- rename,
15543
15447
  rm as rm2,
15544
- symlink,
15545
- writeFile as writeFile7
15448
+ symlink
15546
15449
  } from "node:fs/promises";
15547
- import { createRequire as createRequire5 } from "node:module";
15548
- import { pathToFileURL as pathToFileURL3 } from "node:url";
15549
- import { basename as basename3, dirname as dirname10, join as join28, normalize as normalize3, relative as relative14, resolve as resolve8 } from "pathe";
15450
+ import { createRequire as createRequire4 } from "node:module";
15451
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
15452
+ import { basename as basename3, dirname as dirname11, join as join28, normalize as normalize3, relative as relative14, resolve as resolve8 } from "pathe";
15550
15453
  import { glob as glob7 } from "tinyglobby";
15551
15454
 
15552
15455
  // src/ai/ask-data.ts
@@ -15941,10 +15844,6 @@ var finalize = (key, group, descriptor, label, identifier, warnings) => {
15941
15844
  if (client === "only" && source && !source.framework) {
15942
15845
  warnings.push(`Override "${key}" uses client: "only" but its framework couldn't be inferred; reference a .tsx/.jsx/.vue/.svelte file.`);
15943
15846
  }
15944
- if (client && !source) {
15945
- warnings.push(`Override "${key}" declares client: "${client}" but its component couldn't be resolved to a file, so it can't hydrate. Reference it by an imported component or a path string.`);
15946
- return { identifier, key, source: null };
15947
- }
15948
15847
  if (!client && source?.framework) {
15949
15848
  warnings.push(`Override "${key}" points to a ${FRAMEWORK_LABEL[source.framework]} component (${label}) but has no hydration mode, so it renders as static HTML with no interactivity. Add one, e.g. \`${key}: { component: ${JSON.stringify(label)}, client: "load" }\`.`);
15950
15849
  }
@@ -16033,128 +15932,22 @@ var analyzeComponentOverrides = (source, filePath) => {
16033
15932
  return result;
16034
15933
  };
16035
15934
 
16036
- // src/core/tsconfig-aliases.ts
16037
- import { existsSync as existsSync13, readFileSync as readFileSync6, statSync } from "node:fs";
16038
- import { createRequire as createRequire3 } from "node:module";
16039
- import { pathToFileURL as pathToFileURL2 } from "node:url";
16040
- import { dirname as dirname9, isAbsolute as isAbsolute7, join as join22, resolve as resolve7 } from "pathe";
16041
- var scanJsonChar = (text2, index, inString) => {
16042
- const char = text2[index];
16043
- if (inString) {
16044
- if (char === "\\") {
16045
- return {
16046
- append: char + (text2[index + 1] ?? ""),
16047
- inString: true,
16048
- next: index + 2
16049
- };
16050
- }
16051
- return { append: char ?? "", inString: char !== '"', next: index + 1 };
16052
- }
16053
- if (char === '"') {
16054
- return { append: char, inString: true, next: index + 1 };
16055
- }
16056
- if (char === "/" && text2[index + 1] === "/") {
16057
- const newline = text2.indexOf(`
16058
- `, index + 2);
16059
- return {
16060
- append: "",
16061
- inString: false,
16062
- next: newline === -1 ? text2.length : newline
16063
- };
16064
- }
16065
- if (char === "/" && text2[index + 1] === "*") {
16066
- const end = text2.indexOf("*/", index + 2);
16067
- return {
16068
- append: "",
16069
- inString: false,
16070
- next: end === -1 ? text2.length : end + 2
16071
- };
16072
- }
16073
- return { append: char ?? "", inString: false, next: index + 1 };
16074
- };
16075
- var stripJsonComments = (text2) => {
16076
- let out = "";
16077
- let inString = false;
16078
- let index = 0;
16079
- while (index < text2.length) {
16080
- const {
16081
- append,
16082
- inString: nextInString,
16083
- next
16084
- } = scanJsonChar(text2, index, inString);
16085
- out += append;
16086
- inString = nextInString;
16087
- index = next;
16088
- }
16089
- return out;
16090
- };
16091
- var TRAILING_COMMA = /,(?<rest>\s*[}\]])/gu;
16092
- var parseJsonc = (text2) => {
16093
- try {
16094
- const cleaned = stripJsonComments(text2).replaceAll(TRAILING_COMMA, "$<rest>");
16095
- const value = JSON.parse(cleaned);
16096
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
16097
- } catch {
16098
- return null;
16099
- }
15935
+ // src/core/fs-atomic.ts
15936
+ import { mkdir as mkdir6 } from "node:fs/promises";
15937
+ import { dirname as dirname9 } from "pathe";
15938
+ import writeFileAtomic from "write-file-atomic";
15939
+ var writeTextAtomic = async (path, text2) => {
15940
+ await mkdir6(dirname9(path), { recursive: true });
15941
+ await writeFileAtomic(path, text2, { encoding: "utf-8", fsync: false });
16100
15942
  };
16101
- var isFile = (path) => {
16102
- try {
16103
- return statSync(path).isFile();
16104
- } catch {
16105
- return false;
16106
- }
16107
- };
16108
- var resolveExtends = (spec, fromDir) => {
16109
- if (spec.startsWith(".") || isAbsolute7(spec)) {
16110
- const candidates = spec.endsWith(".json") ? [resolve7(fromDir, spec)] : [
16111
- resolve7(fromDir, `${spec}.json`),
16112
- resolve7(fromDir, spec, "tsconfig.json"),
16113
- resolve7(fromDir, spec)
16114
- ];
16115
- return candidates.find(isFile) ?? null;
16116
- }
16117
- try {
16118
- const requireFromDir = createRequire3(pathToFileURL2(join22(fromDir, "_.js")).href);
16119
- for (const sub of [`${spec}/tsconfig.json`, spec]) {
16120
- try {
16121
- return requireFromDir.resolve(sub);
16122
- } catch {}
16123
- }
16124
- } catch {}
16125
- return null;
16126
- };
16127
- var loadPaths = (file, seen) => {
16128
- if (seen.has(file) || !existsSync13(file)) {
16129
- return null;
16130
- }
16131
- seen.add(file);
16132
- const json = parseJsonc(readFileSync6(file, "utf-8"));
16133
- if (!json) {
16134
- return null;
16135
- }
16136
- const options = json.compilerOptions ?? {};
16137
- if (options.paths && typeof options.paths === "object") {
16138
- const baseUrl = typeof options.baseUrl === "string" ? options.baseUrl : ".";
16139
- return {
16140
- baseDir: resolve7(dirname9(file), baseUrl),
16141
- paths: options.paths
16142
- };
16143
- }
16144
- const bases = Array.isArray(json.extends) ? json.extends : [json.extends].filter(Boolean);
16145
- for (const base of bases) {
16146
- if (typeof base !== "string") {
16147
- continue;
16148
- }
16149
- const resolved = resolveExtends(base, dirname9(file));
16150
- const found = resolved ? loadPaths(resolved, seen) : null;
16151
- if (found) {
16152
- return found;
16153
- }
16154
- }
16155
- return null;
16156
- };
16157
- var toAlias = (key, value, baseDir) => {
15943
+
15944
+ // src/core/tsconfig-aliases.ts
15945
+ import { existsSync as existsSync13 } from "node:fs";
15946
+ import { parseTsconfig } from "get-tsconfig";
15947
+ import { dirname as dirname10, join as join22, resolve as resolve7 } from "pathe";
15948
+ var CONFIG_DIR_TEMPLATE = "${configDir}";
15949
+ var substituteConfigDir = (value, configDir) => value.startsWith(CONFIG_DIR_TEMPLATE) ? join22(configDir, value.slice(CONFIG_DIR_TEMPLATE.length)) : value;
15950
+ var toAlias = (key, value, baseDir, configDir) => {
16158
15951
  const first = Array.isArray(value) ? value[0] : value;
16159
15952
  if (typeof first !== "string") {
16160
15953
  return null;
@@ -16164,20 +15957,31 @@ var toAlias = (key, value, baseDir) => {
16164
15957
  if (find === "" || find === "*") {
16165
15958
  return null;
16166
15959
  }
16167
- return { find, replacement: resolve7(baseDir, target) };
15960
+ return {
15961
+ find,
15962
+ replacement: resolve7(baseDir, substituteConfigDir(target, configDir))
15963
+ };
16168
15964
  };
16169
15965
  var resolveTsconfigAliases = (root) => {
16170
15966
  const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join22(root, name)).find((file) => existsSync13(file));
16171
15967
  if (!entry) {
16172
15968
  return {};
16173
15969
  }
16174
- const loaded = loadPaths(entry, new Set);
16175
- if (!loaded) {
15970
+ let options;
15971
+ try {
15972
+ options = parseTsconfig(entry).compilerOptions;
15973
+ } catch {
15974
+ return {};
15975
+ }
15976
+ const paths = options?.paths;
15977
+ if (!paths) {
16176
15978
  return {};
16177
15979
  }
15980
+ const configDir = dirname10(entry);
15981
+ const baseDir = resolve7(configDir, substituteConfigDir(options?.baseUrl ?? ".", configDir));
16178
15982
  const aliases = {};
16179
- for (const [key, value] of Object.entries(loaded.paths)) {
16180
- const alias = toAlias(key, value, loaded.baseDir);
15983
+ for (const [key, value] of Object.entries(paths)) {
15984
+ const alias = toAlias(key, value, baseDir, configDir);
16181
15985
  if (alias) {
16182
15986
  aliases[alias.find] = alias.replacement;
16183
15987
  }
@@ -16187,9 +15991,9 @@ var resolveTsconfigAliases = (root) => {
16187
15991
 
16188
15992
  // src/og/derive.ts
16189
15993
  import { existsSync as existsSync14 } from "node:fs";
16190
- import { isAbsolute as isAbsolute8, join as join23 } from "pathe";
15994
+ import { isAbsolute as isAbsolute7, join as join23 } from "pathe";
16191
15995
  var CARD_WEIGHTS = [400, 600];
16192
- var absoluteSrc = (root, src) => isAbsolute8(src) ? src : join23(root, src);
15996
+ var absoluteSrc = (root, src) => isAbsolute7(src) ? src : join23(root, src);
16193
15997
  var googleWeights = (weights) => {
16194
15998
  const numbers = weights.filter((weight) => typeof weight === "number");
16195
15999
  const used = numbers.filter((weight) => CARD_WEIGHTS.includes(weight));
@@ -16288,7 +16092,7 @@ var missingFontFiles = (options, root) => {
16288
16092
  };
16289
16093
 
16290
16094
  // src/og/logo.ts
16291
- import { existsSync as existsSync15, readFileSync as readFileSync7 } from "node:fs";
16095
+ import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
16292
16096
  import { join as join24 } from "pathe";
16293
16097
  var resolveOgLogo = (project, source) => {
16294
16098
  if (!source?.toLowerCase().endsWith(".svg")) {
@@ -16299,12 +16103,12 @@ var resolveOgLogo = (project, source) => {
16299
16103
  join24(project.context.root, "public", relative13),
16300
16104
  join24(project.context.root, relative13)
16301
16105
  ].find((path) => existsSync15(path));
16302
- return file ? readFileSync7(file, "utf-8") : undefined;
16106
+ return file ? readFileSync6(file, "utf-8") : undefined;
16303
16107
  };
16304
16108
 
16305
16109
  // src/openapi/scalar.ts
16306
16110
  import { readFile as readFile13 } from "node:fs/promises";
16307
- import { isAbsolute as isAbsolute9, join as join25 } from "pathe";
16111
+ import { isAbsolute as isAbsolute8, join as join25 } from "pathe";
16308
16112
 
16309
16113
  // src/theme/palette.ts
16310
16114
  var FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
@@ -16410,7 +16214,7 @@ var specConfiguration = async (spec, root) => {
16410
16214
  if (URL_SPEC2.test(spec)) {
16411
16215
  return { config: { url: spec } };
16412
16216
  }
16413
- const absolute = isAbsolute9(spec) ? spec : join25(root, spec);
16217
+ const absolute = isAbsolute8(spec) ? spec : join25(root, spec);
16414
16218
  try {
16415
16219
  return { config: { content: await readFile13(absolute, "utf-8") } };
16416
16220
  } catch {
@@ -17242,9 +17046,9 @@ ${options.userCss}
17242
17046
  `;
17243
17047
 
17244
17048
  // src/theme/twoslash.ts
17245
- import { readFileSync as readFileSync8 } from "node:fs";
17246
- import { createRequire as createRequire4 } from "node:module";
17247
- var require2 = createRequire4(import.meta.url);
17049
+ import { readFileSync as readFileSync7 } from "node:fs";
17050
+ import { createRequire as createRequire3 } from "node:module";
17051
+ var require2 = createRequire3(import.meta.url);
17248
17052
  var OVERRIDES = `
17249
17053
  /* Twoslash: theme the rich renderer with Blume tokens. */
17250
17054
  :root {
@@ -17319,7 +17123,7 @@ var OVERRIDES = `
17319
17123
  `;
17320
17124
  var twoslashCss = () => {
17321
17125
  const file = require2.resolve("@shikijs/twoslash/style-rich.css");
17322
- return `${readFileSync8(file, "utf-8")}
17126
+ return `${readFileSync7(file, "utf-8")}
17323
17127
  ${OVERRIDES}`;
17324
17128
  };
17325
17129
 
@@ -17460,7 +17264,7 @@ var readClientMode = (source, file, warnings) => {
17460
17264
  };
17461
17265
  var discoverIslands = async (root) => {
17462
17266
  const dir = join26(root, "islands");
17463
- const matches2 = await glob5(["**/*.{jsx,svelte,tsx,vue}"], {
17267
+ const matches2 = await glob5(["**/*"], {
17464
17268
  absolute: true,
17465
17269
  cwd: dir,
17466
17270
  onlyFiles: true
@@ -17569,7 +17373,7 @@ var exampleMarkdownLookup = (examples) => Object.fromEntries(examples.map((examp
17569
17373
  var BLUME_SRC = join28(packageRoot(), "src");
17570
17374
  var canResolveFrom = (fromDir, spec) => {
17571
17375
  try {
17572
- createRequire5(pathToFileURL3(join28(fromDir, "_.js")).href).resolve(spec);
17376
+ createRequire4(pathToFileURL2(join28(fromDir, "_.js")).href).resolve(spec);
17573
17377
  return true;
17574
17378
  } catch {
17575
17379
  return false;
@@ -17580,7 +17384,7 @@ var resolveReactCompiler = (config, needsReact, pkgDir = packageRoot()) => {
17580
17384
  return null;
17581
17385
  }
17582
17386
  try {
17583
- return createRequire5(pathToFileURL3(join28(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
17387
+ return createRequire4(pathToFileURL2(join28(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
17584
17388
  } catch {
17585
17389
  return null;
17586
17390
  }
@@ -17603,7 +17407,7 @@ var resolvedAstroHit = (fromDir) => {
17603
17407
  if (pkg) {
17604
17408
  return { modulesDir, pkg };
17605
17409
  }
17606
- const parent = dirname10(dir);
17410
+ const parent = dirname11(dir);
17607
17411
  if (parent === dir) {
17608
17412
  return null;
17609
17413
  }
@@ -17619,7 +17423,7 @@ var sameRealDir = (a, b) => {
17619
17423
  };
17620
17424
  var depsCandidates = (pkgDir) => [
17621
17425
  join28(pkgDir, "node_modules"),
17622
- dirname10(pkgDir)
17426
+ dirname11(pkgDir)
17623
17427
  ];
17624
17428
  var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync16(join28(dir, ...segments))) ?? null;
17625
17429
  var linkDepsJunction = async (link, depsDir) => {
@@ -17634,13 +17438,13 @@ var linkDepsJunction = async (link, depsDir) => {
17634
17438
  return;
17635
17439
  }
17636
17440
  try {
17637
- if (resolve8(dirname10(link), await readlink(link)) === resolve8(depsDir)) {
17441
+ if (resolve8(dirname11(link), await readlink(link)) === resolve8(depsDir)) {
17638
17442
  return;
17639
17443
  }
17640
17444
  } catch {}
17641
17445
  await rm2(link, { force: true });
17642
17446
  }
17643
- await mkdir6(dirname10(link), { recursive: true });
17447
+ await mkdir7(dirname11(link), { recursive: true });
17644
17448
  await symlink(depsDir, link, "junction");
17645
17449
  };
17646
17450
  var readPkgVersion = (pkgJsonPath) => {
@@ -17648,7 +17452,7 @@ var readPkgVersion = (pkgJsonPath) => {
17648
17452
  return null;
17649
17453
  }
17650
17454
  try {
17651
- return JSON.parse(readFileSync9(pkgJsonPath, "utf-8")).version ?? null;
17455
+ return JSON.parse(readFileSync8(pkgJsonPath, "utf-8")).version ?? null;
17652
17456
  } catch {
17653
17457
  return null;
17654
17458
  }
@@ -17797,15 +17601,7 @@ var writeIfChanged = async (path, content) => {
17797
17601
  if (existing === content) {
17798
17602
  return false;
17799
17603
  }
17800
- await mkdir6(dirname10(path), { recursive: true });
17801
- const tmp = `${path}.${process.pid}.tmp`;
17802
- await writeFile7(tmp, content, "utf-8");
17803
- try {
17804
- await rename(tmp, path);
17805
- } catch (error) {
17806
- await rm2(tmp, { force: true });
17807
- throw error;
17808
- }
17604
+ await writeTextAtomic(path, content);
17809
17605
  return true;
17810
17606
  };
17811
17607
  var pruneOrphans = async (srcDir, written) => {
@@ -17874,7 +17670,7 @@ var readLogoSvg = (project, source) => {
17874
17670
  join28(project.context.root, "public", rel),
17875
17671
  join28(project.context.root, rel)
17876
17672
  ].find((path) => existsSync16(path));
17877
- return file ? readFileSync9(file, "utf-8") : undefined;
17673
+ return file ? readFileSync8(file, "utf-8") : undefined;
17878
17674
  };
17879
17675
  var resolveLogo = (project) => {
17880
17676
  const { logo } = project.config;
@@ -17917,7 +17713,7 @@ var faviconType = (name) => {
17917
17713
  const ext = name.split(".").pop()?.toLowerCase();
17918
17714
  return ext ? FAVICON_TYPES[ext] : undefined;
17919
17715
  };
17920
- var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync9(file).toString("base64")}`;
17716
+ var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync8(file).toString("base64")}`;
17921
17717
  var defaultFavicon = () => ({
17922
17718
  href: inlineDataUri(join28(BLUME_SRC, "assets", "icon.png"), "image/png"),
17923
17719
  type: "image/png"
@@ -18042,6 +17838,10 @@ var buildRuntimeData = (project) => {
18042
17838
  codeWrap: config.markdown.code.wrap,
18043
17839
  dateFormat: config.dateFormat,
18044
17840
  description: config.description,
17841
+ discovery: {
17842
+ agentReadability: config.seo.agentReadability,
17843
+ llmsTxt: config.ai.llmsTxt.enabled
17844
+ },
18045
17845
  favicon: resolveFavicon(project),
18046
17846
  feedback: config.feedback,
18047
17847
  i18n: i18n ? {
@@ -18441,44 +18241,9 @@ var generateRuntime = async (project) => {
18441
18241
  };
18442
18242
 
18443
18243
  // src/cli/env.ts
18444
- import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
18445
- import { dirname as dirname11, join as join29, resolve as resolve9 } from "pathe";
18446
- var ENV_LINE = /^\s*(?:export\s+)?(?<key>[A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?<value>.*?)\s*$/u;
18447
- var DOUBLE_QUOTED2 = /^"(?<body>[\s\S]*)"$/u;
18448
- var SINGLE_QUOTED = /^'(?<body>[\s\S]*)'$/u;
18449
- var ESCAPE = /\\(?<char>[\\nt"])/gu;
18450
- var UNESCAPED = {
18451
- '"': '"',
18452
- "\\": "\\",
18453
- n: `
18454
- `,
18455
- t: "\t"
18456
- };
18457
- var unquote = (raw) => {
18458
- const double = raw.match(DOUBLE_QUOTED2)?.groups?.body;
18459
- if (double !== undefined) {
18460
- return double.replaceAll(ESCAPE, (match, char) => UNESCAPED[char] ?? match);
18461
- }
18462
- const single = raw.match(SINGLE_QUOTED)?.groups?.body;
18463
- if (single !== undefined) {
18464
- return single;
18465
- }
18466
- const hash = raw.indexOf("#");
18467
- return (hash === -1 ? raw : raw.slice(0, hash)).trim();
18468
- };
18469
- var parseEnv = (content) => {
18470
- const env = {};
18471
- for (const line of content.split(/\r?\n/u)) {
18472
- if (line.trim() === "" || line.trimStart().startsWith("#")) {
18473
- continue;
18474
- }
18475
- const groups = line.match(ENV_LINE)?.groups;
18476
- if (groups?.key !== undefined && groups.value !== undefined) {
18477
- env[groups.key] = unquote(groups.value);
18478
- }
18479
- }
18480
- return env;
18481
- };
18244
+ import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
18245
+ import { parse as parse2 } from "dotenv";
18246
+ import { dirname as dirname12, join as join29, resolve as resolve9 } from "pathe";
18482
18247
  var applyEnv = (parsed) => {
18483
18248
  for (const [key, value] of Object.entries(parsed)) {
18484
18249
  if (!(key in process.env)) {
@@ -18489,7 +18254,7 @@ var applyEnv = (parsed) => {
18489
18254
  var loadFile = (path) => {
18490
18255
  try {
18491
18256
  if (existsSync17(path)) {
18492
- applyEnv(parseEnv(readFileSync10(path, "utf-8")));
18257
+ applyEnv(parse2(readFileSync9(path, "utf-8")));
18493
18258
  }
18494
18259
  } catch {}
18495
18260
  };
@@ -18499,7 +18264,7 @@ var loadEnvFiles = (startDir) => {
18499
18264
  while (!done) {
18500
18265
  loadFile(join29(dir, ".env.local"));
18501
18266
  loadFile(join29(dir, ".env"));
18502
- const parent = dirname11(dir);
18267
+ const parent = dirname12(dir);
18503
18268
  done = existsSync17(join29(dir, ".git")) || parent === dir;
18504
18269
  dir = parent;
18505
18270
  }
@@ -18605,12 +18370,12 @@ var emitRedirectFiles = async (config, distDir) => {
18605
18370
  if (redirects.length === 0 || config.deployment.output !== "static") {
18606
18371
  return;
18607
18372
  }
18608
- await writeFile8(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
18373
+ await writeFile7(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
18609
18374
  const platformFiles = [
18610
18375
  { content: buildNetlifyRedirects(redirects), name: "_redirects" },
18611
18376
  { content: buildVercelConfig(redirects), name: "vercel.json" }
18612
18377
  ];
18613
- await Promise.all(platformFiles.map((file) => existsSync18(join30(distDir, file.name)) ? Promise.resolve() : writeFile8(join30(distDir, file.name), file.content, "utf-8")));
18378
+ await Promise.all(platformFiles.map((file) => existsSync18(join30(distDir, file.name)) ? Promise.resolve() : writeFile7(join30(distDir, file.name), file.content, "utf-8")));
18614
18379
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
18615
18380
  };
18616
18381
  var emitHeaderFiles = async (project, distDir) => {
@@ -18621,7 +18386,7 @@ var emitHeaderFiles = async (project, distDir) => {
18621
18386
  const ours = buildNetlifyHeaders(config, buildHomeLinkHeader(config, markdownRoutePaths(project)));
18622
18387
  const target = join30(distDir, "_headers");
18623
18388
  const existing = existsSync18(target) ? await readFile17(target, "utf-8") : "";
18624
- await writeFile8(target, existing ? `${existing.trimEnd()}
18389
+ await writeFile7(target, existing ? `${existing.trimEnd()}
18625
18390
  ${ours}` : ours, "utf-8");
18626
18391
  logger.success("Emitted _headers (UTF-8 Content-Type + homepage Link header)");
18627
18392
  };
@@ -18649,10 +18414,10 @@ var emitAgentSkills = async (project, distDir) => {
18649
18414
  }
18650
18415
  await Promise.all(skills.map(async (skill) => {
18651
18416
  const target = join30(outDir, skill.path);
18652
- await mkdir7(dirname12(target), { recursive: true });
18653
- await writeFile8(target, skill.content);
18417
+ await mkdir8(dirname13(target), { recursive: true });
18418
+ await writeFile7(target, skill.content);
18654
18419
  }));
18655
- await writeFile8(join30(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
18420
+ await writeFile7(join30(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
18656
18421
  logger.success(`Published ${skills.length} agent skill(s) (.well-known/agent-skills/index.json)`);
18657
18422
  };
18658
18423
  var emitWellKnownFiles = async (config, distDir) => {
@@ -18673,8 +18438,8 @@ var emitWellKnownFiles = async (config, distDir) => {
18673
18438
  if (!file.content || existsSync18(target)) {
18674
18439
  continue;
18675
18440
  }
18676
- await mkdir7(join30(distDir, ".well-known"), { recursive: true });
18677
- await writeFile8(target, file.content, "utf-8");
18441
+ await mkdir8(join30(distDir, ".well-known"), { recursive: true });
18442
+ await writeFile7(target, file.content, "utf-8");
18678
18443
  logger.success(`Generated ${file.path.slice(1)} (${file.label})`);
18679
18444
  }
18680
18445
  };
@@ -18695,7 +18460,7 @@ var emitVercelNegotiation = async (project, routePaths, root) => {
18695
18460
  logger.warn("Could not wire Accept: text/markdown negotiation into .vercel/output/config.json — raw Markdown stays available at the .md URLs.");
18696
18461
  return;
18697
18462
  }
18698
- await writeFile8(configPath, injected, "utf-8");
18463
+ await writeFile7(configPath, injected, "utf-8");
18699
18464
  logger.success("Wired Accept: text/markdown negotiation into the Vercel routing config");
18700
18465
  };
18701
18466
  var warnCloudflareNegotiationSkipped = () => logger.warn("Could not wire Accept: text/markdown negotiation into dist/server/wrangler.json — raw Markdown stays available at the .md URLs.");
@@ -18721,8 +18486,8 @@ var emitCloudflareNegotiation = async (project, routePaths) => {
18721
18486
  warnCloudflareNegotiationSkipped();
18722
18487
  return;
18723
18488
  }
18724
- await writeFile8(join30(serverDir, NEGOTIATION_WORKER_FILE), injected.worker, "utf-8");
18725
- await writeFile8(wranglerPath, injected.wrangler, "utf-8");
18489
+ await writeFile7(join30(serverDir, NEGOTIATION_WORKER_FILE), injected.worker, "utf-8");
18490
+ await writeFile7(wranglerPath, injected.wrangler, "utf-8");
18726
18491
  logger.success("Wired Accept: text/markdown negotiation into the Cloudflare Worker");
18727
18492
  };
18728
18493
  var formatBytes2 = (bytes) => {
@@ -18819,10 +18584,10 @@ var publishLlmsFiles = async (project, distDir) => {
18819
18584
  const { index, full } = await buildLlmsFiles(project);
18820
18585
  const writes = [];
18821
18586
  if (writeIndex) {
18822
- writes.push(writeFile8(indexPath, index, "utf-8"));
18587
+ writes.push(writeFile7(indexPath, index, "utf-8"));
18823
18588
  }
18824
18589
  if (writeFull) {
18825
- writes.push(writeFile8(fullPath, full, "utf-8"));
18590
+ writes.push(writeFile7(fullPath, full, "utf-8"));
18826
18591
  }
18827
18592
  await Promise.all(writes);
18828
18593
  logger.success(`Generated ${[
@@ -18846,17 +18611,17 @@ var publishBuildArtifacts = async (project, distDir, args) => {
18846
18611
  }
18847
18612
  const sitemap = buildSitemap(project);
18848
18613
  if (sitemap && !existsSync18(join30(distDir, "sitemap.xml"))) {
18849
- await writeFile8(join30(distDir, "sitemap.xml"), sitemap, "utf-8");
18614
+ await writeFile7(join30(distDir, "sitemap.xml"), sitemap, "utf-8");
18850
18615
  logger.success("Generated sitemap.xml");
18851
18616
  }
18852
18617
  const robots = buildRobots(project);
18853
18618
  if (robots && !existsSync18(join30(distDir, "robots.txt"))) {
18854
- await writeFile8(join30(distDir, "robots.txt"), robots, "utf-8");
18619
+ await writeFile7(join30(distDir, "robots.txt"), robots, "utf-8");
18855
18620
  logger.success("Generated robots.txt");
18856
18621
  }
18857
18622
  const agentReadability = buildAgentReadability(project);
18858
18623
  if (agentReadability && !existsSync18(join30(distDir, "agent-readability.json"))) {
18859
- await writeFile8(join30(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
18624
+ await writeFile7(join30(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
18860
18625
  `, "utf-8");
18861
18626
  logger.success("Generated agent-readability.json");
18862
18627
  }
@@ -19046,10 +18811,9 @@ var checkCommand = defineCommand4({
19046
18811
  });
19047
18812
 
19048
18813
  // src/cli/commands/dev.ts
19049
- import { watch } from "node:fs";
19050
18814
  import { dev } from "astro";
18815
+ import { watch } from "chokidar";
19051
18816
  import { defineCommand as defineCommand5 } from "citty";
19052
- import { basename as basename4, dirname as dirname13 } from "pathe";
19053
18817
 
19054
18818
  // src/astro/integration.ts
19055
18819
  var overlayServer = null;
@@ -19234,21 +18998,12 @@ var devCommand = defineCommand5({
19234
18998
  project.context.themeFile,
19235
18999
  project.context.componentsFile
19236
19000
  ].filter((target) => target !== null);
19001
+ const projectWatcher = watch([...dirTargets, ...fileTargets], {
19002
+ ignoreInitial: true
19003
+ }).on("all", regenerate);
19237
19004
  const disposers = [
19238
19005
  ...project.sources.map((source) => source.watch?.(regenerate)),
19239
- ...dirTargets.map((target) => {
19240
- const watcher = watch(target, { recursive: true }, regenerate);
19241
- return () => watcher.close();
19242
- }),
19243
- ...fileTargets.map((target) => {
19244
- const name = basename4(target);
19245
- const watcher = watch(dirname13(target), (_event, filename) => {
19246
- if (!filename || filename === name) {
19247
- regenerate();
19248
- }
19249
- });
19250
- return () => watcher.close();
19251
- })
19006
+ () => void projectWatcher.close()
19252
19007
  ].filter((dispose) => dispose !== undefined);
19253
19008
  const shutdown = async () => {
19254
19009
  for (const dispose of disposers) {
@@ -19264,30 +19019,18 @@ var devCommand = defineCommand5({
19264
19019
  });
19265
19020
 
19266
19021
  // src/cli/commands/doctor.ts
19267
- import { readFileSync as readFileSync11 } from "node:fs";
19022
+ import { readFileSync as readFileSync10 } from "node:fs";
19268
19023
  import { defineCommand as defineCommand6 } from "citty";
19269
19024
  import { join as join32 } from "pathe";
19270
- var FALLBACK_MIN_NODE = "22.12.0";
19271
- var LEADING_RANGE = /^[^\d]*/u;
19272
- var minSupportedNode = () => {
19025
+ import { satisfies } from "semver";
19026
+ var FALLBACK_NODE_RANGE = ">=22.12.0";
19027
+ var supportedNodeRange = () => {
19273
19028
  try {
19274
- const pkg = JSON.parse(readFileSync11(join32(packageRoot(), "package.json"), "utf-8"));
19275
- const range = pkg.engines?.node ?? "";
19276
- return range.replace(LEADING_RANGE, "") || FALLBACK_MIN_NODE;
19029
+ const pkg = JSON.parse(readFileSync10(join32(packageRoot(), "package.json"), "utf-8"));
19030
+ return pkg.engines?.node || FALLBACK_NODE_RANGE;
19277
19031
  } catch {
19278
- return FALLBACK_MIN_NODE;
19279
- }
19280
- };
19281
- var versionBelow = (current, minimum) => {
19282
- const a = current.split(".").map((part) => Math.trunc(Number(part)));
19283
- const b = minimum.split(".").map((part) => Math.trunc(Number(part)));
19284
- for (let i = 0;i < 3; i += 1) {
19285
- const delta = (a[i] ?? 0) - (b[i] ?? 0);
19286
- if (delta !== 0) {
19287
- return delta < 0;
19288
- }
19032
+ return FALLBACK_NODE_RANGE;
19289
19033
  }
19290
- return false;
19291
19034
  };
19292
19035
  var doctorCommand = defineCommand6({
19293
19036
  args: {
@@ -19303,11 +19046,11 @@ var doctorCommand = defineCommand6({
19303
19046
  async run({ args }) {
19304
19047
  const root = process.cwd();
19305
19048
  const diagnostics = [];
19306
- const minNode = minSupportedNode();
19307
- if (versionBelow(process.versions.node, minNode)) {
19049
+ const nodeRange = supportedNodeRange();
19050
+ if (!satisfies(process.versions.node, nodeRange)) {
19308
19051
  diagnostics.push({
19309
19052
  code: "BLUME_NODE_VERSION",
19310
- message: `Node ${process.versions.node} is below the supported minimum (${minNode}).`,
19053
+ message: `Node ${process.versions.node} is outside the supported range (${nodeRange}).`,
19311
19054
  severity: "warning"
19312
19055
  });
19313
19056
  }
@@ -19368,7 +19111,7 @@ import { relative as relative17 } from "pathe";
19368
19111
 
19369
19112
  // src/registry/eject.ts
19370
19113
  import { existsSync as existsSync20 } from "node:fs";
19371
- import { cp as cp2, mkdir as mkdir8, readFile as readFile18, rm as rm3, writeFile as writeFile9 } from "node:fs/promises";
19114
+ import { cp as cp2, mkdir as mkdir9, readFile as readFile18, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
19372
19115
  import { join as join33, relative as relative15 } from "pathe";
19373
19116
  var toPosix = (path) => path.split("\\").join("/");
19374
19117
  var LOCAL_BLUME_SOURCE = "../../node_modules/blume/src/**/*.{astro,ts,tsx}";
@@ -19715,8 +19458,8 @@ var eject = async (root) => {
19715
19458
  }
19716
19459
  const written = files.filter((file) => !(file.skipIfExists && existsSync20(file.path)));
19717
19460
  await Promise.all(written.map(async (file) => {
19718
- await mkdir8(join33(file.path, ".."), { recursive: true });
19719
- await writeFile9(file.path, file.content, "utf-8");
19461
+ await mkdir9(join33(file.path, ".."), { recursive: true });
19462
+ await writeFile8(file.path, file.content, "utf-8");
19720
19463
  }));
19721
19464
  const assetsSrc = join33(context.outDir, "public", "blume-assets");
19722
19465
  if (existsSync20(assetsSrc)) {
@@ -19729,7 +19472,7 @@ var eject = async (root) => {
19729
19472
  };
19730
19473
 
19731
19474
  // src/cli/eject-scripts.ts
19732
- import { readFile as readFile19, writeFile as writeFile10 } from "node:fs/promises";
19475
+ import { readFile as readFile19, writeFile as writeFile9 } from "node:fs/promises";
19733
19476
  import { join as join34 } from "pathe";
19734
19477
  var droppedArtifactNotices = (config) => {
19735
19478
  const notices = [];
@@ -19771,14 +19514,14 @@ var updatePackageScripts = async (root) => {
19771
19514
  dev: "astro dev",
19772
19515
  preview: "astro preview"
19773
19516
  };
19774
- await writeFile10(pkgPath, `${JSON.stringify(pkg, null, 2)}
19517
+ await writeFile9(pkgPath, `${JSON.stringify(pkg, null, 2)}
19775
19518
  `, "utf-8");
19776
19519
  };
19777
19520
 
19778
19521
  // src/cli/init/scaffold.ts
19779
19522
  import { existsSync as existsSync21 } from "node:fs";
19780
- import { mkdir as mkdir9, writeFile as writeFile11 } from "node:fs/promises";
19781
- import { basename as basename5, dirname as dirname14, isAbsolute as isAbsolute10, join as join35, relative as relative16 } from "pathe";
19523
+ import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
19524
+ import { basename as basename4, dirname as dirname14, isAbsolute as isAbsolute9, join as join35, relative as relative16 } from "pathe";
19782
19525
 
19783
19526
  // src/core/package-json.ts
19784
19527
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
@@ -19906,34 +19649,35 @@ var detectPackageManager = (userAgent) => {
19906
19649
  const name = userAgent?.split("/")[0];
19907
19650
  return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : "npm";
19908
19651
  };
19909
- var validateContentDir = (root, dir) => isAbsolute10(dir) || relative16(root, join35(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
19652
+ var validateContentDir = (root, dir) => isAbsolute9(dir) || relative16(root, join35(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
19910
19653
  var titleize = (raw) => {
19911
19654
  const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
19912
19655
  return words.length === 0 ? "My Docs" : words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
19913
19656
  };
19914
19657
  var hasRemoteSource = (sources) => sources.some((source) => source !== "filesystem");
19915
- var sourceSnippetFor = (kind) => {
19916
- switch (kind) {
19917
- case "github-releases": {
19918
- return ` // Changelog entries from GitHub Releases. Private repos read
19658
+ var SOURCE_SNIPPETS = {
19659
+ "github-releases": ` // Changelog entries from GitHub Releases. Private repos read
19919
19660
  // GITHUB_TOKEN from the environment.
19920
19661
  {
19921
19662
  type: "github-releases",
19922
19663
  owner: "your-org",
19923
19664
  repo: "your-repo",
19924
19665
  prefix: "changelog",
19925
- },`;
19926
- }
19927
- case "notion": {
19928
- return ` // Pages from a Notion database. Reads NOTION_TOKEN from the environment.
19666
+ },`,
19667
+ "mdx-remote": ` // MDX fetched from a GitHub repo. Private repos read GITHUB_TOKEN
19668
+ // from the environment.
19669
+ {
19670
+ type: "mdx-remote",
19671
+ github: { owner: "your-org", repo: "your-repo", path: "docs" },
19672
+ prefix: "remote",
19673
+ },`,
19674
+ notion: ` // Pages from a Notion database. Reads NOTION_TOKEN from the environment.
19929
19675
  {
19930
19676
  type: "notion",
19931
19677
  database: "your-database-id",
19932
19678
  prefix: "notion",
19933
- },`;
19934
- }
19935
- case "sanity": {
19936
- return ` // Documents from a Sanity dataset. Private datasets read SANITY_TOKEN
19679
+ },`,
19680
+ sanity: ` // Documents from a Sanity dataset. Private datasets read SANITY_TOKEN
19937
19681
  // from the environment.
19938
19682
  {
19939
19683
  type: "sanity",
@@ -19941,21 +19685,7 @@ var sourceSnippetFor = (kind) => {
19941
19685
  dataset: "production",
19942
19686
  query: \`*[_type == "doc"]\`,
19943
19687
  prefix: "sanity",
19944
- },`;
19945
- }
19946
- case "mdx-remote": {
19947
- return ` // MDX fetched from a GitHub repo. Private repos read GITHUB_TOKEN
19948
- // from the environment.
19949
- {
19950
- type: "mdx-remote",
19951
- github: { owner: "your-org", repo: "your-repo", path: "docs" },
19952
- prefix: "remote",
19953
- },`;
19954
- }
19955
- default: {
19956
- return kind;
19957
- }
19958
- }
19688
+ },`
19959
19689
  };
19960
19690
  var contentBlockFor = (answers) => {
19961
19691
  const sources = answers.sources.length === 0 ? ["filesystem"] : answers.sources;
@@ -19965,7 +19695,7 @@ var contentBlockFor = (answers) => {
19965
19695
  root: ${JSON.stringify(answers.contentDir)},
19966
19696
  },`;
19967
19697
  }
19968
- const entries = SOURCE_KINDS.filter((kind) => sources.includes(kind)).map((kind) => kind === "filesystem" ? ` { type: "filesystem", root: ${JSON.stringify(answers.contentDir)} },` : sourceSnippetFor(kind));
19698
+ const entries = SOURCE_KINDS.filter((kind) => sources.includes(kind)).map((kind) => kind === "filesystem" ? ` { type: "filesystem", root: ${JSON.stringify(answers.contentDir)} },` : SOURCE_SNIPPETS[kind]);
19969
19699
  return `
19970
19700
  content: {
19971
19701
  sources: [
@@ -19988,7 +19718,7 @@ var extraDepsFor = (sources) => ({
19988
19718
  var buildPlan = (root, answers) => {
19989
19719
  const files = [
19990
19720
  {
19991
- content: blumePackageJson(toPackageName(basename5(root)), extraDepsFor(answers.sources)),
19721
+ content: blumePackageJson(toPackageName(basename4(root)), extraDepsFor(answers.sources)),
19992
19722
  path: join35(root, "package.json")
19993
19723
  },
19994
19724
  { content: buildConfig(answers), path: join35(root, "blume.config.ts") }
@@ -20003,14 +19733,14 @@ var writeFileSafe = async (file, log) => {
20003
19733
  log.info(`Skipped existing ${file.path}`);
20004
19734
  return false;
20005
19735
  }
20006
- await mkdir9(dirname14(file.path), { recursive: true });
20007
- await writeFile11(file.path, file.content, "utf-8");
19736
+ await mkdir10(dirname14(file.path), { recursive: true });
19737
+ await writeFile10(file.path, file.content, "utf-8");
20008
19738
  log.success(`Created ${file.path}`);
20009
19739
  return true;
20010
19740
  };
20011
19741
  var applyPlan = async (files, log) => {
20012
19742
  const created = await Promise.all(files.map((file) => writeFileSafe(file, log)));
20013
- const createdPackage = files.some((file, index) => created[index] && basename5(file.path) === "package.json");
19743
+ const createdPackage = files.some((file, index) => created[index] && basename4(file.path) === "package.json");
20014
19744
  return { createdPackage };
20015
19745
  };
20016
19746
  var envVarsFor = (sources) => [
@@ -20164,19 +19894,10 @@ Rules:
20164
19894
  When you are done, print the file and suggest running \`blume eval\` to try it.`;
20165
19895
 
20166
19896
  // src/eval/report.ts
20167
- import { mkdtemp as mkdtemp2, writeFile as writeFile12 } from "node:fs/promises";
19897
+ import { mkdtemp as mkdtemp2, writeFile as writeFile11 } from "node:fs/promises";
20168
19898
  import { tmpdir as tmpdir2 } from "node:os";
19899
+ import { colors as colors4 } from "consola/utils";
20169
19900
  import { join as join36, relative as relative18 } from "pathe";
20170
- var ESC4 = String.fromCodePoint(27);
20171
- var COLORS3 = {
20172
- bold: `${ESC4}[1m`,
20173
- cyan: `${ESC4}[36m`,
20174
- dim: `${ESC4}[2m`,
20175
- green: `${ESC4}[32m`,
20176
- red: `${ESC4}[31m`,
20177
- reset: `${ESC4}[0m`,
20178
- yellow: `${ESC4}[33m`
20179
- };
20180
19901
  var GLYPH2 = {
20181
19902
  error: "!",
20182
19903
  fail: "✖",
@@ -20184,10 +19905,10 @@ var GLYPH2 = {
20184
19905
  skip: "⊘"
20185
19906
  };
20186
19907
  var STATUS_COLOR = {
20187
- error: COLORS3.yellow,
20188
- fail: COLORS3.red,
20189
- pass: COLORS3.green,
20190
- skip: COLORS3.dim
19908
+ error: colors4.yellow,
19909
+ fail: colors4.red,
19910
+ pass: colors4.green,
19911
+ skip: colors4.dim
20191
19912
  };
20192
19913
  var ID_PAD = 28;
20193
19914
  var seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
@@ -20202,17 +19923,18 @@ var duration = (ms) => {
20202
19923
  };
20203
19924
  var questionLine = (result) => {
20204
19925
  const color = STATUS_COLOR[result.status];
20205
- const glyph = `${color}${GLYPH2[result.status]}${COLORS3.reset}`;
19926
+ const glyph = color(GLYPH2[result.status]);
20206
19927
  const id2 = result.id.padEnd(ID_PAD);
20207
19928
  if (result.status === "skip") {
20208
- return ` ${glyph} ${id2} ${COLORS3.dim}skipped${COLORS3.reset}`;
19929
+ return ` ${glyph} ${id2} ${colors4.dim("skipped")}`;
20209
19930
  }
20210
19931
  const score = result.score === undefined ? "" : result.score.toFixed(2);
19932
+ const cost = money(result.costUsd);
20211
19933
  const cells = [
20212
- `${color}${result.status}${COLORS3.reset}`,
19934
+ color(result.status),
20213
19935
  score,
20214
- `${COLORS3.dim}${seconds(result.durationMs)}${COLORS3.reset}`,
20215
- `${COLORS3.dim}${money(result.costUsd)}${COLORS3.reset}`
19936
+ colors4.dim(seconds(result.durationMs)),
19937
+ cost === "" ? "" : colors4.dim(cost)
20216
19938
  ].filter((cell) => cell !== "").join(" ");
20217
19939
  return ` ${glyph} ${id2} ${cells}`;
20218
19940
  };
@@ -20220,15 +19942,15 @@ var questionDetails = (result, verbose) => {
20220
19942
  const lines = [];
20221
19943
  if (result.status === "fail") {
20222
19944
  for (const fact of result.missing) {
20223
- lines.push(` ${COLORS3.dim}missing: ${fact}${COLORS3.reset}`);
19945
+ lines.push(` ${colors4.dim(`missing: ${fact}`)}`);
20224
19946
  }
20225
19947
  }
20226
19948
  if (result.status === "error" && result.detail) {
20227
- lines.push(` ${COLORS3.dim}${result.detail}${COLORS3.reset}`);
19949
+ lines.push(` ${colors4.dim(result.detail)}`);
20228
19950
  }
20229
19951
  if (verbose && result.answer && result.status !== "pass") {
20230
19952
  lines.push(...result.answer.split(`
20231
- `).map((line) => ` ${COLORS3.dim}> ${line}${COLORS3.reset}`));
19953
+ `).map((line) => ` ${colors4.dim(`> ${line}`)}`));
20232
19954
  }
20233
19955
  return lines;
20234
19956
  };
@@ -20244,15 +19966,15 @@ var summaryLine2 = (result) => {
20244
19966
  ].filter((part) => part !== "");
20245
19967
  return parts.join(" · ");
20246
19968
  };
20247
- var headerLine = (total, agent) => `${COLORS3.bold}blume eval${COLORS3.reset} ${total} question(s) · ${AGENTS[agent].name}`;
20248
- var startLine = (id2, index, total) => ` ${COLORS3.dim}▸ ${id2} (${index + 1}/${total})${COLORS3.reset}`;
19969
+ var headerLine = (total, agent) => `${colors4.bold("blume eval")} ${total} question(s) · ${AGENTS[agent].name}`;
19970
+ var startLine = (id2, index, total) => ` ${colors4.dim(`▸ ${id2} (${index + 1}/${total})`)}`;
20249
19971
  var fixLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code !== "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
20250
19972
  const site = finding2.file ? `${relative18(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
20251
- return ` ${COLORS3.cyan}fix:${COLORS3.reset} ${site} ${COLORS3.dim}${finding2.message}${COLORS3.reset}`;
19973
+ return ` ${colors4.cyan("fix:")} ${site} ${colors4.dim(finding2.message)}`;
20252
19974
  });
20253
19975
  var warningLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code === "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
20254
19976
  const site = finding2.file ? ` ${relative18(root, finding2.file)}${finding2.line ? `:${finding2.line}` : ""}` : "";
20255
- return ` ${COLORS3.yellow}⚠${COLORS3.reset}${site} ${COLORS3.dim}${finding2.message}${COLORS3.reset}`;
19977
+ return ` ${colors4.yellow("⚠")}${site} ${colors4.dim(finding2.message)}`;
20256
19978
  });
20257
19979
  var evalReportJson = (result, root, threshold) => {
20258
19980
  const diagnostics = result.diagnostics.map((diagnostic) => diagnostic.file ? { ...diagnostic, file: relative18(root, diagnostic.file) } : diagnostic);
@@ -20273,18 +19995,18 @@ var evalReportJson = (result, root, threshold) => {
20273
19995
  var writeEvalReport = async (result, root, threshold) => {
20274
19996
  const dir = await mkdtemp2(join36(tmpdir2(), "blume-eval-"));
20275
19997
  const path = join36(dir, "report.json");
20276
- await writeFile12(path, evalReportJson(result, root, threshold));
19998
+ await writeFile11(path, evalReportJson(result, root, threshold));
20277
19999
  return path;
20278
20000
  };
20279
20001
 
20280
20002
  // src/eval/run.ts
20281
- import { mkdir as mkdir10, mkdtemp as mkdtemp3, writeFile as writeFile14 } from "node:fs/promises";
20003
+ import { mkdir as mkdir11, mkdtemp as mkdtemp3, writeFile as writeFile13 } from "node:fs/promises";
20282
20004
  import { tmpdir as tmpdir3 } from "node:os";
20283
20005
  import { join as join38 } from "pathe";
20284
20006
 
20285
20007
  // src/eval/agents.ts
20286
20008
  import { spawn as spawn2 } from "node:child_process";
20287
- import { readFile as readFile20, writeFile as writeFile13 } from "node:fs/promises";
20009
+ import { readFile as readFile20, writeFile as writeFile12 } from "node:fs/promises";
20288
20010
  import { join as join37 } from "pathe";
20289
20011
  import { z as z3 } from "zod";
20290
20012
  var KILL_GRACE_MS = 5000;
@@ -20357,7 +20079,7 @@ var writeMcpConfig = async (dir, snapshotPath, launcher) => {
20357
20079
  [MCP_SERVER_NAME]: { args: resolved.args, command: resolved.command }
20358
20080
  }
20359
20081
  };
20360
- await writeFile13(configPath, JSON.stringify(config, null, 2));
20082
+ await writeFile12(configPath, JSON.stringify(config, null, 2));
20361
20083
  return {
20362
20084
  configPath,
20363
20085
  serverArgs: resolved.args,
@@ -20632,7 +20354,7 @@ var runQuestion = async (question, index, context) => {
20632
20354
  const started = performance.now();
20633
20355
  const elapsed = () => Math.round(performance.now() - started);
20634
20356
  const workDir = join38(context.dir, `work-${index}`);
20635
- await mkdir10(workDir, { recursive: true });
20357
+ await mkdir11(workDir, { recursive: true });
20636
20358
  const answerPath = join38(workDir, "answer.txt");
20637
20359
  const reader = await context.run(context.bin, agentArgs(context.kind, { lastMessagePath: answerPath, mcp: context.mcp }), {
20638
20360
  cwd: workDir,
@@ -20690,7 +20412,7 @@ var runEval = async (options) => {
20690
20412
  const anchor = { path: options.evalsPath, raw: options.rawEvals };
20691
20413
  const dir = await mkdtemp3(join38(tmpdir3(), "blume-eval-"));
20692
20414
  const snapshotPath = join38(dir, "mcp-data.json");
20693
- await writeFile14(snapshotPath, JSON.stringify(await buildMcpData(options.project)));
20415
+ await writeFile13(snapshotPath, JSON.stringify(await buildMcpData(options.project)));
20694
20416
  const mcp = await writeMcpConfig(dir, snapshotPath);
20695
20417
  const context = {
20696
20418
  bin: AGENTS[kind].bin,
@@ -20969,7 +20691,7 @@ import { defineCommand as defineCommand9 } from "citty";
20969
20691
  import { resolve as resolve12 } from "pathe";
20970
20692
 
20971
20693
  // src/cli/init/questions.ts
20972
- import { basename as basename6, resolve as resolve11 } from "pathe";
20694
+ import { basename as basename5, resolve as resolve11 } from "pathe";
20973
20695
  var cancelled = (value) => typeof value === "symbol";
20974
20696
  var collectAnswers = async (prompter, flags, defaults) => {
20975
20697
  const directory = flags.directory ?? await prompter.text({
@@ -20982,7 +20704,7 @@ var collectAnswers = async (prompter, flags, defaults) => {
20982
20704
  }
20983
20705
  const root = resolve11(defaults.cwd, directory);
20984
20706
  const title = await prompter.text({
20985
- initialValue: titleize(basename6(root)),
20707
+ initialValue: titleize(basename5(root)),
20986
20708
  message: "What's your docs site called?",
20987
20709
  validate: (value) => value?.trim() ? undefined : "Give your docs site a name."
20988
20710
  });
@@ -21606,8 +21328,8 @@ var translateAgentArgs = (kind, lastMessagePath) => kind === "claude" ? claudeAr
21606
21328
 
21607
21329
  // src/translate/ledger.ts
21608
21330
  import { createHash as createHash4 } from "node:crypto";
21609
- import { mkdir as mkdir11, readFile as readFile23, rename as rename2, rm as rm5, writeFile as writeFile15 } from "node:fs/promises";
21610
- import { dirname as dirname15, join as join42 } from "pathe";
21331
+ import { readFile as readFile23 } from "node:fs/promises";
21332
+ import { join as join42 } from "pathe";
21611
21333
  import { z as z5 } from "zod";
21612
21334
  var LEDGER_FILE = "blume.translations.json";
21613
21335
  var ledgerSchema = z5.object({
@@ -21656,15 +21378,7 @@ var writeLedger = async (root, ledger) => {
21656
21378
  if (existing === content) {
21657
21379
  return false;
21658
21380
  }
21659
- await mkdir11(dirname15(path), { recursive: true });
21660
- const tmp = `${path}.${process.pid}.tmp`;
21661
- await writeFile15(tmp, content, "utf-8");
21662
- try {
21663
- await rename2(tmp, path);
21664
- } catch (error) {
21665
- await rm5(tmp, { force: true });
21666
- throw error;
21667
- }
21381
+ await writeTextAtomic(path, content);
21668
21382
  return true;
21669
21383
  };
21670
21384
  var stampLedger = (ledger, sourceRel, locale, hash) => {
@@ -21687,25 +21401,17 @@ var pruneLedger = (ledger, knownSources, knownLocales) => {
21687
21401
  };
21688
21402
 
21689
21403
  // src/translate/report.ts
21690
- var ESC5 = String.fromCodePoint(27);
21691
- var COLORS4 = {
21692
- bold: `${ESC5}[1m`,
21693
- cyan: `${ESC5}[36m`,
21694
- dim: `${ESC5}[2m`,
21695
- green: `${ESC5}[32m`,
21696
- red: `${ESC5}[31m`,
21697
- reset: `${ESC5}[0m`,
21698
- yellow: `${ESC5}[33m`
21699
- };
21404
+ import { colors as colors5 } from "consola/utils";
21405
+ var ESC = String.fromCodePoint(27);
21700
21406
  var GLYPH3 = {
21701
21407
  failed: "✖",
21702
21408
  partial: "!",
21703
21409
  translated: "✔"
21704
21410
  };
21705
21411
  var STATUS_COLOR2 = {
21706
- failed: COLORS4.red,
21707
- partial: COLORS4.yellow,
21708
- translated: COLORS4.green
21412
+ failed: colors5.red,
21413
+ partial: colors5.yellow,
21414
+ translated: colors5.green
21709
21415
  };
21710
21416
  var SPINNER_FRAMES = [
21711
21417
  "⠋",
@@ -21720,7 +21426,7 @@ var SPINNER_FRAMES = [
21720
21426
  "⠏"
21721
21427
  ];
21722
21428
  var SPINNER_INTERVAL_MS = 80;
21723
- var REWRITE = `\r${ESC5}[K`;
21429
+ var REWRITE = `\r${ESC}[K`;
21724
21430
  var seconds2 = (ms) => `${(ms / 1000).toFixed(1)}s`;
21725
21431
  var money2 = (cost) => cost === undefined ? "" : `$${cost.toFixed(2)}`;
21726
21432
  var duration2 = (ms) => {
@@ -21735,20 +21441,20 @@ var itemLabel = (item) => item.kind === "page" ? `${item.sourceRel} → ${item.l
21735
21441
  var spinnerLine = (active, done, total, frame) => {
21736
21442
  const first = active[0];
21737
21443
  const more = active.length > 1 ? ` (+${active.length - 1} more)` : "";
21738
- return ` ${COLORS4.cyan}${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]}${COLORS4.reset} ${itemLabel(first)}${more} ${COLORS4.dim}${done}/${total}${COLORS4.reset}`;
21444
+ return ` ${colors5.cyan(SPINNER_FRAMES[frame % SPINNER_FRAMES.length])} ${itemLabel(first)}${more} ${colors5.dim(`${done}/${total}`)}`;
21739
21445
  };
21740
21446
  var itemEndLine = (result) => {
21741
21447
  const color = STATUS_COLOR2[result.status];
21742
- const glyph = `${color}${GLYPH3[result.status]}${COLORS4.reset}`;
21448
+ const glyph = color(GLYPH3[result.status]);
21743
21449
  const label = itemLabel(result.item);
21744
21450
  if (result.status === "translated") {
21745
21451
  const cells = [seconds2(result.durationMs), money2(result.costUsd)].filter((cell) => cell !== "").join(" ");
21746
- return ` ${glyph} ${label} ${COLORS4.dim}${cells}${COLORS4.reset}`;
21452
+ return ` ${glyph} ${label} ${colors5.dim(cells)}`;
21747
21453
  }
21748
21454
  const word = result.status === "partial" ? "partial" : "failed";
21749
- return ` ${glyph} ${label} ${color}${word}${COLORS4.reset}${result.detail ? `${COLORS4.dim}: ${result.detail}${COLORS4.reset}` : ""}`;
21455
+ return ` ${glyph} ${label} ${color(word)}${result.detail ? colors5.dim(`: ${result.detail}`) : ""}`;
21750
21456
  };
21751
- var translateHeaderLine = (itemCount, localeCount, agent) => `${COLORS4.bold}blume translate${COLORS4.reset} ${itemCount} item(s) · ${localeCount} locale(s) · ${AGENTS[agent].name}`;
21457
+ var translateHeaderLine = (itemCount, localeCount, agent) => `${colors5.bold("blume translate")} ${itemCount} item(s) · ${localeCount} locale(s) · ${AGENTS[agent].name}`;
21752
21458
  var translateSummaryLine = (result, workList) => {
21753
21459
  const { counts } = result;
21754
21460
  const parts = [
@@ -21762,7 +21468,7 @@ var translateSummaryLine = (result, workList) => {
21762
21468
  ].filter((part) => part !== "");
21763
21469
  return parts.join(" · ");
21764
21470
  };
21765
- var diagnosticLines = (diagnostics) => diagnostics.map((diagnostic) => ` ${COLORS4.yellow}⚠${COLORS4.reset} ${COLORS4.dim}${diagnostic.message}${COLORS4.reset}`);
21471
+ var diagnosticLines = (diagnostics) => diagnostics.map((diagnostic) => ` ${colors5.yellow("⚠")} ${colors5.dim(diagnostic.message)}`);
21766
21472
  var driftRows = (workList) => workList.items.flatMap((item) => item.kind === "page" ? [
21767
21473
  {
21768
21474
  locale: item.locale,
@@ -21775,8 +21481,8 @@ var driftRows = (workList) => workList.items.flatMap((item) => item.kind === "pa
21775
21481
  status: entry.status
21776
21482
  })));
21777
21483
  var checkLines = (workList) => [
21778
- ...driftRows(workList).map((row) => ` ${COLORS4.red}✖${COLORS4.reset} ${row.sourceRel} → ${row.locale} ${COLORS4.dim}${row.status}${COLORS4.reset}`),
21779
- ...workList.untracked.map((entry) => ` ${COLORS4.dim}⊘ ${entry.sourceRel} → ${entry.locale} untracked (adopted by the next translate run)${COLORS4.reset}`)
21484
+ ...driftRows(workList).map((row) => ` ${colors5.red("✖")} ${row.sourceRel} → ${row.locale} ${colors5.dim(row.status)}`),
21485
+ ...workList.untracked.map((entry) => ` ${colors5.dim(`⊘ ${entry.sourceRel} → ${entry.locale} untracked (adopted by the next translate run)`)}`)
21780
21486
  ];
21781
21487
  var checkSummaryLine = (workList) => {
21782
21488
  const rows = driftRows(workList);
@@ -21897,20 +21603,13 @@ var createProgressRenderer = (options) => {
21897
21603
 
21898
21604
  // src/translate/run.ts
21899
21605
  import { existsSync as existsSync24 } from "node:fs";
21900
- import {
21901
- mkdir as mkdir12,
21902
- mkdtemp as mkdtemp4,
21903
- readFile as readFile25,
21904
- rename as rename3,
21905
- rm as rm6,
21906
- writeFile as writeFile16
21907
- } from "node:fs/promises";
21606
+ import { mkdtemp as mkdtemp4, readFile as readFile25 } from "node:fs/promises";
21908
21607
  import { tmpdir as tmpdir4 } from "node:os";
21909
- import { dirname as dirname17, join as join44 } from "pathe";
21608
+ import { join as join44 } from "pathe";
21910
21609
 
21911
21610
  // src/translate/meta.ts
21912
21611
  import { readFile as readFile24 } from "node:fs/promises";
21913
- import { dirname as dirname16, join as join43, relative as relative19 } from "pathe";
21612
+ import { dirname as dirname15, join as join43, relative as relative19 } from "pathe";
21914
21613
  import { glob as glob8 } from "tinyglobby";
21915
21614
  var META_FILES2 = ["**/meta.ts", "**/meta.js", "**/meta.mjs"];
21916
21615
  var metaTargetPath = (meta, locale) => join43(meta.contentRoot, locale, meta.dir, "meta.ts");
@@ -21932,7 +21631,7 @@ var discoverTranslatableMeta = async (project) => {
21932
21631
  onlyFiles: true
21933
21632
  });
21934
21633
  for (const file of files.toSorted()) {
21935
- const dir = relative19(contentRoot2, dirname16(file));
21634
+ const dir = relative19(contentRoot2, dirname15(file));
21936
21635
  const first = dir.split("/")[0]?.toLowerCase();
21937
21636
  if (first && localeDirs.has(first)) {
21938
21637
  continue;
@@ -22127,17 +21826,6 @@ var parseMetaTitles = (agentText, expectedKeys) => {
22127
21826
 
22128
21827
  // src/translate/run.ts
22129
21828
  var metaDirKey = (dir) => dir === "" ? "." : dir;
22130
- var writeFileAtomic = async (path, text3) => {
22131
- await mkdir12(dirname17(path), { recursive: true });
22132
- const tmp = `${path}.${process.pid}.tmp`;
22133
- await writeFile16(tmp, text3, "utf-8");
22134
- try {
22135
- await rename3(tmp, path);
22136
- } catch (error) {
22137
- await rm6(tmp, { force: true });
22138
- throw error;
22139
- }
22140
- };
22141
21829
  var invokeAgent = async (context, prompt, index) => {
22142
21830
  const messagePath = join44(context.dir, `message-${index}.txt`);
22143
21831
  const result = await context.run(context.bin, translateAgentArgs(context.kind, messagePath), { cwd: context.dir, prompt, timeoutMs: context.timeoutMs });
@@ -22168,7 +21856,7 @@ var runPageItem = async (item, index, context, ledger) => {
22168
21856
  if (!validated.ok) {
22169
21857
  return done("failed", validated.reason, output.costUsd);
22170
21858
  }
22171
- await writeFileAtomic(item.targetPath, validated.text);
21859
+ await writeTextAtomic(item.targetPath, validated.text);
22172
21860
  stampLedger(ledger, item.sourceRel, item.locale, hashSource(sourceText));
22173
21861
  return done("translated", undefined, output.costUsd);
22174
21862
  };
@@ -22193,7 +21881,7 @@ var runMetaItem = async (item, index, context, ledger) => {
22193
21881
  if (translated === undefined) {
22194
21882
  continue;
22195
21883
  }
22196
- await writeFileAtomic(entry.targetPath, generateMetaModule(entry.meta.data, translated));
21884
+ await writeTextAtomic(entry.targetPath, generateMetaModule(entry.meta.data, translated));
22197
21885
  stampLedger(ledger, entry.meta.sourceRel, item.locale, hashSource(entry.meta.raw));
22198
21886
  }
22199
21887
  if (parsed.missing.length === item.entries.length) {
@@ -22293,13 +21981,9 @@ var runTranslate = async (options) => {
22293
21981
  // src/translate/work-list.ts
22294
21982
  import { existsSync as existsSync25 } from "node:fs";
22295
21983
  import { readFile as readFile26 } from "node:fs/promises";
22296
- import { dirname as dirname18, extname as extname8, join as join45, relative as relative20 } from "pathe";
21984
+ import { dirname as dirname16, extname as extname8, join as join45, relative as relative20 } from "pathe";
22297
21985
  var PAGE_EXTENSIONS = new Set([".md", ".mdx"]);
22298
- var translatablePages = (project) => {
22299
- const { i18n } = project.config;
22300
- if (!i18n) {
22301
- return [];
22302
- }
21986
+ var translatablePages = (project, i18n) => {
22303
21987
  const rootsByName = new Map(project.sources.flatMap((source) => source.staged || !source.contentRoot ? [] : [[source.name, source.contentRoot]]));
22304
21988
  const seen = new Set;
22305
21989
  const universe = [];
@@ -22337,7 +22021,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
22337
22021
  const untracked = [];
22338
22022
  const pageItems = [];
22339
22023
  let upToDate = 0;
22340
- for (const { page: page2, contentRoot: contentRoot2, ext, sourcePath } of translatablePages(project)) {
22024
+ for (const { page: page2, contentRoot: contentRoot2, ext, sourcePath } of translatablePages(project, i18n)) {
22341
22025
  const sourceRel = relative20(root, sourcePath);
22342
22026
  knownSources.add(sourceRel);
22343
22027
  const hash = hashSource(await readFile26(sourcePath, "utf-8"));
@@ -22376,7 +22060,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
22376
22060
  const entries = [];
22377
22061
  for (const source of meta.metas) {
22378
22062
  knownSources.add(source.sourceRel);
22379
- const targetDir = dirname18(metaTargetPath(source, locale));
22063
+ const targetDir = dirname16(metaTargetPath(source, locale));
22380
22064
  const exists = ["meta.ts", "meta.js", "meta.mjs"].some((name) => existsSync25(join45(targetDir, name)));
22381
22065
  const hash = hashSource(source.raw);
22382
22066
  const stamp = ledger.files[source.sourceRel]?.[locale];
@@ -22615,7 +22299,7 @@ import { join as join47 } from "pathe";
22615
22299
 
22616
22300
  // src/core/links.ts
22617
22301
  import { existsSync as existsSync26 } from "node:fs";
22618
- import { basename as basename7, join as join46 } from "pathe";
22302
+ import { basename as basename6, join as join46 } from "pathe";
22619
22303
  var HTTP = /^https?:\/\//iu;
22620
22304
  var PROTOCOL_RELATIVE = /^\/\//u;
22621
22305
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -22630,7 +22314,7 @@ var DOC_EXT = /\.(?:md|mdx)$/iu;
22630
22314
  var FILE_EXT = /\.[a-z0-9]+$/iu;
22631
22315
  var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync26(join46(ctx.publicDir, resolved));
22632
22316
  var NUMERIC_PREFIX3 = /^\d+[-_.]/u;
22633
- var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename7(page2.navPath).replace(NUMERIC_PREFIX3, ""));
22317
+ var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename6(page2.navPath).replace(NUMERIC_PREFIX3, ""));
22634
22318
  var applyRelativePart = (segments, part) => {
22635
22319
  if (part === "" || part === ".") {
22636
22320
  return;
@@ -22909,5 +22593,5 @@ process.on("unhandledRejection", (error) => {
22909
22593
  });
22910
22594
  runMain(main);
22911
22595
 
22912
- //# debugId=661E597E02CED63864756E2164756E21
22596
+ //# debugId=84600BB45DA0E5EB64756E2164756E21
22913
22597
  //# sourceMappingURL=index.js.map