blume 1.1.0 → 1.1.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.
package/dist/cli/index.js CHANGED
@@ -308,12 +308,14 @@ var DOCS_PATHS = {
308
308
  BLUME_CONTENT_ROOT_MISSING: DOCS_CONTENT_SOURCES,
309
309
  BLUME_DEAD_LINK: DOCS_REFERENCE_CLI,
310
310
  BLUME_DUPLICATE_ROUTE: DOCS_CONTENT_NAVIGATION,
311
+ BLUME_DUPLICATE_SIDEBAR_ORDER: DOCS_CONTENT_NAVIGATION,
311
312
  BLUME_FRONTMATTER_INVALID: "/docs/reference/frontmatter",
312
313
  BLUME_META_INVALID: "/docs/content/meta",
313
314
  BLUME_META_LOAD_FAILED: "/docs/content/meta",
314
315
  BLUME_MISSING_SECRET: DOCS_DEPLOYMENT,
315
316
  BLUME_NAV_DUPLICATE_LABEL: DOCS_CONTENT_NAVIGATION,
316
317
  BLUME_NAV_HIDDEN_IN_SIDEBAR: DOCS_CONTENT_NAVIGATION,
318
+ BLUME_NAV_INDEX_TITLE_MISMATCH: DOCS_CONTENT_NAVIGATION,
317
319
  BLUME_NAV_MISSING_PAGE: DOCS_CONTENT_NAVIGATION,
318
320
  BLUME_NODE_VERSION: "/docs/quickstart",
319
321
  BLUME_SERVER_FEATURE_REQUIRED: DOCS_DEPLOYMENT,
@@ -2301,7 +2303,7 @@ var llmsChecks = {
2301
2303
  continue;
2302
2304
  }
2303
2305
  listed.add(path);
2304
- if (!context.byUrl.has(path)) {
2306
+ if (!context.byUrl.has(path) && !context.files.has(path)) {
2305
2307
  found.push(finding("BLUME_AUDIT_LLMS_TXT_STALE_ENTRY", { file: llms.file, line: entry.line, url: entry.url }, `llms.txt lists ${path}, which the build does not serve.`));
2306
2308
  }
2307
2309
  }
@@ -3531,9 +3533,7 @@ var astroConfigTemplate = (options) => {
3531
3533
  ];
3532
3534
  const blumeImport = `import { ${blumeImports.join(", ")} } from "blume/astro";
3533
3535
  `;
3534
- const twoslashImport = `import { transformerTwoslash } from "@shikijs/twoslash";
3535
- `;
3536
- const twoslashTransformer = "transformerTwoslash({ explicitTrigger: true }), ";
3536
+ const twoslashTransformer = "blumeTwoslashTransformer(), ";
3537
3537
  const deployBase = normalizeBasePath(deployment.base);
3538
3538
  const integrations = [
3539
3539
  `mdx({ processor: blumeMdxProcessor(${JSON.stringify({
@@ -3557,8 +3557,8 @@ var astroConfigTemplate = (options) => {
3557
3557
  ${defineConfigImport}
3558
3558
  import mdx from "@astrojs/mdx";
3559
3559
  import tailwindcss from "@tailwindcss/vite";
3560
- import { blumeMarkdownProcessor, blumeMdxProcessor, blumeShikiTransformers } from "blume/markdown";
3561
- ${twoslashImport}${reactImport}${vueImport}${svelteImport}${blumeImport}${adapterImport}
3560
+ import { blumeMarkdownProcessor, blumeMdxProcessor, blumeShikiTransformers, blumeTwoslashTransformer } from "blume/markdown";
3561
+ ${reactImport}${vueImport}${svelteImport}${blumeImport}${adapterImport}
3562
3562
  export default defineConfig({
3563
3563
  root: ${JSON.stringify(context.outDir)},
3564
3564
  srcDir: ${JSON.stringify(`${context.outDir}/src`)},
@@ -3986,7 +3986,9 @@ import data from "blume:data";
3986
3986
  export const prerender = true;
3987
3987
 
3988
3988
  // Custom (non-content) pages opted into a generated card, baked in at build.
3989
- const customRoutes = ${JSON.stringify(customRoutes)};
3989
+ // The annotation keeps the empty-array case from being an implicit any[]
3990
+ // (ts(7034)) under a strict tsconfig.
3991
+ const customRoutes: { slug: string; title: string }[] = ${JSON.stringify(customRoutes)};
3990
3992
 
3991
3993
  export function getStaticPaths() {
3992
3994
  const seen = new Set<string>();
@@ -4651,9 +4653,16 @@ const htmlLang = i18n ? i18n.defaultLocale : "en";
4651
4653
  </PageLayout>
4652
4654
  `;
4653
4655
  var islandDirective = (spec) => spec.client === "only" ? `client:only="${spec.framework}"` : `client:${spec.client}`;
4656
+ var wrapperPropsType = (name) => `type Props = typeof ${name} extends (
4657
+ props: infer P extends object,
4658
+ ...rest: never[]
4659
+ ) => unknown
4660
+ ? P
4661
+ : Record<string, unknown>;`;
4654
4662
  var islandWrapperTemplate = (spec) => `---
4655
4663
  // Generated by Blume. Do not edit.
4656
4664
  import Island from ${JSON.stringify(spec.file)};
4665
+ ${wrapperPropsType("Island")}
4657
4666
  ---
4658
4667
  <Island ${islandDirective(spec)} {...Astro.props}><slot /></Island>
4659
4668
  `;
@@ -4684,6 +4693,7 @@ var exampleSlug = (path) => path.replaceAll(/[^a-zA-Z0-9]/gu, (char) => `_${(cha
4684
4693
  var exampleWrapperTemplate = (spec) => `---
4685
4694
  // Generated by Blume. Do not edit.
4686
4695
  import Example from ${JSON.stringify(spec.file)};
4696
+ ${wrapperPropsType("Example")}
4687
4697
  ---
4688
4698
  <Example ${exampleDirective(spec)}{...Astro.props}><slot /></Example>
4689
4699
  `;
@@ -7879,7 +7889,8 @@ var ogConfigSchema = z2.strictObject({
7879
7889
  enabled: z2.boolean().optional(),
7880
7890
  fonts: z2.array(ogFontSchema).optional(),
7881
7891
  logo: z2.string().optional(),
7882
- palette: ogPaletteSchema.optional()
7892
+ palette: ogPaletteSchema.optional(),
7893
+ titles: z2.record(z2.string(), z2.string()).optional()
7883
7894
  });
7884
7895
  var rssConfigSchema = z2.strictObject({
7885
7896
  enabled: z2.boolean().default(true),
@@ -8383,19 +8394,23 @@ var ensureGroup = (parent, rawSegment) => {
8383
8394
  };
8384
8395
  var pageOrder = (page, filename) => {
8385
8396
  if (page.meta.sidebar.order !== undefined) {
8386
- return page.meta.sidebar.order;
8397
+ return { order: page.meta.sidebar.order, orderIsAuthored: true };
8387
8398
  }
8388
8399
  if (isIndexStem(filename.replace(extname(filename), ""))) {
8389
- return Number.NEGATIVE_INFINITY;
8400
+ return { order: Number.NEGATIVE_INFINITY, orderIsAuthored: false };
8390
8401
  }
8391
8402
  if (page.contentType === "changelog") {
8392
8403
  const iso = page.meta.date ?? page.meta.changelog?.date;
8393
8404
  const time = iso ? Date.parse(iso) : Number.NaN;
8394
8405
  if (!Number.isNaN(time)) {
8395
- return -time;
8406
+ return { order: -time, orderIsAuthored: false };
8396
8407
  }
8397
8408
  }
8398
- return numericOrder(filename);
8409
+ const order = numericOrder(filename);
8410
+ return {
8411
+ order,
8412
+ orderIsAuthored: page.contentType !== "changelog" && Number.isFinite(order)
8413
+ };
8399
8414
  };
8400
8415
  var metaKey = (path, metaPrefix) => {
8401
8416
  if (!metaPrefix) {
@@ -8416,6 +8431,9 @@ var applyFolderMeta = (group, folderMeta, sharedMeta, metaPrefix) => {
8416
8431
  const position = rank.get(child.key);
8417
8432
  if (position !== undefined) {
8418
8433
  child.order = position;
8434
+ if (child.kind === "page") {
8435
+ child.orderIsAuthored = true;
8436
+ }
8419
8437
  }
8420
8438
  }
8421
8439
  }
@@ -8426,7 +8444,56 @@ var applyFolderMeta = (group, folderMeta, sharedMeta, metaPrefix) => {
8426
8444
  }
8427
8445
  }
8428
8446
  };
8429
- var sortNodes = (nodes) => {
8447
+ var indexTitleMismatchDiagnostic = (page, folderPath, folderMeta, sharedMeta, metaPrefix) => {
8448
+ if (!page.meta.title || page.fallback || folderPath === "") {
8449
+ return;
8450
+ }
8451
+ const meta = folderMeta.get(metaKey(folderPath, metaPrefix)) ?? sharedMeta.get(folderPath);
8452
+ if (!meta?.title || meta.title === page.title) {
8453
+ return;
8454
+ }
8455
+ return {
8456
+ code: "BLUME_NAV_INDEX_TITLE_MISMATCH",
8457
+ file: page.sourcePath ?? page.id,
8458
+ message: `Index page "${page.navPath}" has title "${page.title}", but its folder's meta.title is "${meta.title}" — the sidebar shows the folder title while the page's own <title>/heading still say "${page.title}".`,
8459
+ severity: "warning",
8460
+ suggestion: `Update the page's frontmatter title to match ("${meta.title}"), or leave it if the divergence is intentional.`
8461
+ };
8462
+ };
8463
+ var isAuthoredOrder = (node) => node.kind === "group" || node.orderIsAuthored;
8464
+ var duplicateOrderDiagnostics = (nodes) => {
8465
+ const byOrder = new Map;
8466
+ for (const node of nodes) {
8467
+ if (!Number.isFinite(node.order) || !isAuthoredOrder(node)) {
8468
+ continue;
8469
+ }
8470
+ const tied = byOrder.get(node.order);
8471
+ if (tied) {
8472
+ tied.push(node);
8473
+ } else {
8474
+ byOrder.set(node.order, [node]);
8475
+ }
8476
+ }
8477
+ const diagnostics = [];
8478
+ for (const [order, tied] of byOrder) {
8479
+ if (tied.length > 1) {
8480
+ const names = tied.map((node) => `"${node.label}"`);
8481
+ const list2 = names.length > 2 ? `${names.slice(0, -1).join(", ")}, and ${names.at(-1)}` : names.join(" and ");
8482
+ const verb = tied.length > 2 ? "all have" : "both have";
8483
+ const file = tied.find((node) => node.kind === "page")?.file;
8484
+ diagnostics.push({
8485
+ code: "BLUME_DUPLICATE_SIDEBAR_ORDER",
8486
+ file,
8487
+ message: `${list2} ${verb} sidebar order ${order}; falling back to alphabetical order.`,
8488
+ severity: "warning",
8489
+ suggestion: "Give each item a distinct sidebar.order (or folder meta order)."
8490
+ });
8491
+ }
8492
+ }
8493
+ return diagnostics;
8494
+ };
8495
+ var sortNodes = (nodes, diagnostics) => {
8496
+ diagnostics.push(...duplicateOrderDiagnostics(nodes));
8430
8497
  nodes.sort((a, b) => {
8431
8498
  if (a.order !== b.order) {
8432
8499
  return a.order - b.order;
@@ -8435,7 +8502,7 @@ var sortNodes = (nodes) => {
8435
8502
  });
8436
8503
  for (const node of nodes) {
8437
8504
  if (node.kind === "group") {
8438
- sortNodes(node.children);
8505
+ sortNodes(node.children, diagnostics);
8439
8506
  }
8440
8507
  }
8441
8508
  };
@@ -8483,16 +8550,22 @@ var toNavNode = (node, display) => {
8483
8550
  path: node.routePath
8484
8551
  };
8485
8552
  };
8486
- var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix, display, tabPaths) => {
8553
+ var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix, display, tabPaths, diagnostics = []) => {
8487
8554
  const root = createGroup("", "", "", 0);
8488
8555
  for (const page of pages) {
8489
- if (page.meta.sidebar.hidden) {
8490
- continue;
8491
- }
8492
8556
  const parts = page.navPath.split("/");
8493
8557
  const filename = parts.at(-1) ?? page.navPath;
8494
8558
  const stem = filename.replace(extname(filename), "");
8495
8559
  const dirs = parts.slice(0, -1);
8560
+ if (isIndexStem(stem)) {
8561
+ const diagnostic = indexTitleMismatchDiagnostic(page, dirs.join("/"), folderMeta, sharedMeta, metaPrefix);
8562
+ if (diagnostic) {
8563
+ diagnostics.push(diagnostic);
8564
+ }
8565
+ }
8566
+ if (page.meta.sidebar.hidden) {
8567
+ continue;
8568
+ }
8496
8569
  const routeSegments = page.route.split("/").filter(Boolean);
8497
8570
  const folderParts = isIndexStem(stem) ? routeSegments : routeSegments.slice(0, -1);
8498
8571
  const routeDirCount = dirs.filter((dir) => !GROUP_FOLDER.test(dir)).length;
@@ -8506,21 +8579,24 @@ var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix, display
8506
8579
  }
8507
8580
  parent.routePath ??= `/${folderParts.slice(0, consumed).join("/")}`;
8508
8581
  }
8582
+ const { order, orderIsAuthored } = pageOrder(page, filename);
8509
8583
  parent.children.push({
8510
8584
  badge: page.meta.sidebar.badge,
8511
8585
  deprecated: page.meta.deprecated || undefined,
8512
8586
  description: page.description,
8587
+ file: page.sourcePath,
8513
8588
  icon: page.meta.sidebar.icon,
8514
8589
  key: segmentKey(stem),
8515
8590
  kind: "page",
8516
8591
  label: page.meta.sidebar.label ?? page.title,
8517
- order: pageOrder(page, filename),
8592
+ order,
8593
+ orderIsAuthored,
8518
8594
  pageId: page.id,
8519
8595
  route: page.route
8520
8596
  });
8521
8597
  }
8522
8598
  applyFolderMeta(root, folderMeta, sharedMeta, metaPrefix);
8523
- sortNodes(root.children);
8599
+ sortNodes(root.children, diagnostics);
8524
8600
  hoistPages(root.children, display === "flat");
8525
8601
  hoistTabSections(root.children, tabPaths, display === "flat");
8526
8602
  return root.children.map((child) => toNavNode(child, display));
@@ -8672,7 +8748,7 @@ var buildNavigation = (pages, options) => {
8672
8748
  };
8673
8749
  }
8674
8750
  const rootTabPath = withBasePath(basePath, options.localizedRoot ?? "/");
8675
- const sidebar = buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, display, new Set(tabs.flatMap((tab) => tab.path === rootTabPath ? [] : [tab.path])));
8751
+ const sidebar = buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, display, new Set(tabs.flatMap((tab) => tab.path === rootTabPath ? [] : [tab.path])), options.diagnostics);
8676
8752
  return {
8677
8753
  featured,
8678
8754
  selectors,
@@ -8711,6 +8787,7 @@ var localePagesFor = (code, real, fallback, fallbackByKey, i18n, basePath) => {
8711
8787
  if (!present.has(key)) {
8712
8788
  filled.push({
8713
8789
  ...source,
8790
+ fallback: true,
8714
8791
  locale: code,
8715
8792
  route: withBasePath(basePath, localizeRoute(key, code, i18n))
8716
8793
  });
@@ -8718,7 +8795,7 @@ var localePagesFor = (code, real, fallback, fallbackByKey, i18n, basePath) => {
8718
8795
  }
8719
8796
  return [...real, ...filled];
8720
8797
  };
8721
- var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n) => {
8798
+ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n, diagnostics) => {
8722
8799
  const localizePath = (path) => path.startsWith("/") ? localizeRoute(path, code, i18n) : path;
8723
8800
  const tabs = options.navigation.tabs?.map((tab) => ({
8724
8801
  ...tab,
@@ -8732,6 +8809,7 @@ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n
8732
8809
  const localePages = localePagesFor(code, real, fallback, fallbackByKey, i18n, options.basePath ?? "");
8733
8810
  return buildNavigation(localePages, {
8734
8811
  basePath: options.basePath ?? "",
8812
+ diagnostics,
8735
8813
  display: options.navigation.sidebar.display,
8736
8814
  featured: options.navigation.featured,
8737
8815
  folderMeta: options.folderMeta,
@@ -8744,7 +8822,7 @@ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n
8744
8822
  tabs
8745
8823
  });
8746
8824
  };
8747
- var buildI18nNavigation = (pages, options, i18n) => {
8825
+ var buildI18nNavigation = (pages, options, i18n, diagnostics) => {
8748
8826
  const fallback = resolveFallbackLocale(i18n);
8749
8827
  const fallbackByKey = new Map;
8750
8828
  if (fallback) {
@@ -8755,8 +8833,19 @@ var buildI18nNavigation = (pages, options, i18n) => {
8755
8833
  }
8756
8834
  }
8757
8835
  const navigationByLocale = {};
8836
+ const seen = new Set;
8758
8837
  for (const { code } of i18n.locales) {
8759
- navigationByLocale[code] = buildLocaleNavigation(code, pages, fallback, fallbackByKey, options, i18n);
8838
+ const localeDiagnostics = [];
8839
+ navigationByLocale[code] = buildLocaleNavigation(code, pages, fallback, fallbackByKey, options, i18n, localeDiagnostics);
8840
+ for (const diagnostic of localeDiagnostics) {
8841
+ const key = `${diagnostic.code}
8842
+ ${diagnostic.file ?? ""}
8843
+ ${diagnostic.message}`;
8844
+ if (!seen.has(key)) {
8845
+ seen.add(key);
8846
+ diagnostics.push(diagnostic);
8847
+ }
8848
+ }
8760
8849
  }
8761
8850
  const navigation = navigationByLocale[i18n.defaultLocale] ?? {
8762
8851
  featured: [],
@@ -8769,9 +8858,10 @@ var buildI18nNavigation = (pages, options, i18n) => {
8769
8858
  var buildContentGraph = (pages, options) => {
8770
8859
  const { diagnostics, routes } = collectRoutes(pages);
8771
8860
  const { i18n } = options;
8772
- const { navigation, navigationByLocale } = i18n ? buildI18nNavigation(pages, options, i18n) : {
8861
+ const { navigation, navigationByLocale } = i18n ? buildI18nNavigation(pages, options, i18n, diagnostics) : {
8773
8862
  navigation: buildNavigation(pages, {
8774
8863
  basePath: options.basePath ?? "",
8864
+ diagnostics,
8775
8865
  display: options.navigation.sidebar.display,
8776
8866
  featured: options.navigation.featured,
8777
8867
  folderMeta: options.folderMeta,
@@ -9939,6 +10029,34 @@ var PER_PAGE = 100;
9939
10029
  var LEADING_V = /^v/iu;
9940
10030
  var NON_SLUG3 = /[^a-z0-9]+/gu;
9941
10031
  var EDGE_DASHES = /^-+|-+$/gu;
10032
+ var DESCRIPTION_MAX = 160;
10033
+ var DESCRIPTION_MIN = 110;
10034
+ var CODE_FENCE2 = /```[\s\S]*?```/gu;
10035
+ var HEADING_LINE = /^#{1,6}\s.*$/gmu;
10036
+ var LIST_MARK = /^\s*(?:[-*+]|\d+[.)])\s+/u;
10037
+ var CHANGESET_HASH = /^[0-9a-f]{7,40}:\s+/u;
10038
+ var IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
10039
+ var LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
10040
+ var INLINE_CODE2 = /`(?<code>[^`]+)`/gu;
10041
+ var HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
10042
+ var MARKDOWN_PUNCT = /[*_~>]+/gu;
10043
+ var WHITESPACE2 = /\s+/gu;
10044
+ var TRAILING_FRAGMENT = /[\s,;:.—–-]+$/u;
10045
+ var releaseDescription = (body) => {
10046
+ const text = body.replaceAll(CODE_FENCE2, " ").replaceAll(HEADING_LINE, "").split(`
10047
+ `).map((line) => line.replace(LIST_MARK, "").replace(CHANGESET_HASH, "")).join(`
10048
+ `).replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>").replaceAll(INLINE_CODE2, "$<code>").replaceAll(HTML_OR_JSX, " ").replaceAll(MARKDOWN_PUNCT, " ").replaceAll(WHITESPACE2, " ").trim();
10049
+ if (!text) {
10050
+ return;
10051
+ }
10052
+ if (text.length <= DESCRIPTION_MAX) {
10053
+ return text;
10054
+ }
10055
+ const slice = text.slice(0, DESCRIPTION_MAX - 1);
10056
+ const boundary = slice.lastIndexOf(" ");
10057
+ const head = (boundary >= DESCRIPTION_MIN ? slice.slice(0, boundary) : slice).replace(TRAILING_FRAGMENT, "");
10058
+ return `${head}…`;
10059
+ };
9942
10060
  var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG3, "-").replaceAll(EDGE_DASHES, "");
9943
10061
  var githubHeaders = () => {
9944
10062
  const headers = new Headers({ Accept: "application/vnd.github+json" });
@@ -9956,9 +10074,11 @@ var releaseToEntry = (release) => {
9956
10074
  const body = (release.body ?? "").replaceAll(`\r
9957
10075
  `, `
9958
10076
  `).trim();
10077
+ const description = releaseDescription(body);
9959
10078
  const data = {
9960
10079
  changelog: { category, version },
9961
10080
  date,
10081
+ ...description ? { seo: { description } } : {},
9962
10082
  title,
9963
10083
  type: "changelog"
9964
10084
  };
@@ -10940,6 +11060,34 @@ var entryIdDiagnostics = (pages, collectionBase) => {
10940
11060
  }
10941
11061
  return diagnostics;
10942
11062
  };
11063
+ var normalizeLoadedEntries = (loaded, config) => {
11064
+ const frontmatterExtend = Object.keys(config.frontmatter.extend).length > 0 ? config.frontmatter.extend : undefined;
11065
+ const pages = [];
11066
+ const allDiagnostics = [];
11067
+ let droppedPages = 0;
11068
+ for (const { source, entries, diagnostics } of loaded) {
11069
+ allDiagnostics.push(...diagnostics);
11070
+ for (const entry of entries) {
11071
+ const normalized = normalizeEntry(entry, {
11072
+ basePath: config.basePath,
11073
+ defaultType: config.content.defaultType,
11074
+ frontmatterExtend,
11075
+ i18n: config.i18n,
11076
+ source: {
11077
+ name: source.name,
11078
+ prefix: source.prefix,
11079
+ staged: source.staged
11080
+ }
11081
+ });
11082
+ if (normalized.pages.length === 0 && normalized.diagnostics.length > 0) {
11083
+ droppedPages += 1;
11084
+ }
11085
+ pages.push(...normalized.pages);
11086
+ allDiagnostics.push(...normalized.diagnostics);
11087
+ }
11088
+ }
11089
+ return { diagnostics: allDiagnostics, droppedPages, pages };
11090
+ };
10943
11091
  var scanProject = async (root, options = {}) => {
10944
11092
  const mode = options.mode ?? "dev";
10945
11093
  const preview = options.preview ?? false;
@@ -10964,27 +11112,11 @@ var scanProject = async (root, options = {}) => {
10964
11112
  Promise.all(sources.map(async (source) => ({ source, ...await source.load() }))),
10965
11113
  discoverFolderMeta(metaSources, { localeDirs })
10966
11114
  ]);
10967
- const frontmatterExtend = Object.keys(config.frontmatter.extend).length > 0 ? config.frontmatter.extend : undefined;
10968
- const allPages = [];
10969
- const contentDiagnostics = [];
10970
- for (const { source, entries, diagnostics } of loaded) {
10971
- contentDiagnostics.push(...diagnostics);
10972
- for (const entry of entries) {
10973
- const normalized = normalizeEntry(entry, {
10974
- basePath: config.basePath,
10975
- defaultType: config.content.defaultType,
10976
- frontmatterExtend,
10977
- i18n: config.i18n,
10978
- source: {
10979
- name: source.name,
10980
- prefix: source.prefix,
10981
- staged: source.staged
10982
- }
10983
- });
10984
- allPages.push(...normalized.pages);
10985
- contentDiagnostics.push(...normalized.diagnostics);
10986
- }
10987
- }
11115
+ const {
11116
+ diagnostics: contentDiagnostics,
11117
+ droppedPages,
11118
+ pages: allPages
11119
+ } = normalizeLoadedEntries(loaded, config);
10988
11120
  const pages = mode === "build" && !preview ? allPages.filter((page) => !page.meta.draft) : allPages;
10989
11121
  const lastModified = resolveLastModifiedConfig(config.lastModified);
10990
11122
  if (lastModified.enabled && lastModified.source === "git") {
@@ -11016,6 +11148,7 @@ var scanProject = async (root, options = {}) => {
11016
11148
  ...graph.diagnostics,
11017
11149
  ...i18nWarnings
11018
11150
  ],
11151
+ droppedPages,
11019
11152
  graph,
11020
11153
  manifest,
11021
11154
  mode,
@@ -11390,15 +11523,15 @@ var parseYouTubeId = (input) => {
11390
11523
  };
11391
11524
 
11392
11525
  // src/ai/component-markdown.ts
11393
- var evaluateExpression = (raw) => {
11526
+ var evaluateExpression = (raw, frontmatter) => {
11394
11527
  try {
11395
- const value = new Function(`"use strict"; return (${raw});`)();
11528
+ const value = new Function("frontmatter", `"use strict"; return (${raw});`)(frontmatter);
11396
11529
  return { ok: true, value };
11397
11530
  } catch {
11398
11531
  return { ok: false, value: undefined };
11399
11532
  }
11400
11533
  };
11401
- var readProps = (node) => {
11534
+ var readProps = (node, frontmatter) => {
11402
11535
  const props = {};
11403
11536
  let lossy = false;
11404
11537
  for (const attribute of node.attributes ?? []) {
@@ -11411,7 +11544,7 @@ var readProps = (node) => {
11411
11544
  } else if (typeof attribute.value === "string") {
11412
11545
  props[attribute.name] = attribute.value;
11413
11546
  } else {
11414
- const result = evaluateExpression(attribute.value.value);
11547
+ const result = evaluateExpression(attribute.value.value, frontmatter);
11415
11548
  if (result.ok) {
11416
11549
  props[attribute.name] = result.value;
11417
11550
  } else {
@@ -11578,12 +11711,13 @@ var renderChildren2 = (walk, node) => {
11578
11711
  return dedent(spliced).trim();
11579
11712
  };
11580
11713
  var serializeElement = (serializer, walk, node) => serializer({
11581
- ...readProps(node),
11714
+ ...readProps(node, walk.frontmatter),
11582
11715
  childComponents: (name) => (node.children ?? []).filter((child) => isJsxElement(child) && child.name === name).map((child) => ({
11583
- ...readProps(child),
11716
+ ...readProps(child, walk.frontmatter),
11584
11717
  children: renderChildren2(walk, child)
11585
11718
  })),
11586
- children: renderChildren2(walk, node)
11719
+ children: renderChildren2(walk, node),
11720
+ frontmatter: walk.frontmatter ?? {}
11587
11721
  });
11588
11722
  var collectSplices = (walk, nodes, out) => {
11589
11723
  for (const node of nodes) {
@@ -11602,7 +11736,7 @@ var collectSplices = (walk, nodes, out) => {
11602
11736
  collectSplices(walk, node.children ?? [], out);
11603
11737
  }
11604
11738
  };
11605
- var downlevelComponents = (source, components) => {
11739
+ var downlevelComponents = (source, components, frontmatter) => {
11606
11740
  const custom = components && Object.keys(components).length > 0;
11607
11741
  const registry2 = custom ? { ...SERIALIZERS, ...components } : SERIALIZERS;
11608
11742
  const hint = custom ? componentHint(registry2) : BUILT_IN_HINT;
@@ -11616,7 +11750,7 @@ var downlevelComponents = (source, components) => {
11616
11750
  return source;
11617
11751
  }
11618
11752
  const splices = [];
11619
- collectSplices({ registry: registry2, source }, tree.children ?? [], splices);
11753
+ collectSplices({ frontmatter, registry: registry2, source }, tree.children ?? [], splices);
11620
11754
  return splices.length > 0 ? applySplices(source, splices) : source;
11621
11755
  };
11622
11756
 
@@ -11746,7 +11880,8 @@ var buildFull = async (project) => {
11746
11880
  const pages = eligiblePages(project).toSorted((a, b) => a.route.localeCompare(b.route));
11747
11881
  const sections = await Promise.all(pages.map(async (page) => {
11748
11882
  const raw = await readEntryText(project, page);
11749
- const body = downlevelComponents(applyAgentVisibility(frontmatter_default(raw).content), config.ai.markdownComponents).trim();
11883
+ const parsed = frontmatter_default(raw);
11884
+ const body = downlevelComponents(applyAgentVisibility(parsed.content), config.ai.markdownComponents, parsed.data).trim();
11750
11885
  const url = pageUrl(page.route, config.deployment.site, normalizeBasePath(config.deployment.base));
11751
11886
  return [`# ${page.title}`, `Source: ${url}`, "", body].join(`
11752
11887
  `);
@@ -11920,7 +12055,11 @@ var hasGeneratedChangelog = (project, userPages) => {
11920
12055
  return (hasChangelog || hasChangelogSource) && !routeIsTaken(userPages, project.graph.pages, "/changelog");
11921
12056
  };
11922
12057
  var humanizeSegment = (segment) => segment.split(/[-_]/u).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
11923
- var customOgRoutes = (pages, siteTitle) => {
12058
+ var customOgRoutes = (pages, siteTitle, titles = {}) => {
12059
+ const overrides = new Map(Object.entries(titles).map(([route, title]) => [
12060
+ `/${route.split("/").filter(Boolean).join("/")}`,
12061
+ title
12062
+ ]));
11924
12063
  const seen = new Set;
11925
12064
  const routes = [];
11926
12065
  const collectRoute = (pattern) => {
@@ -11934,7 +12073,10 @@ var customOgRoutes = (pages, siteTitle) => {
11934
12073
  }
11935
12074
  seen.add(slug);
11936
12075
  const last = segments.at(-1);
11937
- routes.push({ slug, title: last ? humanizeSegment(last) : siteTitle });
12076
+ routes.push({
12077
+ slug,
12078
+ title: overrides.get(`/${segments.join("/")}`) ?? (last ? humanizeSegment(last) : siteTitle)
12079
+ });
11938
12080
  };
11939
12081
  for (const { pattern } of pages) {
11940
12082
  collectRoute(pattern);
@@ -12006,25 +12148,25 @@ var buildSearchIndex = async (outDir) => {
12006
12148
  };
12007
12149
 
12008
12150
  // src/search/documents.ts
12009
- var CODE_FENCE2 = /```[\s\S]*?```/gu;
12010
- var INLINE_CODE2 = /`(?<code>[^`]+)`/gu;
12011
- var HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
12012
- var IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
12013
- var LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
12151
+ var CODE_FENCE3 = /```[\s\S]*?```/gu;
12152
+ var INLINE_CODE3 = /`(?<code>[^`]+)`/gu;
12153
+ var HTML_OR_JSX2 = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
12154
+ var IMAGE2 = /!\[[^\]]*\]\([^)]*\)/gu;
12155
+ var LINK2 = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
12014
12156
  var HEADING_MARK = /^#{1,6}\s+/gmu;
12015
- var MARKDOWN_PUNCT = /[*_~>]+/gu;
12016
- var WHITESPACE2 = /\s+/gu;
12157
+ var MARKDOWN_PUNCT2 = /[*_~>]+/gu;
12158
+ var WHITESPACE3 = /\s+/gu;
12017
12159
  var toPlainText = (markdown) => {
12018
- const withoutBlocks = markdown.replaceAll(CODE_FENCE2, " ").replaceAll(IMAGE, " ").replaceAll(LINK, "$<text>");
12160
+ const withoutBlocks = markdown.replaceAll(CODE_FENCE3, " ").replaceAll(IMAGE2, " ").replaceAll(LINK2, "$<text>");
12019
12161
  const pieces = [];
12020
12162
  let cursor = 0;
12021
- for (const match of withoutBlocks.matchAll(INLINE_CODE2)) {
12163
+ for (const match of withoutBlocks.matchAll(INLINE_CODE3)) {
12022
12164
  const start = match.index ?? 0;
12023
- pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "), match.groups?.code ?? "");
12165
+ pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX2, " "), match.groups?.code ?? "");
12024
12166
  cursor = start + match[0].length;
12025
12167
  }
12026
- pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
12027
- return pieces.join("").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT, " ").replaceAll(WHITESPACE2, " ").trim();
12168
+ pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX2, " "));
12169
+ return pieces.join("").replaceAll(HEADING_MARK, "").replaceAll(MARKDOWN_PUNCT2, " ").replaceAll(WHITESPACE3, " ").trim();
12028
12170
  };
12029
12171
  var buildCrumbIndex = (sidebar) => {
12030
12172
  const index = new Map;
@@ -12389,7 +12531,7 @@ var buildRawMarkdown = async (project) => {
12389
12531
  };
12390
12532
  const entries = await Promise.all(project.manifest.routes.map(async (route) => {
12391
12533
  const source = applyAgentVisibility(await readRoute(route));
12392
- const md = downlevelComponents(source, project.config.ai.markdownComponents);
12534
+ const md = downlevelComponents(source, project.config.ai.markdownComponents, frontmatter_default(source).data);
12393
12535
  const entry = md === source ? { mdx: source } : { md, mdx: source };
12394
12536
  return [route.path, entry];
12395
12537
  }));
@@ -14261,10 +14403,11 @@ var resolvedAstroPath = (fromDir) => {
14261
14403
  dir = parent;
14262
14404
  }
14263
14405
  };
14264
- var blumeDepsDir = (pkgDir = packageRoot()) => {
14265
- const candidates = [join26(pkgDir, "node_modules"), dirname9(pkgDir)];
14266
- return candidates.find((dir) => existsSync14(join26(dir, "astro"))) ?? null;
14267
- };
14406
+ var depsCandidates = (pkgDir) => [
14407
+ join26(pkgDir, "node_modules"),
14408
+ dirname9(pkgDir)
14409
+ ];
14410
+ var candidateHolding = (pkgDir, ...segments) => depsCandidates(pkgDir).find((dir) => existsSync14(join26(dir, ...segments))) ?? null;
14268
14411
  var linkDepsJunction = async (link, depsDir) => {
14269
14412
  let existing;
14270
14413
  try {
@@ -14299,17 +14442,19 @@ var astroConflictWarning = (blumeAstroPkg, shadowAstroPkg) => {
14299
14442
  return `Astro version conflict: another dependency hoisted ${versions} to the project root, so @astrojs/mdx binds to the wrong copy and the build fails on a missing export (e.g. "chunkToString"). A single symlink can't reconcile a split install — pin Blume's Astro by adding a package.json "overrides" (npm/bun/pnpm) or "resolutions" (yarn) entry { "astro": "${pin}" }, then reinstall. Run \`npm ls astro\` to find the dependency pulling the older copy.`;
14300
14443
  };
14301
14444
  var ensureDepsLink = async (outDir, pkgDir = packageRoot()) => {
14302
- const depsDir = blumeDepsDir(pkgDir);
14303
- if (!depsDir) {
14445
+ const astroDir = candidateHolding(pkgDir, "astro");
14446
+ if (!astroDir) {
14304
14447
  return null;
14305
14448
  }
14306
- const blumeAstro = resolveAstroPackageJson(depsDir);
14449
+ const mdxDir = candidateHolding(pkgDir, "@astrojs", "mdx");
14450
+ const blumeAstro = resolveAstroPackageJson(astroDir);
14307
14451
  const outDirAstro = resolvedAstroPath(outDir);
14308
- if (blumeAstro && outDirAstro === blumeAstro) {
14452
+ const astroCorrect = blumeAstro !== null && outDirAstro === blumeAstro;
14453
+ if (astroCorrect && mdxDir === astroDir) {
14309
14454
  return null;
14310
14455
  }
14311
- if (existsSync14(join26(depsDir, "@astrojs", "mdx"))) {
14312
- await linkDepsJunction(join26(outDir, "node_modules"), depsDir);
14456
+ if (mdxDir && (mdxDir === astroDir || astroCorrect)) {
14457
+ await linkDepsJunction(join26(outDir, "node_modules"), mdxDir);
14313
14458
  return null;
14314
14459
  }
14315
14460
  return astroConflictWarning(blumeAstro, outDirAstro);
@@ -14807,7 +14952,7 @@ var generateRuntime = async (project) => {
14807
14952
  const needsVue = frameworks.has("vue");
14808
14953
  const needsSvelte = frameworks.has("svelte");
14809
14954
  const reactCompilerPath = resolveReactCompiler(config, needsReact);
14810
- const ogRoutes = customOgRoutes(pages, config.title);
14955
+ const ogRoutes = customOgRoutes(pages, config.title, config.seo.og.titles);
14811
14956
  const mcp = planMcp(project, srcDir, pages);
14812
14957
  pages.push(...mcp.discoveryPages);
14813
14958
  const hasStaged = staged.size > 0;
@@ -15104,12 +15249,13 @@ var prepareProject = async (options) => {
15104
15249
  }
15105
15250
  }
15106
15251
  const hadErrors = reportDiagnostics(project.diagnostics, options.root);
15252
+ const dropped = project.droppedPages > 0 ? `${project.droppedPages} page(s) failed frontmatter validation and were dropped from the site. ` : "";
15107
15253
  if (hadErrors && options.strict) {
15108
- logger.error("Aborting due to errors (strict mode).");
15254
+ logger.error(`Aborting due to errors. ${dropped}Fix the diagnostics above, or pass --no-strict to continue despite them.`);
15109
15255
  process.exit(1);
15110
15256
  }
15111
15257
  if (hasErrors(project.diagnostics) && !options.strict) {
15112
- logger.warn("Continuing despite errors. Use --strict to fail the build.");
15258
+ logger.warn(`Continuing despite errors. ${dropped}Use --strict to fail instead.`);
15113
15259
  }
15114
15260
  const { warnings } = await generateRuntime(project);
15115
15261
  for (const warning of warnings) {
@@ -15307,6 +15453,9 @@ var publishBuildArtifacts = async (project, distDir, args) => {
15307
15453
  ].join(`
15308
15454
  `));
15309
15455
  await runClientAssetChecks(distDir, args);
15456
+ if (project.droppedPages > 0) {
15457
+ logger.warn(`${project.droppedPages} page(s) failed frontmatter validation and are missing from this build.`);
15458
+ }
15310
15459
  logger.success(`Built to ${distDir}`);
15311
15460
  };
15312
15461
  var buildCommand = defineCommand3({
@@ -15343,7 +15492,11 @@ var buildCommand = defineCommand3({
15343
15492
  description: "Include drafts and unpublished CMS content.",
15344
15493
  type: "boolean"
15345
15494
  },
15346
- strict: { description: "Fail on diagnostics.", type: "boolean" }
15495
+ strict: {
15496
+ default: true,
15497
+ description: "Fail on error diagnostics (default; pass --no-strict to build anyway, dropping pages that fail validation).",
15498
+ type: "boolean"
15499
+ }
15347
15500
  },
15348
15501
  meta: {
15349
15502
  description: "Build the docs site for production.",
@@ -16032,7 +16185,7 @@ var eject = async (root) => {
16032
16185
  }
16033
16186
  if (config.seo.og.enabled) {
16034
16187
  files.push({
16035
- content: ogEndpointTemplate(customOgRoutes(pages, config.title)),
16188
+ content: ogEndpointTemplate(customOgRoutes(pages, config.title, config.seo.og.titles)),
16036
16189
  path: join31(srcDir, "pages", "og", "[...slug].png.ts")
16037
16190
  });
16038
16191
  }
@@ -17071,5 +17224,5 @@ process.on("unhandledRejection", (error) => {
17071
17224
  });
17072
17225
  runMain(main);
17073
17226
 
17074
- //# debugId=2D51E90E387F80A664756E2164756E21
17227
+ //# debugId=F92BD712EE9633C264756E2164756E21
17075
17228
  //# sourceMappingURL=index.js.map