blume 1.3.0 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # blume
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 4d7dc87: Negotiate `Accept: text/markdown` on the homepage even when it's a landing page. A user-authored home page has no Markdown source, so agent requests for a markdown homepage previously fell through to HTML; the homepage's mirror now falls back to the `llms.txt` index — the machine-readable map of the site — served at `/index.md` and wired into the dev server, the Vercel routing config, and the homepage `Link` header's `rel="alternate"` entry.
8
+ - ff31ab5: Remove a polynomial-backtracking regex from the font-name slugifier's dash trim
9
+ - 02eb6c7: Fix the homepage `Link` header and `Vary: Accept` never being sent on Vercel deploys. The injected header routes sat after `handle: "filesystem"` in the Build Output config — the miss phase, which prerendered static responses never reach — so agent-readiness checkers saw no `Link` header on `GET /`. Both header routes now ride in the main phase, ahead of static-file matching.
10
+ - de62812: Stamp an `x-markdown-tokens` header (estimated token count, ~4 characters per token) on Markdown responses, following the Cloudflare Markdown for Agents convention: the raw-Markdown endpoints send it on dev and server-rendered responses, and the Vercel routing config carries it on the negotiated homepage.
11
+
3
12
  ## 1.3.0
4
13
 
5
14
  ### Minor Changes
package/dist/cli/index.js CHANGED
@@ -3522,7 +3522,7 @@ var GOOGLE_FONTS = {
3522
3522
  var FONT_SLUGS = Object.keys(GOOGLE_FONTS);
3523
3523
  var isFontSlug = (value) => Object.hasOwn(GOOGLE_FONTS, value);
3524
3524
  var DEFAULT_REMOTE_WEIGHTS = [400, 500, 600, 700];
3525
- var slugifyFontName = (name) => name.toLowerCase().replaceAll(/[^a-z0-9]+/gu, "-").replaceAll(/^-+|-+$/gu, "");
3525
+ var slugifyFontName = (name) => name.toLowerCase().replaceAll(/[^a-z0-9]+/gu, "-").replaceAll(/^-|-$/gu, "");
3526
3526
  var fontVar = (slug) => `--blume-ff-${slug}`;
3527
3527
  var SLOTS = ["display", "body", "mono"];
3528
3528
  var slotCategory = (slot) => slot === "mono" ? "mono" : "sans";
@@ -4269,8 +4269,13 @@ export function getStaticPaths() {
4269
4269
  export function GET({ props }: { props: { route: string } }) {
4270
4270
  const entries = raw as Record<string, { md?: string; mdx?: string }>;
4271
4271
  const entry = entries[props.route];
4272
- return new Response(entry ? ${kind === "md" ? '(entry.md ?? entry.mdx ?? "")' : '(entry.mdx ?? "")'} : "", {
4273
- headers: { "Content-Type": "text/markdown; charset=utf-8" },
4272
+ const body = entry ? ${kind === "md" ? '(entry.md ?? entry.mdx ?? "")' : '(entry.mdx ?? "")'} : "";
4273
+ return new Response(body, {
4274
+ headers: {
4275
+ "Content-Type": "text/markdown; charset=utf-8",
4276
+ // ~4 characters per token; keep in sync with markdownTokenCount.
4277
+ "x-markdown-tokens": String(Math.ceil(body.length / 4)),
4278
+ },
4274
4279
  });
4275
4280
  }
4276
4281
  `;
@@ -12593,7 +12598,7 @@ var indexedNavigations = (project) => {
12593
12598
  }
12594
12599
  return [{ nav: project.graph.navigation }];
12595
12600
  };
12596
- var buildIndex = (project) => {
12601
+ var buildLlmsIndex = (project) => {
12597
12602
  const { config } = project;
12598
12603
  const { site } = config.deployment;
12599
12604
  const base = normalizeBasePath(config.deployment.base);
@@ -12704,12 +12709,55 @@ ${sections.join(`
12704
12709
  };
12705
12710
  var buildLlmsFiles = async (project) => ({
12706
12711
  full: await buildFull(project),
12707
- index: buildIndex(project)
12712
+ index: buildLlmsIndex(project)
12708
12713
  });
12709
12714
 
12715
+ // src/ai/markdown.ts
12716
+ import { readFile as readFile10 } from "node:fs/promises";
12717
+ var agentMarkdown = (entry) => entry.md ?? entry.mdx;
12718
+ var markdownTokenCount = (text) => Math.ceil(text.length / 4);
12719
+ var buildRawMarkdown = async (project) => {
12720
+ const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
12721
+ const components = {
12722
+ ...exampleComponentSerializers(project.examples ?? {}),
12723
+ ...project.config.ai.markdownComponents
12724
+ };
12725
+ const readRoute = async (route) => {
12726
+ const page = pageById.get(route.id);
12727
+ if (page) {
12728
+ return await readEntryText(project, page);
12729
+ }
12730
+ return route.sourcePath ? await readFile10(route.sourcePath, "utf-8") : "";
12731
+ };
12732
+ const entries = await Promise.all(project.manifest.routes.map(async (route) => {
12733
+ let text = await readRoute(route);
12734
+ if (route.sourcePath) {
12735
+ text = rewriteRelativeImages({
12736
+ deployBase: project.config.deployment.base,
12737
+ projectRoot: project.context.root,
12738
+ source: text,
12739
+ sourcePath: route.sourcePath
12740
+ });
12741
+ }
12742
+ const source = applyAgentVisibility(text);
12743
+ const md = downlevelComponents(source, components, frontmatter_default(source).data);
12744
+ const entry = md === source ? { mdx: source } : { md, mdx: source };
12745
+ return [route.path, entry];
12746
+ }));
12747
+ const map = Object.fromEntries(entries);
12748
+ if (!map["/"]) {
12749
+ map["/"] = { mdx: buildLlmsIndex(project) };
12750
+ }
12751
+ return map;
12752
+ };
12753
+ var markdownRoutePaths = (project) => {
12754
+ const paths = project.manifest.routes.map((route) => route.path);
12755
+ return paths.includes("/") ? paths : [...paths, "/"];
12756
+ };
12757
+
12710
12758
  // src/ai/skills.ts
12711
12759
  import { createHash as createHash2 } from "node:crypto";
12712
- import { readdir, readFile as readFile10, stat as stat2 } from "node:fs/promises";
12760
+ import { readdir, readFile as readFile11, stat as stat2 } from "node:fs/promises";
12713
12761
  import { join as join18 } from "pathe";
12714
12762
 
12715
12763
  // src/ai/tar.ts
@@ -12794,7 +12842,7 @@ var collectEntries = async (dir, prefix = "") => {
12794
12842
  if (item.isDirectory()) {
12795
12843
  entries.push(...await collectEntries(path, relative12));
12796
12844
  } else if (item.isFile()) {
12797
- const [content, info] = await Promise.all([readFile10(path), stat2(path)]);
12845
+ const [content, info] = await Promise.all([readFile11(path), stat2(path)]);
12798
12846
  entries.push({
12799
12847
  content: new Uint8Array(content),
12800
12848
  executable: (info.mode & 64) !== 0,
@@ -12895,12 +12943,12 @@ var buildSignaturesDirectory = (config) => {
12895
12943
 
12896
12944
  // src/core/gitignore.ts
12897
12945
  import { existsSync as existsSync10 } from "node:fs";
12898
- import { readFile as readFile11, writeFile as writeFile6 } from "node:fs/promises";
12946
+ import { readFile as readFile12, writeFile as writeFile6 } from "node:fs/promises";
12899
12947
  import { join as join19 } from "pathe";
12900
12948
  var gitignoreKey = (line) => line.trim().replace(/\/+$/u, "");
12901
12949
  var ensureGitignore = async (root, entries) => {
12902
12950
  const path = join19(root, ".gitignore");
12903
- const existing = existsSync10(path) ? await readFile11(path, "utf-8") : "";
12951
+ const existing = existsSync10(path) ? await readFile12(path, "utf-8") : "";
12904
12952
  const present = new Set(existing.split(`
12905
12953
  `).flatMap((line) => {
12906
12954
  const key = gitignoreKey(line);
@@ -13167,7 +13215,7 @@ var chunkPatterns = (patterns) => {
13167
13215
  }
13168
13216
  return chunks;
13169
13217
  };
13170
- var buildNegotiationRoutes = (routePaths) => {
13218
+ var buildNegotiationRoutes = (routePaths, homeTokens) => {
13171
13219
  const home = routePaths.includes("/");
13172
13220
  const rest = routePaths.filter((path) => path !== "/").map((path) => routePattern(path));
13173
13221
  const chunks = chunkPatterns(rest);
@@ -13175,7 +13223,7 @@ var buildNegotiationRoutes = (routePaths) => {
13175
13223
  {
13176
13224
  dest: "/index.md",
13177
13225
  has: ACCEPT_MARKDOWN_CONDITION,
13178
- headers: VARY_ACCEPT,
13226
+ headers: homeTokens === undefined ? VARY_ACCEPT : { ...VARY_ACCEPT, "x-markdown-tokens": String(homeTokens) },
13179
13227
  src: "^/$"
13180
13228
  }
13181
13229
  ] : [];
@@ -13197,7 +13245,7 @@ var buildNegotiationRoutes = (routePaths) => {
13197
13245
  };
13198
13246
  var HOME_SRC = "^/$";
13199
13247
  var isNegotiationRoute = (route) => route.has?.some((condition) => condition.value === ACCEPT_MARKDOWN_HEADER_VALUE) === true || route.continue === true && route.headers?.vary === "Accept" && typeof route.src === "string" && Object.keys(route).length === 3 || route.continue === true && typeof route.headers?.link === "string" && route.src === HOME_SRC && Object.keys(route).length === 3;
13200
- var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTypeOverrides) => {
13248
+ var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTypeOverrides, homeTokens) => {
13201
13249
  const overrideEntries = Object.entries(contentTypeOverrides ?? {});
13202
13250
  if (routePaths.length === 0 && !homeLinkHeader && overrideEntries.length === 0) {
13203
13251
  return null;
@@ -13219,7 +13267,7 @@ var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTy
13219
13267
  if (filesystemIndex === -1) {
13220
13268
  return null;
13221
13269
  }
13222
- const { headerRoutes, rewriteRoutes } = buildNegotiationRoutes(routePaths);
13270
+ const { headerRoutes, rewriteRoutes } = buildNegotiationRoutes(routePaths, homeTokens);
13223
13271
  if (homeLinkHeader) {
13224
13272
  headerRoutes.push({
13225
13273
  continue: true,
@@ -13227,8 +13275,7 @@ var injectNegotiationRoutes = (configText, routePaths, homeLinkHeader, contentTy
13227
13275
  src: HOME_SRC
13228
13276
  });
13229
13277
  }
13230
- routes.splice(filesystemIndex + 1, 0, ...headerRoutes);
13231
- routes.splice(filesystemIndex, 0, ...rewriteRoutes);
13278
+ routes.splice(filesystemIndex, 0, ...headerRoutes, ...rewriteRoutes);
13232
13279
  config.routes = routes;
13233
13280
  return `${JSON.stringify(config, null, "\t")}
13234
13281
  `;
@@ -13622,40 +13669,6 @@ var buildAskData = async (project) => {
13622
13669
  };
13623
13670
  };
13624
13671
 
13625
- // src/ai/markdown.ts
13626
- import { readFile as readFile12 } from "node:fs/promises";
13627
- var agentMarkdown = (entry) => entry.md ?? entry.mdx;
13628
- var buildRawMarkdown = async (project) => {
13629
- const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
13630
- const components = {
13631
- ...exampleComponentSerializers(project.examples ?? {}),
13632
- ...project.config.ai.markdownComponents
13633
- };
13634
- const readRoute = async (route) => {
13635
- const page = pageById.get(route.id);
13636
- if (page) {
13637
- return await readEntryText(project, page);
13638
- }
13639
- return route.sourcePath ? await readFile12(route.sourcePath, "utf-8") : "";
13640
- };
13641
- const entries = await Promise.all(project.manifest.routes.map(async (route) => {
13642
- let text2 = await readRoute(route);
13643
- if (route.sourcePath) {
13644
- text2 = rewriteRelativeImages({
13645
- deployBase: project.config.deployment.base,
13646
- projectRoot: project.context.root,
13647
- source: text2,
13648
- sourcePath: route.sourcePath
13649
- });
13650
- }
13651
- const source = applyAgentVisibility(text2);
13652
- const md = downlevelComponents(source, components, frontmatter_default(source).data);
13653
- const entry = md === source ? { mdx: source } : { md, mdx: source };
13654
- return [route.path, entry];
13655
- }));
13656
- return Object.fromEntries(entries);
13657
- };
13658
-
13659
13672
  // src/ai/mcp/data.ts
13660
13673
  var buildMcpData = async (project) => {
13661
13674
  const { config, graph, manifest } = project;
@@ -16366,7 +16379,7 @@ var generateRuntime = async (project) => {
16366
16379
  aliases: resolveTsconfigAliases(context.root),
16367
16380
  askPath,
16368
16381
  config,
16369
- contentRoutes: project.manifest.routes.map((route) => route.path),
16382
+ contentRoutes: markdownRoutePaths(project),
16370
16383
  contentWatchesRuntimeDir: contentWatchesRuntimeDir(hasFilesystemSource, docsCollection.base, context),
16371
16384
  context,
16372
16385
  dataPath,
@@ -16700,7 +16713,7 @@ var emitHeaderFiles = async (project, distDir) => {
16700
16713
  if (config.deployment.output !== "static" || existsSync18(join30(distDir, "_headers"))) {
16701
16714
  return;
16702
16715
  }
16703
- await writeFile8(join30(distDir, "_headers"), buildNetlifyHeaders(config, buildHomeLinkHeader(config, project.manifest.routes.map((route) => route.path))), "utf-8");
16716
+ await writeFile8(join30(distDir, "_headers"), buildNetlifyHeaders(config, buildHomeLinkHeader(config, markdownRoutePaths(project))), "utf-8");
16704
16717
  logger.success("Emitted _headers (UTF-8 Content-Type + homepage Link header)");
16705
16718
  };
16706
16719
  var emitAgentSkills = async (project, distDir) => {
@@ -16756,7 +16769,8 @@ var emitWellKnownFiles = async (config, distDir) => {
16756
16769
  logger.success(`Generated ${file.path.slice(1)} (${file.label})`);
16757
16770
  }
16758
16771
  };
16759
- var emitVercelNegotiation = async (config, routePaths, root) => {
16772
+ var emitVercelNegotiation = async (project, routePaths, root) => {
16773
+ const { config } = project;
16760
16774
  const configPath = join30(root, ".vercel", "output", "config.json");
16761
16775
  if (!existsSync18(configPath)) {
16762
16776
  return;
@@ -16765,7 +16779,9 @@ var emitVercelNegotiation = async (config, routePaths, root) => {
16765
16779
  ...hasApiCatalog(config) ? { [API_CATALOG_PATH.slice(1)]: API_CATALOG_TYPE } : {},
16766
16780
  ...config.ai.webBotAuth.keys.length > 0 ? { [SIGNATURES_DIRECTORY_PATH.slice(1)]: SIGNATURES_DIRECTORY_TYPE } : {}
16767
16781
  };
16768
- const injected = injectNegotiationRoutes(await readFile17(configPath, "utf-8"), routePaths, buildHomeLinkHeader(config, routePaths), overrides);
16782
+ const rawMarkdown = await buildRawMarkdown(project);
16783
+ const home = rawMarkdown["/"];
16784
+ const injected = injectNegotiationRoutes(await readFile17(configPath, "utf-8"), routePaths, buildHomeLinkHeader(config, routePaths), overrides, home ? markdownTokenCount(agentMarkdown(home)) : undefined);
16769
16785
  if (injected === null) {
16770
16786
  logger.warn("Could not wire Accept: text/markdown negotiation into .vercel/output/config.json — raw Markdown stays available at the .md URLs.");
16771
16787
  return;
@@ -17026,7 +17042,7 @@ var buildCommand = defineCommand3({
17026
17042
  logger.success(`Surfaced ${adapter} output to ${surfaced.to}`);
17027
17043
  }
17028
17044
  if (project.config.deployment.output === "server" && adapter === "vercel") {
17029
- await emitVercelNegotiation(project.config, project.manifest.routes.map((route) => route.path), root);
17045
+ await emitVercelNegotiation(project, markdownRoutePaths(project), root);
17030
17046
  }
17031
17047
  await publishBuildArtifacts(project, deployStaticDir(project.config, project.context), args);
17032
17048
  }
@@ -19873,5 +19889,5 @@ process.on("unhandledRejection", (error) => {
19873
19889
  });
19874
19890
  runMain(main);
19875
19891
 
19876
- //# debugId=CCE85D85BA5F467464756E2164756E21
19892
+ //# debugId=DE086E5CDF1627AA64756E2164756E21
19877
19893
  //# sourceMappingURL=index.js.map