blume 1.4.0 → 1.4.1

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 (46) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cli/index.js +322 -644
  3. package/dist/cli/index.js.map +34 -34
  4. package/package.json +22 -1
  5. package/src/ai/component-markdown.ts +7 -6
  6. package/src/astro/generate.ts +4 -13
  7. package/src/astro/islands.ts +4 -1
  8. package/src/astro/templates.ts +3 -4
  9. package/src/audit/checks/indexability.ts +3 -6
  10. package/src/audit/checks/robots.ts +18 -37
  11. package/src/audit/crawl.ts +49 -49
  12. package/src/audit/image-size.ts +13 -53
  13. package/src/audit/report.ts +22 -33
  14. package/src/audit/types.ts +6 -2
  15. package/src/cli/commands/dev.ts +9 -21
  16. package/src/cli/commands/doctor.ts +9 -22
  17. package/src/cli/env.ts +6 -52
  18. package/src/cli/init/scaffold.ts +15 -28
  19. package/src/cli/internal-error.ts +11 -11
  20. package/src/components/islands/ask-ai.tsx +25 -100
  21. package/src/components/islands/hooks.ts +10 -3
  22. package/src/components/layout/RootLayout.astro +37 -109
  23. package/src/components/layout/Search.astro +3 -5
  24. package/src/components/layout/search/types.ts +4 -16
  25. package/src/components/openapi/helpers.ts +21 -75
  26. package/src/core/component-overrides.ts +0 -7
  27. package/src/core/config.ts +3 -3
  28. package/src/core/diagnostics.ts +10 -20
  29. package/src/core/fs-atomic.ts +22 -0
  30. package/src/core/sources/github-releases.ts +29 -26
  31. package/src/core/sources/mdx-remote.ts +10 -57
  32. package/src/core/sources/notion.ts +17 -23
  33. package/src/core/tsconfig-aliases.ts +39 -172
  34. package/src/deploy/rss.ts +4 -1
  35. package/src/deploy/sitemap.ts +3 -1
  36. package/src/eval/report.ts +20 -28
  37. package/src/markdown/directives.ts +6 -18
  38. package/src/markdown/index.ts +1 -6
  39. package/src/markdown/package-commands.ts +0 -4
  40. package/src/openapi/parse.ts +11 -9
  41. package/src/search/popular-icon.ts +3 -3
  42. package/src/translate/ledger.ts +5 -11
  43. package/src/translate/report.ts +22 -28
  44. package/src/translate/run.ts +5 -24
  45. package/src/translate/work-list.ts +0 -0
  46. 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) {
@@ -5464,35 +5403,39 @@ var routeIndex = (manifest, basePath) => {
5464
5403
  }
5465
5404
  return index;
5466
5405
  };
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);
5406
+ var sitemapParser = new XMLParser({
5407
+ htmlEntities: true,
5408
+ ignoreAttributes: true,
5409
+ parseTagValue: false,
5410
+ removeNSPrefix: true
5411
+ });
5478
5412
  var parseSitemap = (file, xml, bytes) => {
5479
5413
  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";
5414
+ let parsed;
5415
+ try {
5416
+ parsed = sitemapParser.parse(xml);
5417
+ } catch {
5418
+ doc.error = "no <urlset> element";
5482
5419
  return doc;
5483
5420
  }
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
- }
5421
+ if (!Object.hasOwn(parsed, "urlset")) {
5422
+ doc.error = Object.hasOwn(parsed, "sitemapindex") ? "sitemap is an index, not a urlset" : "no <urlset> element";
5423
+ return doc;
5489
5424
  }
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);
5425
+ const urlset = parsed.urlset;
5426
+ const entries = typeof urlset === "object" && urlset !== null ? [urlset.url].flat() : [];
5427
+ for (const entry of entries) {
5428
+ if (typeof entry !== "object" || entry === null) {
5429
+ continue;
5430
+ }
5431
+ const { loc, lastmod } = entry;
5432
+ const locText = typeof loc === "string" ? loc.trim() : "";
5433
+ if (!locText) {
5434
+ continue;
5435
+ }
5436
+ doc.urls.push(locText);
5437
+ if (typeof lastmod === "string" && lastmod.trim() !== "") {
5438
+ doc.lastmod?.set(locText, lastmod.trim());
5496
5439
  }
5497
5440
  }
5498
5441
  return doc;
@@ -5512,8 +5455,7 @@ var parseLlms = (file, text) => {
5512
5455
  };
5513
5456
  var ROBOTS_DIRECTIVE = /^(?<field>[a-z-]+)\s*:\s*(?<value>.*)$/iu;
5514
5457
  var parseRobots = (file, text) => {
5515
- const doc = { disallow: [], file, invalid: [], sitemaps: [] };
5516
- let appliesToAll = false;
5458
+ const doc = { file, invalid: [], raw: text, sitemaps: [] };
5517
5459
  for (const [index, raw] of text.split(/\r?\n/u).entries()) {
5518
5460
  const line = raw.trim();
5519
5461
  if (line === "" || line.startsWith("#")) {
@@ -5526,11 +5468,7 @@ var parseRobots = (file, text) => {
5526
5468
  }
5527
5469
  const field = (match.groups?.field ?? "").toLowerCase();
5528
5470
  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) {
5471
+ if (field === "sitemap" && value) {
5534
5472
  doc.sitemaps.push(value);
5535
5473
  }
5536
5474
  }
@@ -10230,12 +10168,13 @@ var loadConfig = async (root, options = {}) => {
10230
10168
  };
10231
10169
  const moreIssues = rest.map((d) => ` - ${d.message}`).join(`
10232
10170
  `);
10233
- throw new BlumeError(rest.length > 0 ? {
10171
+ const detail = rest.length > 0 ? {
10234
10172
  ...primary,
10235
10173
  message: `${primary.message}
10236
10174
  ${rest.length} more config issue(s):
10237
10175
  ${moreIssues}`
10238
- } : primary);
10176
+ } : primary;
10177
+ throw new BlumeError(detail);
10239
10178
  }
10240
10179
  const config = applyDeploymentEnv(parsed.data);
10241
10180
  const site = config.deployment.site ?? options.devServerUrl;
@@ -11894,10 +11833,12 @@ var fetchSpecText = async (spec) => {
11894
11833
  if ("text" in last) {
11895
11834
  return last.text;
11896
11835
  }
11897
- if (!last.retryable || attempt === MAX_ATTEMPTS - 1) {
11898
- throw last.error;
11836
+ if (!last.retryable) {
11837
+ break;
11838
+ }
11839
+ if (attempt < MAX_ATTEMPTS - 1) {
11840
+ await sleep(Math.min(last.retryAfter ?? BASE_BACKOFF_MS * 2 ** attempt, MAX_RETRY_WAIT_MS));
11899
11841
  }
11900
- await sleep(Math.min(last.retryAfter ?? BASE_BACKOFF_MS * 2 ** attempt, MAX_RETRY_WAIT_MS));
11901
11842
  }
11902
11843
  throw last.error;
11903
11844
  };
@@ -12263,6 +12204,10 @@ var filesystemSource = (options) => {
12263
12204
  };
12264
12205
 
12265
12206
  // src/core/sources/github-releases.ts
12207
+ import { fromMarkdown } from "mdast-util-from-markdown";
12208
+ import { gfmFromMarkdown } from "mdast-util-gfm";
12209
+ import { toString as mdastToString } from "mdast-util-to-string";
12210
+ import { gfm } from "micromark-extension-gfm";
12266
12211
  var DEFAULT_BASE_URL = "https://api.github.com";
12267
12212
  var DEFAULT_LIMIT = 100;
12268
12213
  var PER_PAGE = 100;
@@ -12271,21 +12216,16 @@ var NON_SLUG3 = /[^a-z0-9]+/gu;
12271
12216
  var EDGE_DASHES = /^-+|-+$/gu;
12272
12217
  var DESCRIPTION_MAX = 160;
12273
12218
  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;
12219
+ var CHANGESET_HASH = /^(?<mark>\s*(?:[-*+]|\d+[.)])\s+)[0-9a-f]{7,40}:\s+/gmu;
12283
12220
  var WHITESPACE2 = /\s+/gu;
12284
12221
  var TRAILING_FRAGMENT = /[\s,;:.—–-]+$/u;
12222
+ var NON_PROSE = new Set(["code", "heading", "html", "thematicBreak"]);
12285
12223
  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();
12224
+ const tree = fromMarkdown(body.replaceAll(CHANGESET_HASH, "$<mark>"), {
12225
+ extensions: [gfm()],
12226
+ mdastExtensions: [gfmFromMarkdown()]
12227
+ });
12228
+ const text = tree.children.filter((node) => !NON_PROSE.has(node.type)).map((node) => mdastToString(node, { includeHtml: false, includeImageAlt: false })).join(" ").replaceAll(WHITESPACE2, " ").trim();
12289
12229
  if (!text) {
12290
12230
  return;
12291
12231
  }
@@ -12406,42 +12346,7 @@ var githubReleasesSource = (options, ctx) => {
12406
12346
  };
12407
12347
 
12408
12348
  // 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));
12349
+ import picomatch from "picomatch";
12445
12350
  var GITHUB_HOSTS = new Set(["api.github.com", "raw.githubusercontent.com"]);
12446
12351
  var githubHeaders2 = (url) => {
12447
12352
  const token = process.env.GITHUB_TOKEN;
@@ -12466,12 +12371,13 @@ var enumerateGithub = async (github, include, doFetch) => {
12466
12371
  }
12467
12372
  const body = await res.json();
12468
12373
  const prefix = base ? `${base}/` : "";
12374
+ const included = picomatch(include);
12469
12375
  const refs = (body.tree ?? []).flatMap((node) => {
12470
12376
  if (!(node.type === "blob" && node.path.startsWith(prefix))) {
12471
12377
  return [];
12472
12378
  }
12473
12379
  const rel = node.path.slice(prefix.length);
12474
- if (!matchesInclude(rel, include)) {
12380
+ if (!included(rel)) {
12475
12381
  return [];
12476
12382
  }
12477
12383
  return [
@@ -12503,7 +12409,8 @@ var mdxRemoteSource = (options, ctx) => {
12503
12409
  return await enumerateGithub(options.github, options.include, doFetch);
12504
12410
  }
12505
12411
  const base = (options.url ?? "").replace(/\/$/u, "");
12506
- const refs = (options.files ?? []).flatMap((ref) => matchesInclude(ref, options.include) ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }] : []);
12412
+ const included = picomatch(options.include);
12413
+ const refs = (options.files ?? []).flatMap((ref) => included(ref) ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }] : []);
12507
12414
  return { refs, truncated: false };
12508
12415
  };
12509
12416
  const fetchEntry = async (item) => {
@@ -12662,23 +12569,19 @@ var RATE_LIMITED = 429;
12662
12569
  var MAX_RETRIES = 4;
12663
12570
  var BASE_DELAY_MS = 500;
12664
12571
  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);
12572
+ var withNotionRetry = async (call, attempt = 0) => {
12573
+ try {
12574
+ return await call();
12575
+ } catch (error) {
12576
+ const { status } = error;
12577
+ if (status !== RATE_LIMITED || attempt === MAX_RETRIES) {
12578
+ throw error;
12679
12579
  }
12580
+ const retryAfter = Number(error.headers?.["retry-after"]);
12581
+ const wait = retryAfter > 0 ? retryAfter * SECOND_MS2 : BASE_DELAY_MS * 2 ** attempt;
12582
+ await sleep2(wait);
12583
+ return withNotionRetry(call, attempt + 1);
12680
12584
  }
12681
- throw lastError instanceof Error ? lastError : new Error("Notion request failed after retries.");
12682
12585
  };
12683
12586
  var collectAll = async (page, cursor, acc = []) => {
12684
12587
  const res = await page(cursor);
@@ -13401,11 +13304,7 @@ var scanProject = async (root, options = {}) => {
13401
13304
  };
13402
13305
 
13403
13306
  // 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`;
13307
+ import { colors as colors3 } from "consola/utils";
13409
13308
  var ISSUES_URL = "https://github.com/haydenbleasel/blume/issues";
13410
13309
  var BLUME_FRAME = /(?<abs>(?:\/[^\s()]*\/|[A-Za-z]:\\[^\s()]*\\)\.blume[/\\][^\s()]*)/gu;
13411
13310
  var BLUME_MARKER = /[/\\]\.blume[/\\]/u;
@@ -13416,16 +13315,21 @@ var remapBlumeStack = (stack) => stack.replaceAll(BLUME_FRAME, (match) => {
13416
13315
  var reportInternalError = (error) => {
13417
13316
  const err = error instanceof Error ? error : new Error(String(error));
13418
13317
  const lines = [
13419
- `${RED}${BOLD}BLUME_INTERNAL${RESET} An unexpected error occurred.`,
13318
+ `${colors3.red(colors3.bold("BLUME_INTERNAL"))} An unexpected error occurred.`,
13420
13319
  ` ${err.message}`
13421
13320
  ];
13422
13321
  const stack = remapBlumeStack(err.stack ?? "").split(`
13423
13322
  `).slice(1, 5).map((line) => line.trim()).filter(Boolean);
13424
13323
  if (stack.length > 0) {
13425
- lines.push("", `${DIM}${stack.join(`
13426
- `)}${RESET}`);
13324
+ lines.push("", colors3.dim(stack.join(`
13325
+ `)));
13427
13326
  }
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}`);
13327
+ lines.push("", "This is likely a bug in Blume. Please report it with the details below:", colors3.dim([
13328
+ ` Blume: ${getBlumeVersion()}`,
13329
+ ` Node: ${process.version}`,
13330
+ ` Platform: ${process.platform} ${process.arch}`
13331
+ ].join(`
13332
+ `)), ` ${ISSUES_URL}`);
13429
13333
  process.stderr.write(`${lines.join(`
13430
13334
  `)}
13431
13335
  `);
@@ -13581,15 +13485,13 @@ var auditCommand = defineCommand2({
13581
13485
 
13582
13486
  // src/cli/commands/build.ts
13583
13487
  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";
13488
+ import { mkdir as mkdir8, readdir as readdir2, readFile as readFile17, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
13585
13489
  import { build } from "astro";
13586
13490
  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;");
13491
+ import { dirname as dirname13, join as join30, resolve as resolve10 } from "pathe";
13591
13492
 
13592
13493
  // src/deploy/rss.ts
13494
+ import { escape as escapeXml } from "html-escaper";
13593
13495
  var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
13594
13496
  var pageDate = (page) => {
13595
13497
  const raw = page.meta.date ?? page.meta.changelog?.date;
@@ -13881,6 +13783,7 @@ var readEntryText = async (ctx, page) => {
13881
13783
  };
13882
13784
 
13883
13785
  // src/ai/component-markdown.ts
13786
+ import { markdownTable } from "markdown-table";
13884
13787
  import { mdxToMdast } from "satteri";
13885
13788
 
13886
13789
  // src/components/content/youtube.ts
@@ -13978,14 +13881,11 @@ var typeTable = ({ children, props }) => {
13978
13881
  const typeCell = info.typeDescriptionLink ? `[${cellCode(info.type)}](${cellText(info.typeDescriptionLink)})` : cellCode(info.type);
13979
13882
  const defaultCell = info.default === undefined ? "-" : cellCode(info.default);
13980
13883
  const description = cellText([info.description, info.typeDescription].filter((part) => typeof part === "string" && part !== "").join(" "));
13981
- return `| ${prop} | ${typeCell} | ${defaultCell} | ${description} |`;
13884
+ return [prop, typeCell, defaultCell, description];
13982
13885
  });
13983
- const table = rows.length > 0 ? [
13984
- "| Prop | Type | Default | Description |",
13985
- "| --- | --- | --- | --- |",
13986
- ...rows
13987
- ].join(`
13988
- `) : "";
13886
+ const table = rows.length > 0 ? markdownTable([["Prop", "Type", "Default", "Description"], ...rows], {
13887
+ alignDelimiters: false
13888
+ }) : "";
13989
13889
  return [table, children].filter(Boolean).join(`
13990
13890
 
13991
13891
  `);
@@ -14944,6 +14844,9 @@ var buildRobots = (project) => {
14944
14844
  `;
14945
14845
  };
14946
14846
 
14847
+ // src/deploy/sitemap.ts
14848
+ import { escape as escapeXml2 } from "html-escaper";
14849
+
14947
14850
  // src/astro/pages.ts
14948
14851
  import { extname as extname6, relative as relative12 } from "pathe";
14949
14852
  import { glob as glob4, globSync } from "tinyglobby";
@@ -15039,7 +14942,7 @@ var buildSitemap = (project) => {
15039
14942
  const seen = new Set;
15040
14943
  const urls = [];
15041
14944
  const pushUrl = (route, lastModified) => {
15042
- const loc = escapeXml(encodeURI(`${base}${route}`));
14945
+ const loc = escapeXml2(encodeURI(`${base}${route}`));
15043
14946
  if (seen.has(loc)) {
15044
14947
  return;
15045
14948
  }
@@ -15193,25 +15096,25 @@ var pageFacets = (page, config) => {
15193
15096
  };
15194
15097
 
15195
15098
  // 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;
15099
+ var CODE_FENCE2 = /```[\s\S]*?```/gu;
15100
+ var INLINE_CODE2 = /`(?<code>[^`]+)`/gu;
15101
+ var HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
15102
+ var IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
15103
+ var LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
15201
15104
  var HEADING_MARK = /^#{1,6}\s+/gmu;
15202
- var MARKDOWN_PUNCT2 = /[*_~>]+/gu;
15105
+ var MARKDOWN_PUNCT = /[*_~>]+/gu;
15203
15106
  var WHITESPACE3 = /\s+/gu;
15204
15107
  var toPlainText = (markdown) => {
15205
- const withoutBlocks = markdown.replaceAll(CODE_FENCE3, " ").replaceAll(IMAGE2, " ").replaceAll(LINK2, "$<text>");
15108
+ const withoutBlocks = markdown.replaceAll(CODE_FENCE2, " ").replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>");
15206
15109
  const pieces = [];
15207
15110
  let cursor = 0;
15208
- for (const match of withoutBlocks.matchAll(INLINE_CODE3)) {
15111
+ for (const match of withoutBlocks.matchAll(INLINE_CODE2)) {
15209
15112
  const start = match.index ?? 0;
15210
- pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX2, " "), match.groups?.code ?? "");
15113
+ pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "), match.groups?.code ?? "");
15211
15114
  cursor = start + match[0].length;
15212
15115
  }
15213
- pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX2, " "));
15214
- return pieces.join("").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT2, " ").replaceAll(WHITESPACE3, " ").trim();
15116
+ pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
15117
+ return pieces.join("").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT, " ").replaceAll(WHITESPACE3, " ").trim();
15215
15118
  };
15216
15119
  var buildCrumbIndex = (sidebar) => {
15217
15120
  const index = new Map;
@@ -15532,21 +15435,19 @@ var refuseIfDevRunning = (root, action, options = {}) => {
15532
15435
 
15533
15436
  // src/astro/generate.ts
15534
15437
  import { createHash as createHash3 } from "node:crypto";
15535
- import { existsSync as existsSync16, readFileSync as readFileSync9, realpathSync } from "node:fs";
15438
+ import { existsSync as existsSync16, readFileSync as readFileSync8, realpathSync } from "node:fs";
15536
15439
  import {
15537
15440
  lstat,
15538
- mkdir as mkdir6,
15441
+ mkdir as mkdir7,
15539
15442
  readFile as readFile16,
15540
15443
  readlink,
15541
15444
  realpath,
15542
- rename,
15543
15445
  rm as rm2,
15544
- symlink,
15545
- writeFile as writeFile7
15446
+ symlink
15546
15447
  } 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";
15448
+ import { createRequire as createRequire4 } from "node:module";
15449
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
15450
+ import { basename as basename3, dirname as dirname11, join as join28, normalize as normalize3, relative as relative14, resolve as resolve8 } from "pathe";
15550
15451
  import { glob as glob7 } from "tinyglobby";
15551
15452
 
15552
15453
  // src/ai/ask-data.ts
@@ -15941,10 +15842,6 @@ var finalize = (key, group, descriptor, label, identifier, warnings) => {
15941
15842
  if (client === "only" && source && !source.framework) {
15942
15843
  warnings.push(`Override "${key}" uses client: "only" but its framework couldn't be inferred; reference a .tsx/.jsx/.vue/.svelte file.`);
15943
15844
  }
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
15845
  if (!client && source?.framework) {
15949
15846
  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
15847
  }
@@ -16033,128 +15930,22 @@ var analyzeComponentOverrides = (source, filePath) => {
16033
15930
  return result;
16034
15931
  };
16035
15932
 
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
- }
15933
+ // src/core/fs-atomic.ts
15934
+ import { mkdir as mkdir6 } from "node:fs/promises";
15935
+ import { dirname as dirname9 } from "pathe";
15936
+ import writeFileAtomic from "write-file-atomic";
15937
+ var writeTextAtomic = async (path, text2) => {
15938
+ await mkdir6(dirname9(path), { recursive: true });
15939
+ await writeFileAtomic(path, text2, { encoding: "utf-8", fsync: false });
16100
15940
  };
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) => {
15941
+
15942
+ // src/core/tsconfig-aliases.ts
15943
+ import { existsSync as existsSync13 } from "node:fs";
15944
+ import { parseTsconfig } from "get-tsconfig";
15945
+ import { dirname as dirname10, join as join22, resolve as resolve7 } from "pathe";
15946
+ var CONFIG_DIR_TEMPLATE = "${configDir}";
15947
+ var substituteConfigDir = (value, configDir) => value.startsWith(CONFIG_DIR_TEMPLATE) ? join22(configDir, value.slice(CONFIG_DIR_TEMPLATE.length)) : value;
15948
+ var toAlias = (key, value, baseDir, configDir) => {
16158
15949
  const first = Array.isArray(value) ? value[0] : value;
16159
15950
  if (typeof first !== "string") {
16160
15951
  return null;
@@ -16164,20 +15955,31 @@ var toAlias = (key, value, baseDir) => {
16164
15955
  if (find === "" || find === "*") {
16165
15956
  return null;
16166
15957
  }
16167
- return { find, replacement: resolve7(baseDir, target) };
15958
+ return {
15959
+ find,
15960
+ replacement: resolve7(baseDir, substituteConfigDir(target, configDir))
15961
+ };
16168
15962
  };
16169
15963
  var resolveTsconfigAliases = (root) => {
16170
15964
  const entry = ["tsconfig.json", "jsconfig.json"].map((name) => join22(root, name)).find((file) => existsSync13(file));
16171
15965
  if (!entry) {
16172
15966
  return {};
16173
15967
  }
16174
- const loaded = loadPaths(entry, new Set);
16175
- if (!loaded) {
15968
+ let options;
15969
+ try {
15970
+ options = parseTsconfig(entry).compilerOptions;
15971
+ } catch {
15972
+ return {};
15973
+ }
15974
+ const paths = options?.paths;
15975
+ if (!paths) {
16176
15976
  return {};
16177
15977
  }
15978
+ const configDir = dirname10(entry);
15979
+ const baseDir = resolve7(configDir, substituteConfigDir(options?.baseUrl ?? ".", configDir));
16178
15980
  const aliases = {};
16179
- for (const [key, value] of Object.entries(loaded.paths)) {
16180
- const alias = toAlias(key, value, loaded.baseDir);
15981
+ for (const [key, value] of Object.entries(paths)) {
15982
+ const alias = toAlias(key, value, baseDir, configDir);
16181
15983
  if (alias) {
16182
15984
  aliases[alias.find] = alias.replacement;
16183
15985
  }
@@ -16187,9 +15989,9 @@ var resolveTsconfigAliases = (root) => {
16187
15989
 
16188
15990
  // src/og/derive.ts
16189
15991
  import { existsSync as existsSync14 } from "node:fs";
16190
- import { isAbsolute as isAbsolute8, join as join23 } from "pathe";
15992
+ import { isAbsolute as isAbsolute7, join as join23 } from "pathe";
16191
15993
  var CARD_WEIGHTS = [400, 600];
16192
- var absoluteSrc = (root, src) => isAbsolute8(src) ? src : join23(root, src);
15994
+ var absoluteSrc = (root, src) => isAbsolute7(src) ? src : join23(root, src);
16193
15995
  var googleWeights = (weights) => {
16194
15996
  const numbers = weights.filter((weight) => typeof weight === "number");
16195
15997
  const used = numbers.filter((weight) => CARD_WEIGHTS.includes(weight));
@@ -16288,7 +16090,7 @@ var missingFontFiles = (options, root) => {
16288
16090
  };
16289
16091
 
16290
16092
  // src/og/logo.ts
16291
- import { existsSync as existsSync15, readFileSync as readFileSync7 } from "node:fs";
16093
+ import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
16292
16094
  import { join as join24 } from "pathe";
16293
16095
  var resolveOgLogo = (project, source) => {
16294
16096
  if (!source?.toLowerCase().endsWith(".svg")) {
@@ -16299,12 +16101,12 @@ var resolveOgLogo = (project, source) => {
16299
16101
  join24(project.context.root, "public", relative13),
16300
16102
  join24(project.context.root, relative13)
16301
16103
  ].find((path) => existsSync15(path));
16302
- return file ? readFileSync7(file, "utf-8") : undefined;
16104
+ return file ? readFileSync6(file, "utf-8") : undefined;
16303
16105
  };
16304
16106
 
16305
16107
  // src/openapi/scalar.ts
16306
16108
  import { readFile as readFile13 } from "node:fs/promises";
16307
- import { isAbsolute as isAbsolute9, join as join25 } from "pathe";
16109
+ import { isAbsolute as isAbsolute8, join as join25 } from "pathe";
16308
16110
 
16309
16111
  // src/theme/palette.ts
16310
16112
  var FALLBACK_ACCENT = "oklch(0.62 0.16 250)";
@@ -16410,7 +16212,7 @@ var specConfiguration = async (spec, root) => {
16410
16212
  if (URL_SPEC2.test(spec)) {
16411
16213
  return { config: { url: spec } };
16412
16214
  }
16413
- const absolute = isAbsolute9(spec) ? spec : join25(root, spec);
16215
+ const absolute = isAbsolute8(spec) ? spec : join25(root, spec);
16414
16216
  try {
16415
16217
  return { config: { content: await readFile13(absolute, "utf-8") } };
16416
16218
  } catch {
@@ -17242,9 +17044,9 @@ ${options.userCss}
17242
17044
  `;
17243
17045
 
17244
17046
  // 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);
17047
+ import { readFileSync as readFileSync7 } from "node:fs";
17048
+ import { createRequire as createRequire3 } from "node:module";
17049
+ var require2 = createRequire3(import.meta.url);
17248
17050
  var OVERRIDES = `
17249
17051
  /* Twoslash: theme the rich renderer with Blume tokens. */
17250
17052
  :root {
@@ -17319,7 +17121,7 @@ var OVERRIDES = `
17319
17121
  `;
17320
17122
  var twoslashCss = () => {
17321
17123
  const file = require2.resolve("@shikijs/twoslash/style-rich.css");
17322
- return `${readFileSync8(file, "utf-8")}
17124
+ return `${readFileSync7(file, "utf-8")}
17323
17125
  ${OVERRIDES}`;
17324
17126
  };
17325
17127
 
@@ -17460,7 +17262,7 @@ var readClientMode = (source, file, warnings) => {
17460
17262
  };
17461
17263
  var discoverIslands = async (root) => {
17462
17264
  const dir = join26(root, "islands");
17463
- const matches2 = await glob5(["**/*.{jsx,svelte,tsx,vue}"], {
17265
+ const matches2 = await glob5(["**/*"], {
17464
17266
  absolute: true,
17465
17267
  cwd: dir,
17466
17268
  onlyFiles: true
@@ -17569,7 +17371,7 @@ var exampleMarkdownLookup = (examples) => Object.fromEntries(examples.map((examp
17569
17371
  var BLUME_SRC = join28(packageRoot(), "src");
17570
17372
  var canResolveFrom = (fromDir, spec) => {
17571
17373
  try {
17572
- createRequire5(pathToFileURL3(join28(fromDir, "_.js")).href).resolve(spec);
17374
+ createRequire4(pathToFileURL2(join28(fromDir, "_.js")).href).resolve(spec);
17573
17375
  return true;
17574
17376
  } catch {
17575
17377
  return false;
@@ -17580,7 +17382,7 @@ var resolveReactCompiler = (config, needsReact, pkgDir = packageRoot()) => {
17580
17382
  return null;
17581
17383
  }
17582
17384
  try {
17583
- return createRequire5(pathToFileURL3(join28(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
17385
+ return createRequire4(pathToFileURL2(join28(pkgDir, "_.js")).href).resolve("babel-plugin-react-compiler");
17584
17386
  } catch {
17585
17387
  return null;
17586
17388
  }
@@ -17603,7 +17405,7 @@ var resolvedAstroHit = (fromDir) => {
17603
17405
  if (pkg) {
17604
17406
  return { modulesDir, pkg };
17605
17407
  }
17606
- const parent = dirname10(dir);
17408
+ const parent = dirname11(dir);
17607
17409
  if (parent === dir) {
17608
17410
  return null;
17609
17411
  }
@@ -17619,7 +17421,7 @@ var sameRealDir = (a, b) => {
17619
17421
  };
17620
17422
  var depsCandidates = (pkgDir) => [
17621
17423
  join28(pkgDir, "node_modules"),
17622
- dirname10(pkgDir)
17424
+ dirname11(pkgDir)
17623
17425
  ];
17624
17426
  var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync16(join28(dir, ...segments))) ?? null;
17625
17427
  var linkDepsJunction = async (link, depsDir) => {
@@ -17634,13 +17436,13 @@ var linkDepsJunction = async (link, depsDir) => {
17634
17436
  return;
17635
17437
  }
17636
17438
  try {
17637
- if (resolve8(dirname10(link), await readlink(link)) === resolve8(depsDir)) {
17439
+ if (resolve8(dirname11(link), await readlink(link)) === resolve8(depsDir)) {
17638
17440
  return;
17639
17441
  }
17640
17442
  } catch {}
17641
17443
  await rm2(link, { force: true });
17642
17444
  }
17643
- await mkdir6(dirname10(link), { recursive: true });
17445
+ await mkdir7(dirname11(link), { recursive: true });
17644
17446
  await symlink(depsDir, link, "junction");
17645
17447
  };
17646
17448
  var readPkgVersion = (pkgJsonPath) => {
@@ -17648,7 +17450,7 @@ var readPkgVersion = (pkgJsonPath) => {
17648
17450
  return null;
17649
17451
  }
17650
17452
  try {
17651
- return JSON.parse(readFileSync9(pkgJsonPath, "utf-8")).version ?? null;
17453
+ return JSON.parse(readFileSync8(pkgJsonPath, "utf-8")).version ?? null;
17652
17454
  } catch {
17653
17455
  return null;
17654
17456
  }
@@ -17797,15 +17599,7 @@ var writeIfChanged = async (path, content) => {
17797
17599
  if (existing === content) {
17798
17600
  return false;
17799
17601
  }
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
- }
17602
+ await writeTextAtomic(path, content);
17809
17603
  return true;
17810
17604
  };
17811
17605
  var pruneOrphans = async (srcDir, written) => {
@@ -17874,7 +17668,7 @@ var readLogoSvg = (project, source) => {
17874
17668
  join28(project.context.root, "public", rel),
17875
17669
  join28(project.context.root, rel)
17876
17670
  ].find((path) => existsSync16(path));
17877
- return file ? readFileSync9(file, "utf-8") : undefined;
17671
+ return file ? readFileSync8(file, "utf-8") : undefined;
17878
17672
  };
17879
17673
  var resolveLogo = (project) => {
17880
17674
  const { logo } = project.config;
@@ -17917,7 +17711,7 @@ var faviconType = (name) => {
17917
17711
  const ext = name.split(".").pop()?.toLowerCase();
17918
17712
  return ext ? FAVICON_TYPES[ext] : undefined;
17919
17713
  };
17920
- var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync9(file).toString("base64")}`;
17714
+ var inlineDataUri = (file, type) => `data:${type};base64,${readFileSync8(file).toString("base64")}`;
17921
17715
  var defaultFavicon = () => ({
17922
17716
  href: inlineDataUri(join28(BLUME_SRC, "assets", "icon.png"), "image/png"),
17923
17717
  type: "image/png"
@@ -18441,44 +18235,9 @@ var generateRuntime = async (project) => {
18441
18235
  };
18442
18236
 
18443
18237
  // 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
- };
18238
+ import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
18239
+ import { parse as parse2 } from "dotenv";
18240
+ import { dirname as dirname12, join as join29, resolve as resolve9 } from "pathe";
18482
18241
  var applyEnv = (parsed) => {
18483
18242
  for (const [key, value] of Object.entries(parsed)) {
18484
18243
  if (!(key in process.env)) {
@@ -18489,7 +18248,7 @@ var applyEnv = (parsed) => {
18489
18248
  var loadFile = (path) => {
18490
18249
  try {
18491
18250
  if (existsSync17(path)) {
18492
- applyEnv(parseEnv(readFileSync10(path, "utf-8")));
18251
+ applyEnv(parse2(readFileSync9(path, "utf-8")));
18493
18252
  }
18494
18253
  } catch {}
18495
18254
  };
@@ -18499,7 +18258,7 @@ var loadEnvFiles = (startDir) => {
18499
18258
  while (!done) {
18500
18259
  loadFile(join29(dir, ".env.local"));
18501
18260
  loadFile(join29(dir, ".env"));
18502
- const parent = dirname11(dir);
18261
+ const parent = dirname12(dir);
18503
18262
  done = existsSync17(join29(dir, ".git")) || parent === dir;
18504
18263
  dir = parent;
18505
18264
  }
@@ -18605,12 +18364,12 @@ var emitRedirectFiles = async (config, distDir) => {
18605
18364
  if (redirects.length === 0 || config.deployment.output !== "static") {
18606
18365
  return;
18607
18366
  }
18608
- await writeFile8(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
18367
+ await writeFile7(join30(distDir, "blume-redirects.json"), buildRedirectManifest(redirects), "utf-8");
18609
18368
  const platformFiles = [
18610
18369
  { content: buildNetlifyRedirects(redirects), name: "_redirects" },
18611
18370
  { content: buildVercelConfig(redirects), name: "vercel.json" }
18612
18371
  ];
18613
- await Promise.all(platformFiles.map((file) => existsSync18(join30(distDir, file.name)) ? Promise.resolve() : writeFile8(join30(distDir, file.name), file.content, "utf-8")));
18372
+ await Promise.all(platformFiles.map((file) => existsSync18(join30(distDir, file.name)) ? Promise.resolve() : writeFile7(join30(distDir, file.name), file.content, "utf-8")));
18614
18373
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
18615
18374
  };
18616
18375
  var emitHeaderFiles = async (project, distDir) => {
@@ -18621,7 +18380,7 @@ var emitHeaderFiles = async (project, distDir) => {
18621
18380
  const ours = buildNetlifyHeaders(config, buildHomeLinkHeader(config, markdownRoutePaths(project)));
18622
18381
  const target = join30(distDir, "_headers");
18623
18382
  const existing = existsSync18(target) ? await readFile17(target, "utf-8") : "";
18624
- await writeFile8(target, existing ? `${existing.trimEnd()}
18383
+ await writeFile7(target, existing ? `${existing.trimEnd()}
18625
18384
  ${ours}` : ours, "utf-8");
18626
18385
  logger.success("Emitted _headers (UTF-8 Content-Type + homepage Link header)");
18627
18386
  };
@@ -18649,10 +18408,10 @@ var emitAgentSkills = async (project, distDir) => {
18649
18408
  }
18650
18409
  await Promise.all(skills.map(async (skill) => {
18651
18410
  const target = join30(outDir, skill.path);
18652
- await mkdir7(dirname12(target), { recursive: true });
18653
- await writeFile8(target, skill.content);
18411
+ await mkdir8(dirname13(target), { recursive: true });
18412
+ await writeFile7(target, skill.content);
18654
18413
  }));
18655
- await writeFile8(join30(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
18414
+ await writeFile7(join30(outDir, "index.json"), buildSkillsIndex(skills, project.config), "utf-8");
18656
18415
  logger.success(`Published ${skills.length} agent skill(s) (.well-known/agent-skills/index.json)`);
18657
18416
  };
18658
18417
  var emitWellKnownFiles = async (config, distDir) => {
@@ -18673,8 +18432,8 @@ var emitWellKnownFiles = async (config, distDir) => {
18673
18432
  if (!file.content || existsSync18(target)) {
18674
18433
  continue;
18675
18434
  }
18676
- await mkdir7(join30(distDir, ".well-known"), { recursive: true });
18677
- await writeFile8(target, file.content, "utf-8");
18435
+ await mkdir8(join30(distDir, ".well-known"), { recursive: true });
18436
+ await writeFile7(target, file.content, "utf-8");
18678
18437
  logger.success(`Generated ${file.path.slice(1)} (${file.label})`);
18679
18438
  }
18680
18439
  };
@@ -18695,7 +18454,7 @@ var emitVercelNegotiation = async (project, routePaths, root) => {
18695
18454
  logger.warn("Could not wire Accept: text/markdown negotiation into .vercel/output/config.json — raw Markdown stays available at the .md URLs.");
18696
18455
  return;
18697
18456
  }
18698
- await writeFile8(configPath, injected, "utf-8");
18457
+ await writeFile7(configPath, injected, "utf-8");
18699
18458
  logger.success("Wired Accept: text/markdown negotiation into the Vercel routing config");
18700
18459
  };
18701
18460
  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 +18480,8 @@ var emitCloudflareNegotiation = async (project, routePaths) => {
18721
18480
  warnCloudflareNegotiationSkipped();
18722
18481
  return;
18723
18482
  }
18724
- await writeFile8(join30(serverDir, NEGOTIATION_WORKER_FILE), injected.worker, "utf-8");
18725
- await writeFile8(wranglerPath, injected.wrangler, "utf-8");
18483
+ await writeFile7(join30(serverDir, NEGOTIATION_WORKER_FILE), injected.worker, "utf-8");
18484
+ await writeFile7(wranglerPath, injected.wrangler, "utf-8");
18726
18485
  logger.success("Wired Accept: text/markdown negotiation into the Cloudflare Worker");
18727
18486
  };
18728
18487
  var formatBytes2 = (bytes) => {
@@ -18819,10 +18578,10 @@ var publishLlmsFiles = async (project, distDir) => {
18819
18578
  const { index, full } = await buildLlmsFiles(project);
18820
18579
  const writes = [];
18821
18580
  if (writeIndex) {
18822
- writes.push(writeFile8(indexPath, index, "utf-8"));
18581
+ writes.push(writeFile7(indexPath, index, "utf-8"));
18823
18582
  }
18824
18583
  if (writeFull) {
18825
- writes.push(writeFile8(fullPath, full, "utf-8"));
18584
+ writes.push(writeFile7(fullPath, full, "utf-8"));
18826
18585
  }
18827
18586
  await Promise.all(writes);
18828
18587
  logger.success(`Generated ${[
@@ -18846,17 +18605,17 @@ var publishBuildArtifacts = async (project, distDir, args) => {
18846
18605
  }
18847
18606
  const sitemap = buildSitemap(project);
18848
18607
  if (sitemap && !existsSync18(join30(distDir, "sitemap.xml"))) {
18849
- await writeFile8(join30(distDir, "sitemap.xml"), sitemap, "utf-8");
18608
+ await writeFile7(join30(distDir, "sitemap.xml"), sitemap, "utf-8");
18850
18609
  logger.success("Generated sitemap.xml");
18851
18610
  }
18852
18611
  const robots = buildRobots(project);
18853
18612
  if (robots && !existsSync18(join30(distDir, "robots.txt"))) {
18854
- await writeFile8(join30(distDir, "robots.txt"), robots, "utf-8");
18613
+ await writeFile7(join30(distDir, "robots.txt"), robots, "utf-8");
18855
18614
  logger.success("Generated robots.txt");
18856
18615
  }
18857
18616
  const agentReadability = buildAgentReadability(project);
18858
18617
  if (agentReadability && !existsSync18(join30(distDir, "agent-readability.json"))) {
18859
- await writeFile8(join30(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
18618
+ await writeFile7(join30(distDir, "agent-readability.json"), `${JSON.stringify(agentReadability, null, 2)}
18860
18619
  `, "utf-8");
18861
18620
  logger.success("Generated agent-readability.json");
18862
18621
  }
@@ -19046,10 +18805,9 @@ var checkCommand = defineCommand4({
19046
18805
  });
19047
18806
 
19048
18807
  // src/cli/commands/dev.ts
19049
- import { watch } from "node:fs";
19050
18808
  import { dev } from "astro";
18809
+ import { watch } from "chokidar";
19051
18810
  import { defineCommand as defineCommand5 } from "citty";
19052
- import { basename as basename4, dirname as dirname13 } from "pathe";
19053
18811
 
19054
18812
  // src/astro/integration.ts
19055
18813
  var overlayServer = null;
@@ -19234,21 +18992,12 @@ var devCommand = defineCommand5({
19234
18992
  project.context.themeFile,
19235
18993
  project.context.componentsFile
19236
18994
  ].filter((target) => target !== null);
18995
+ const projectWatcher = watch([...dirTargets, ...fileTargets], {
18996
+ ignoreInitial: true
18997
+ }).on("all", regenerate);
19237
18998
  const disposers = [
19238
18999
  ...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
- })
19000
+ () => void projectWatcher.close()
19252
19001
  ].filter((dispose) => dispose !== undefined);
19253
19002
  const shutdown = async () => {
19254
19003
  for (const dispose of disposers) {
@@ -19264,30 +19013,18 @@ var devCommand = defineCommand5({
19264
19013
  });
19265
19014
 
19266
19015
  // src/cli/commands/doctor.ts
19267
- import { readFileSync as readFileSync11 } from "node:fs";
19016
+ import { readFileSync as readFileSync10 } from "node:fs";
19268
19017
  import { defineCommand as defineCommand6 } from "citty";
19269
19018
  import { join as join32 } from "pathe";
19270
- var FALLBACK_MIN_NODE = "22.12.0";
19271
- var LEADING_RANGE = /^[^\d]*/u;
19272
- var minSupportedNode = () => {
19019
+ import { satisfies } from "semver";
19020
+ var FALLBACK_NODE_RANGE = ">=22.12.0";
19021
+ var supportedNodeRange = () => {
19273
19022
  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;
19023
+ const pkg = JSON.parse(readFileSync10(join32(packageRoot(), "package.json"), "utf-8"));
19024
+ return pkg.engines?.node || FALLBACK_NODE_RANGE;
19277
19025
  } 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
- }
19026
+ return FALLBACK_NODE_RANGE;
19289
19027
  }
19290
- return false;
19291
19028
  };
19292
19029
  var doctorCommand = defineCommand6({
19293
19030
  args: {
@@ -19303,11 +19040,11 @@ var doctorCommand = defineCommand6({
19303
19040
  async run({ args }) {
19304
19041
  const root = process.cwd();
19305
19042
  const diagnostics = [];
19306
- const minNode = minSupportedNode();
19307
- if (versionBelow(process.versions.node, minNode)) {
19043
+ const nodeRange = supportedNodeRange();
19044
+ if (!satisfies(process.versions.node, nodeRange)) {
19308
19045
  diagnostics.push({
19309
19046
  code: "BLUME_NODE_VERSION",
19310
- message: `Node ${process.versions.node} is below the supported minimum (${minNode}).`,
19047
+ message: `Node ${process.versions.node} is outside the supported range (${nodeRange}).`,
19311
19048
  severity: "warning"
19312
19049
  });
19313
19050
  }
@@ -19368,7 +19105,7 @@ import { relative as relative17 } from "pathe";
19368
19105
 
19369
19106
  // src/registry/eject.ts
19370
19107
  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";
19108
+ import { cp as cp2, mkdir as mkdir9, readFile as readFile18, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
19372
19109
  import { join as join33, relative as relative15 } from "pathe";
19373
19110
  var toPosix = (path) => path.split("\\").join("/");
19374
19111
  var LOCAL_BLUME_SOURCE = "../../node_modules/blume/src/**/*.{astro,ts,tsx}";
@@ -19715,8 +19452,8 @@ var eject = async (root) => {
19715
19452
  }
19716
19453
  const written = files.filter((file) => !(file.skipIfExists && existsSync20(file.path)));
19717
19454
  await Promise.all(written.map(async (file) => {
19718
- await mkdir8(join33(file.path, ".."), { recursive: true });
19719
- await writeFile9(file.path, file.content, "utf-8");
19455
+ await mkdir9(join33(file.path, ".."), { recursive: true });
19456
+ await writeFile8(file.path, file.content, "utf-8");
19720
19457
  }));
19721
19458
  const assetsSrc = join33(context.outDir, "public", "blume-assets");
19722
19459
  if (existsSync20(assetsSrc)) {
@@ -19729,7 +19466,7 @@ var eject = async (root) => {
19729
19466
  };
19730
19467
 
19731
19468
  // src/cli/eject-scripts.ts
19732
- import { readFile as readFile19, writeFile as writeFile10 } from "node:fs/promises";
19469
+ import { readFile as readFile19, writeFile as writeFile9 } from "node:fs/promises";
19733
19470
  import { join as join34 } from "pathe";
19734
19471
  var droppedArtifactNotices = (config) => {
19735
19472
  const notices = [];
@@ -19771,14 +19508,14 @@ var updatePackageScripts = async (root) => {
19771
19508
  dev: "astro dev",
19772
19509
  preview: "astro preview"
19773
19510
  };
19774
- await writeFile10(pkgPath, `${JSON.stringify(pkg, null, 2)}
19511
+ await writeFile9(pkgPath, `${JSON.stringify(pkg, null, 2)}
19775
19512
  `, "utf-8");
19776
19513
  };
19777
19514
 
19778
19515
  // src/cli/init/scaffold.ts
19779
19516
  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";
19517
+ import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
19518
+ import { basename as basename4, dirname as dirname14, isAbsolute as isAbsolute9, join as join35, relative as relative16 } from "pathe";
19782
19519
 
19783
19520
  // src/core/package-json.ts
19784
19521
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
@@ -19906,34 +19643,35 @@ var detectPackageManager = (userAgent) => {
19906
19643
  const name = userAgent?.split("/")[0];
19907
19644
  return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : "npm";
19908
19645
  };
19909
- var validateContentDir = (root, dir) => isAbsolute10(dir) || relative16(root, join35(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
19646
+ var validateContentDir = (root, dir) => isAbsolute9(dir) || relative16(root, join35(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
19910
19647
  var titleize = (raw) => {
19911
19648
  const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
19912
19649
  return words.length === 0 ? "My Docs" : words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
19913
19650
  };
19914
19651
  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
19652
+ var SOURCE_SNIPPETS = {
19653
+ "github-releases": ` // Changelog entries from GitHub Releases. Private repos read
19919
19654
  // GITHUB_TOKEN from the environment.
19920
19655
  {
19921
19656
  type: "github-releases",
19922
19657
  owner: "your-org",
19923
19658
  repo: "your-repo",
19924
19659
  prefix: "changelog",
19925
- },`;
19926
- }
19927
- case "notion": {
19928
- return ` // Pages from a Notion database. Reads NOTION_TOKEN from the environment.
19660
+ },`,
19661
+ "mdx-remote": ` // MDX fetched from a GitHub repo. Private repos read GITHUB_TOKEN
19662
+ // from the environment.
19663
+ {
19664
+ type: "mdx-remote",
19665
+ github: { owner: "your-org", repo: "your-repo", path: "docs" },
19666
+ prefix: "remote",
19667
+ },`,
19668
+ notion: ` // Pages from a Notion database. Reads NOTION_TOKEN from the environment.
19929
19669
  {
19930
19670
  type: "notion",
19931
19671
  database: "your-database-id",
19932
19672
  prefix: "notion",
19933
- },`;
19934
- }
19935
- case "sanity": {
19936
- return ` // Documents from a Sanity dataset. Private datasets read SANITY_TOKEN
19673
+ },`,
19674
+ sanity: ` // Documents from a Sanity dataset. Private datasets read SANITY_TOKEN
19937
19675
  // from the environment.
19938
19676
  {
19939
19677
  type: "sanity",
@@ -19941,21 +19679,7 @@ var sourceSnippetFor = (kind) => {
19941
19679
  dataset: "production",
19942
19680
  query: \`*[_type == "doc"]\`,
19943
19681
  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
- }
19682
+ },`
19959
19683
  };
19960
19684
  var contentBlockFor = (answers) => {
19961
19685
  const sources = answers.sources.length === 0 ? ["filesystem"] : answers.sources;
@@ -19965,7 +19689,7 @@ var contentBlockFor = (answers) => {
19965
19689
  root: ${JSON.stringify(answers.contentDir)},
19966
19690
  },`;
19967
19691
  }
19968
- const entries = SOURCE_KINDS.filter((kind) => sources.includes(kind)).map((kind) => kind === "filesystem" ? ` { type: "filesystem", root: ${JSON.stringify(answers.contentDir)} },` : sourceSnippetFor(kind));
19692
+ const entries = SOURCE_KINDS.filter((kind) => sources.includes(kind)).map((kind) => kind === "filesystem" ? ` { type: "filesystem", root: ${JSON.stringify(answers.contentDir)} },` : SOURCE_SNIPPETS[kind]);
19969
19693
  return `
19970
19694
  content: {
19971
19695
  sources: [
@@ -19988,7 +19712,7 @@ var extraDepsFor = (sources) => ({
19988
19712
  var buildPlan = (root, answers) => {
19989
19713
  const files = [
19990
19714
  {
19991
- content: blumePackageJson(toPackageName(basename5(root)), extraDepsFor(answers.sources)),
19715
+ content: blumePackageJson(toPackageName(basename4(root)), extraDepsFor(answers.sources)),
19992
19716
  path: join35(root, "package.json")
19993
19717
  },
19994
19718
  { content: buildConfig(answers), path: join35(root, "blume.config.ts") }
@@ -20003,14 +19727,14 @@ var writeFileSafe = async (file, log) => {
20003
19727
  log.info(`Skipped existing ${file.path}`);
20004
19728
  return false;
20005
19729
  }
20006
- await mkdir9(dirname14(file.path), { recursive: true });
20007
- await writeFile11(file.path, file.content, "utf-8");
19730
+ await mkdir10(dirname14(file.path), { recursive: true });
19731
+ await writeFile10(file.path, file.content, "utf-8");
20008
19732
  log.success(`Created ${file.path}`);
20009
19733
  return true;
20010
19734
  };
20011
19735
  var applyPlan = async (files, log) => {
20012
19736
  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");
19737
+ const createdPackage = files.some((file, index) => created[index] && basename4(file.path) === "package.json");
20014
19738
  return { createdPackage };
20015
19739
  };
20016
19740
  var envVarsFor = (sources) => [
@@ -20164,19 +19888,10 @@ Rules:
20164
19888
  When you are done, print the file and suggest running \`blume eval\` to try it.`;
20165
19889
 
20166
19890
  // src/eval/report.ts
20167
- import { mkdtemp as mkdtemp2, writeFile as writeFile12 } from "node:fs/promises";
19891
+ import { mkdtemp as mkdtemp2, writeFile as writeFile11 } from "node:fs/promises";
20168
19892
  import { tmpdir as tmpdir2 } from "node:os";
19893
+ import { colors as colors4 } from "consola/utils";
20169
19894
  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
19895
  var GLYPH2 = {
20181
19896
  error: "!",
20182
19897
  fail: "✖",
@@ -20184,10 +19899,10 @@ var GLYPH2 = {
20184
19899
  skip: "⊘"
20185
19900
  };
20186
19901
  var STATUS_COLOR = {
20187
- error: COLORS3.yellow,
20188
- fail: COLORS3.red,
20189
- pass: COLORS3.green,
20190
- skip: COLORS3.dim
19902
+ error: colors4.yellow,
19903
+ fail: colors4.red,
19904
+ pass: colors4.green,
19905
+ skip: colors4.dim
20191
19906
  };
20192
19907
  var ID_PAD = 28;
20193
19908
  var seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
@@ -20202,17 +19917,18 @@ var duration = (ms) => {
20202
19917
  };
20203
19918
  var questionLine = (result) => {
20204
19919
  const color = STATUS_COLOR[result.status];
20205
- const glyph = `${color}${GLYPH2[result.status]}${COLORS3.reset}`;
19920
+ const glyph = color(GLYPH2[result.status]);
20206
19921
  const id2 = result.id.padEnd(ID_PAD);
20207
19922
  if (result.status === "skip") {
20208
- return ` ${glyph} ${id2} ${COLORS3.dim}skipped${COLORS3.reset}`;
19923
+ return ` ${glyph} ${id2} ${colors4.dim("skipped")}`;
20209
19924
  }
20210
19925
  const score = result.score === undefined ? "" : result.score.toFixed(2);
19926
+ const cost = money(result.costUsd);
20211
19927
  const cells = [
20212
- `${color}${result.status}${COLORS3.reset}`,
19928
+ color(result.status),
20213
19929
  score,
20214
- `${COLORS3.dim}${seconds(result.durationMs)}${COLORS3.reset}`,
20215
- `${COLORS3.dim}${money(result.costUsd)}${COLORS3.reset}`
19930
+ colors4.dim(seconds(result.durationMs)),
19931
+ cost === "" ? "" : colors4.dim(cost)
20216
19932
  ].filter((cell) => cell !== "").join(" ");
20217
19933
  return ` ${glyph} ${id2} ${cells}`;
20218
19934
  };
@@ -20220,15 +19936,15 @@ var questionDetails = (result, verbose) => {
20220
19936
  const lines = [];
20221
19937
  if (result.status === "fail") {
20222
19938
  for (const fact of result.missing) {
20223
- lines.push(` ${COLORS3.dim}missing: ${fact}${COLORS3.reset}`);
19939
+ lines.push(` ${colors4.dim(`missing: ${fact}`)}`);
20224
19940
  }
20225
19941
  }
20226
19942
  if (result.status === "error" && result.detail) {
20227
- lines.push(` ${COLORS3.dim}${result.detail}${COLORS3.reset}`);
19943
+ lines.push(` ${colors4.dim(result.detail)}`);
20228
19944
  }
20229
19945
  if (verbose && result.answer && result.status !== "pass") {
20230
19946
  lines.push(...result.answer.split(`
20231
- `).map((line) => ` ${COLORS3.dim}> ${line}${COLORS3.reset}`));
19947
+ `).map((line) => ` ${colors4.dim(`> ${line}`)}`));
20232
19948
  }
20233
19949
  return lines;
20234
19950
  };
@@ -20244,15 +19960,15 @@ var summaryLine2 = (result) => {
20244
19960
  ].filter((part) => part !== "");
20245
19961
  return parts.join(" · ");
20246
19962
  };
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}`;
19963
+ var headerLine = (total, agent) => `${colors4.bold("blume eval")} ${total} question(s) · ${AGENTS[agent].name}`;
19964
+ var startLine = (id2, index, total) => ` ${colors4.dim(`▸ ${id2} (${index + 1}/${total})`)}`;
20249
19965
  var fixLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code !== "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
20250
19966
  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}`;
19967
+ return ` ${colors4.cyan("fix:")} ${site} ${colors4.dim(finding2.message)}`;
20252
19968
  });
20253
19969
  var warningLines = (result, root) => result.diagnostics.filter((diagnostic) => diagnostic.code === "BLUME_EVAL_ROUTE_UNKNOWN").map((finding2) => {
20254
19970
  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}`;
19971
+ return ` ${colors4.yellow("⚠")}${site} ${colors4.dim(finding2.message)}`;
20256
19972
  });
20257
19973
  var evalReportJson = (result, root, threshold) => {
20258
19974
  const diagnostics = result.diagnostics.map((diagnostic) => diagnostic.file ? { ...diagnostic, file: relative18(root, diagnostic.file) } : diagnostic);
@@ -20273,18 +19989,18 @@ var evalReportJson = (result, root, threshold) => {
20273
19989
  var writeEvalReport = async (result, root, threshold) => {
20274
19990
  const dir = await mkdtemp2(join36(tmpdir2(), "blume-eval-"));
20275
19991
  const path = join36(dir, "report.json");
20276
- await writeFile12(path, evalReportJson(result, root, threshold));
19992
+ await writeFile11(path, evalReportJson(result, root, threshold));
20277
19993
  return path;
20278
19994
  };
20279
19995
 
20280
19996
  // src/eval/run.ts
20281
- import { mkdir as mkdir10, mkdtemp as mkdtemp3, writeFile as writeFile14 } from "node:fs/promises";
19997
+ import { mkdir as mkdir11, mkdtemp as mkdtemp3, writeFile as writeFile13 } from "node:fs/promises";
20282
19998
  import { tmpdir as tmpdir3 } from "node:os";
20283
19999
  import { join as join38 } from "pathe";
20284
20000
 
20285
20001
  // src/eval/agents.ts
20286
20002
  import { spawn as spawn2 } from "node:child_process";
20287
- import { readFile as readFile20, writeFile as writeFile13 } from "node:fs/promises";
20003
+ import { readFile as readFile20, writeFile as writeFile12 } from "node:fs/promises";
20288
20004
  import { join as join37 } from "pathe";
20289
20005
  import { z as z3 } from "zod";
20290
20006
  var KILL_GRACE_MS = 5000;
@@ -20357,7 +20073,7 @@ var writeMcpConfig = async (dir, snapshotPath, launcher) => {
20357
20073
  [MCP_SERVER_NAME]: { args: resolved.args, command: resolved.command }
20358
20074
  }
20359
20075
  };
20360
- await writeFile13(configPath, JSON.stringify(config, null, 2));
20076
+ await writeFile12(configPath, JSON.stringify(config, null, 2));
20361
20077
  return {
20362
20078
  configPath,
20363
20079
  serverArgs: resolved.args,
@@ -20632,7 +20348,7 @@ var runQuestion = async (question, index, context) => {
20632
20348
  const started = performance.now();
20633
20349
  const elapsed = () => Math.round(performance.now() - started);
20634
20350
  const workDir = join38(context.dir, `work-${index}`);
20635
- await mkdir10(workDir, { recursive: true });
20351
+ await mkdir11(workDir, { recursive: true });
20636
20352
  const answerPath = join38(workDir, "answer.txt");
20637
20353
  const reader = await context.run(context.bin, agentArgs(context.kind, { lastMessagePath: answerPath, mcp: context.mcp }), {
20638
20354
  cwd: workDir,
@@ -20690,7 +20406,7 @@ var runEval = async (options) => {
20690
20406
  const anchor = { path: options.evalsPath, raw: options.rawEvals };
20691
20407
  const dir = await mkdtemp3(join38(tmpdir3(), "blume-eval-"));
20692
20408
  const snapshotPath = join38(dir, "mcp-data.json");
20693
- await writeFile14(snapshotPath, JSON.stringify(await buildMcpData(options.project)));
20409
+ await writeFile13(snapshotPath, JSON.stringify(await buildMcpData(options.project)));
20694
20410
  const mcp = await writeMcpConfig(dir, snapshotPath);
20695
20411
  const context = {
20696
20412
  bin: AGENTS[kind].bin,
@@ -20969,7 +20685,7 @@ import { defineCommand as defineCommand9 } from "citty";
20969
20685
  import { resolve as resolve12 } from "pathe";
20970
20686
 
20971
20687
  // src/cli/init/questions.ts
20972
- import { basename as basename6, resolve as resolve11 } from "pathe";
20688
+ import { basename as basename5, resolve as resolve11 } from "pathe";
20973
20689
  var cancelled = (value) => typeof value === "symbol";
20974
20690
  var collectAnswers = async (prompter, flags, defaults) => {
20975
20691
  const directory = flags.directory ?? await prompter.text({
@@ -20982,7 +20698,7 @@ var collectAnswers = async (prompter, flags, defaults) => {
20982
20698
  }
20983
20699
  const root = resolve11(defaults.cwd, directory);
20984
20700
  const title = await prompter.text({
20985
- initialValue: titleize(basename6(root)),
20701
+ initialValue: titleize(basename5(root)),
20986
20702
  message: "What's your docs site called?",
20987
20703
  validate: (value) => value?.trim() ? undefined : "Give your docs site a name."
20988
20704
  });
@@ -21606,8 +21322,8 @@ var translateAgentArgs = (kind, lastMessagePath) => kind === "claude" ? claudeAr
21606
21322
 
21607
21323
  // src/translate/ledger.ts
21608
21324
  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";
21325
+ import { readFile as readFile23 } from "node:fs/promises";
21326
+ import { join as join42 } from "pathe";
21611
21327
  import { z as z5 } from "zod";
21612
21328
  var LEDGER_FILE = "blume.translations.json";
21613
21329
  var ledgerSchema = z5.object({
@@ -21656,15 +21372,7 @@ var writeLedger = async (root, ledger) => {
21656
21372
  if (existing === content) {
21657
21373
  return false;
21658
21374
  }
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
- }
21375
+ await writeTextAtomic(path, content);
21668
21376
  return true;
21669
21377
  };
21670
21378
  var stampLedger = (ledger, sourceRel, locale, hash) => {
@@ -21687,25 +21395,17 @@ var pruneLedger = (ledger, knownSources, knownLocales) => {
21687
21395
  };
21688
21396
 
21689
21397
  // 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
- };
21398
+ import { colors as colors5 } from "consola/utils";
21399
+ var ESC = String.fromCodePoint(27);
21700
21400
  var GLYPH3 = {
21701
21401
  failed: "✖",
21702
21402
  partial: "!",
21703
21403
  translated: "✔"
21704
21404
  };
21705
21405
  var STATUS_COLOR2 = {
21706
- failed: COLORS4.red,
21707
- partial: COLORS4.yellow,
21708
- translated: COLORS4.green
21406
+ failed: colors5.red,
21407
+ partial: colors5.yellow,
21408
+ translated: colors5.green
21709
21409
  };
21710
21410
  var SPINNER_FRAMES = [
21711
21411
  "⠋",
@@ -21720,7 +21420,7 @@ var SPINNER_FRAMES = [
21720
21420
  "⠏"
21721
21421
  ];
21722
21422
  var SPINNER_INTERVAL_MS = 80;
21723
- var REWRITE = `\r${ESC5}[K`;
21423
+ var REWRITE = `\r${ESC}[K`;
21724
21424
  var seconds2 = (ms) => `${(ms / 1000).toFixed(1)}s`;
21725
21425
  var money2 = (cost) => cost === undefined ? "" : `$${cost.toFixed(2)}`;
21726
21426
  var duration2 = (ms) => {
@@ -21735,20 +21435,20 @@ var itemLabel = (item) => item.kind === "page" ? `${item.sourceRel} → ${item.l
21735
21435
  var spinnerLine = (active, done, total, frame) => {
21736
21436
  const first = active[0];
21737
21437
  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}`;
21438
+ return ` ${colors5.cyan(SPINNER_FRAMES[frame % SPINNER_FRAMES.length])} ${itemLabel(first)}${more} ${colors5.dim(`${done}/${total}`)}`;
21739
21439
  };
21740
21440
  var itemEndLine = (result) => {
21741
21441
  const color = STATUS_COLOR2[result.status];
21742
- const glyph = `${color}${GLYPH3[result.status]}${COLORS4.reset}`;
21442
+ const glyph = color(GLYPH3[result.status]);
21743
21443
  const label = itemLabel(result.item);
21744
21444
  if (result.status === "translated") {
21745
21445
  const cells = [seconds2(result.durationMs), money2(result.costUsd)].filter((cell) => cell !== "").join(" ");
21746
- return ` ${glyph} ${label} ${COLORS4.dim}${cells}${COLORS4.reset}`;
21446
+ return ` ${glyph} ${label} ${colors5.dim(cells)}`;
21747
21447
  }
21748
21448
  const word = result.status === "partial" ? "partial" : "failed";
21749
- return ` ${glyph} ${label} ${color}${word}${COLORS4.reset}${result.detail ? `${COLORS4.dim}: ${result.detail}${COLORS4.reset}` : ""}`;
21449
+ return ` ${glyph} ${label} ${color(word)}${result.detail ? colors5.dim(`: ${result.detail}`) : ""}`;
21750
21450
  };
21751
- var translateHeaderLine = (itemCount, localeCount, agent) => `${COLORS4.bold}blume translate${COLORS4.reset} ${itemCount} item(s) · ${localeCount} locale(s) · ${AGENTS[agent].name}`;
21451
+ var translateHeaderLine = (itemCount, localeCount, agent) => `${colors5.bold("blume translate")} ${itemCount} item(s) · ${localeCount} locale(s) · ${AGENTS[agent].name}`;
21752
21452
  var translateSummaryLine = (result, workList) => {
21753
21453
  const { counts } = result;
21754
21454
  const parts = [
@@ -21762,7 +21462,7 @@ var translateSummaryLine = (result, workList) => {
21762
21462
  ].filter((part) => part !== "");
21763
21463
  return parts.join(" · ");
21764
21464
  };
21765
- var diagnosticLines = (diagnostics) => diagnostics.map((diagnostic) => ` ${COLORS4.yellow}⚠${COLORS4.reset} ${COLORS4.dim}${diagnostic.message}${COLORS4.reset}`);
21465
+ var diagnosticLines = (diagnostics) => diagnostics.map((diagnostic) => ` ${colors5.yellow("⚠")} ${colors5.dim(diagnostic.message)}`);
21766
21466
  var driftRows = (workList) => workList.items.flatMap((item) => item.kind === "page" ? [
21767
21467
  {
21768
21468
  locale: item.locale,
@@ -21775,8 +21475,8 @@ var driftRows = (workList) => workList.items.flatMap((item) => item.kind === "pa
21775
21475
  status: entry.status
21776
21476
  })));
21777
21477
  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}`)
21478
+ ...driftRows(workList).map((row) => ` ${colors5.red("✖")} ${row.sourceRel} → ${row.locale} ${colors5.dim(row.status)}`),
21479
+ ...workList.untracked.map((entry) => ` ${colors5.dim(`⊘ ${entry.sourceRel} → ${entry.locale} untracked (adopted by the next translate run)`)}`)
21780
21480
  ];
21781
21481
  var checkSummaryLine = (workList) => {
21782
21482
  const rows = driftRows(workList);
@@ -21897,20 +21597,13 @@ var createProgressRenderer = (options) => {
21897
21597
 
21898
21598
  // src/translate/run.ts
21899
21599
  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";
21600
+ import { mkdtemp as mkdtemp4, readFile as readFile25 } from "node:fs/promises";
21908
21601
  import { tmpdir as tmpdir4 } from "node:os";
21909
- import { dirname as dirname17, join as join44 } from "pathe";
21602
+ import { join as join44 } from "pathe";
21910
21603
 
21911
21604
  // src/translate/meta.ts
21912
21605
  import { readFile as readFile24 } from "node:fs/promises";
21913
- import { dirname as dirname16, join as join43, relative as relative19 } from "pathe";
21606
+ import { dirname as dirname15, join as join43, relative as relative19 } from "pathe";
21914
21607
  import { glob as glob8 } from "tinyglobby";
21915
21608
  var META_FILES2 = ["**/meta.ts", "**/meta.js", "**/meta.mjs"];
21916
21609
  var metaTargetPath = (meta, locale) => join43(meta.contentRoot, locale, meta.dir, "meta.ts");
@@ -21932,7 +21625,7 @@ var discoverTranslatableMeta = async (project) => {
21932
21625
  onlyFiles: true
21933
21626
  });
21934
21627
  for (const file of files.toSorted()) {
21935
- const dir = relative19(contentRoot2, dirname16(file));
21628
+ const dir = relative19(contentRoot2, dirname15(file));
21936
21629
  const first = dir.split("/")[0]?.toLowerCase();
21937
21630
  if (first && localeDirs.has(first)) {
21938
21631
  continue;
@@ -22127,17 +21820,6 @@ var parseMetaTitles = (agentText, expectedKeys) => {
22127
21820
 
22128
21821
  // src/translate/run.ts
22129
21822
  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
21823
  var invokeAgent = async (context, prompt, index) => {
22142
21824
  const messagePath = join44(context.dir, `message-${index}.txt`);
22143
21825
  const result = await context.run(context.bin, translateAgentArgs(context.kind, messagePath), { cwd: context.dir, prompt, timeoutMs: context.timeoutMs });
@@ -22168,7 +21850,7 @@ var runPageItem = async (item, index, context, ledger) => {
22168
21850
  if (!validated.ok) {
22169
21851
  return done("failed", validated.reason, output.costUsd);
22170
21852
  }
22171
- await writeFileAtomic(item.targetPath, validated.text);
21853
+ await writeTextAtomic(item.targetPath, validated.text);
22172
21854
  stampLedger(ledger, item.sourceRel, item.locale, hashSource(sourceText));
22173
21855
  return done("translated", undefined, output.costUsd);
22174
21856
  };
@@ -22193,7 +21875,7 @@ var runMetaItem = async (item, index, context, ledger) => {
22193
21875
  if (translated === undefined) {
22194
21876
  continue;
22195
21877
  }
22196
- await writeFileAtomic(entry.targetPath, generateMetaModule(entry.meta.data, translated));
21878
+ await writeTextAtomic(entry.targetPath, generateMetaModule(entry.meta.data, translated));
22197
21879
  stampLedger(ledger, entry.meta.sourceRel, item.locale, hashSource(entry.meta.raw));
22198
21880
  }
22199
21881
  if (parsed.missing.length === item.entries.length) {
@@ -22293,13 +21975,9 @@ var runTranslate = async (options) => {
22293
21975
  // src/translate/work-list.ts
22294
21976
  import { existsSync as existsSync25 } from "node:fs";
22295
21977
  import { readFile as readFile26 } from "node:fs/promises";
22296
- import { dirname as dirname18, extname as extname8, join as join45, relative as relative20 } from "pathe";
21978
+ import { dirname as dirname16, extname as extname8, join as join45, relative as relative20 } from "pathe";
22297
21979
  var PAGE_EXTENSIONS = new Set([".md", ".mdx"]);
22298
- var translatablePages = (project) => {
22299
- const { i18n } = project.config;
22300
- if (!i18n) {
22301
- return [];
22302
- }
21980
+ var translatablePages = (project, i18n) => {
22303
21981
  const rootsByName = new Map(project.sources.flatMap((source) => source.staged || !source.contentRoot ? [] : [[source.name, source.contentRoot]]));
22304
21982
  const seen = new Set;
22305
21983
  const universe = [];
@@ -22337,7 +22015,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
22337
22015
  const untracked = [];
22338
22016
  const pageItems = [];
22339
22017
  let upToDate = 0;
22340
- for (const { page: page2, contentRoot: contentRoot2, ext, sourcePath } of translatablePages(project)) {
22018
+ for (const { page: page2, contentRoot: contentRoot2, ext, sourcePath } of translatablePages(project, i18n)) {
22341
22019
  const sourceRel = relative20(root, sourcePath);
22342
22020
  knownSources.add(sourceRel);
22343
22021
  const hash = hashSource(await readFile26(sourcePath, "utf-8"));
@@ -22376,7 +22054,7 @@ var computeWorkList = async (project, ledger, options = {}) => {
22376
22054
  const entries = [];
22377
22055
  for (const source of meta.metas) {
22378
22056
  knownSources.add(source.sourceRel);
22379
- const targetDir = dirname18(metaTargetPath(source, locale));
22057
+ const targetDir = dirname16(metaTargetPath(source, locale));
22380
22058
  const exists = ["meta.ts", "meta.js", "meta.mjs"].some((name) => existsSync25(join45(targetDir, name)));
22381
22059
  const hash = hashSource(source.raw);
22382
22060
  const stamp = ledger.files[source.sourceRel]?.[locale];
@@ -22615,7 +22293,7 @@ import { join as join47 } from "pathe";
22615
22293
 
22616
22294
  // src/core/links.ts
22617
22295
  import { existsSync as existsSync26 } from "node:fs";
22618
- import { basename as basename7, join as join46 } from "pathe";
22296
+ import { basename as basename6, join as join46 } from "pathe";
22619
22297
  var HTTP = /^https?:\/\//iu;
22620
22298
  var PROTOCOL_RELATIVE = /^\/\//u;
22621
22299
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -22630,7 +22308,7 @@ var DOC_EXT = /\.(?:md|mdx)$/iu;
22630
22308
  var FILE_EXT = /\.[a-z0-9]+$/iu;
22631
22309
  var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync26(join46(ctx.publicDir, resolved));
22632
22310
  var NUMERIC_PREFIX3 = /^\d+[-_.]/u;
22633
- var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename7(page2.navPath).replace(NUMERIC_PREFIX3, ""));
22311
+ var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename6(page2.navPath).replace(NUMERIC_PREFIX3, ""));
22634
22312
  var applyRelativePart = (segments, part) => {
22635
22313
  if (part === "" || part === ".") {
22636
22314
  return;
@@ -22909,5 +22587,5 @@ process.on("unhandledRejection", (error) => {
22909
22587
  });
22910
22588
  runMain(main);
22911
22589
 
22912
- //# debugId=661E597E02CED63864756E2164756E21
22590
+ //# debugId=C86B4C53CC85579764756E2164756E21
22913
22591
  //# sourceMappingURL=index.js.map