nuxt-ai-ready 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/chunks/agent-skills.mjs +1 -1
  2. package/dist/chunks/prerender.mjs +35 -17
  3. package/dist/cli.mjs +259 -0
  4. package/dist/module.d.mts +1 -0
  5. package/dist/module.json +1 -1
  6. package/dist/module.mjs +1 -1
  7. package/dist/runtime/llms-txt-files.js +58 -3
  8. package/dist/runtime/llms-txt-format.js +5 -4
  9. package/dist/runtime/markdown-text.d.ts +2 -0
  10. package/dist/runtime/markdown-text.js +3 -0
  11. package/dist/runtime/server/db/drizzle/queries.d.ts +2 -6
  12. package/dist/runtime/server/db/drizzle/queries.js +30 -10
  13. package/dist/runtime/server/db/queries.d.ts +2 -6
  14. package/dist/runtime/server/db/queries.js +12 -7
  15. package/dist/runtime/server/db/schema/postgres.d.ts +30 -0
  16. package/dist/runtime/server/db/schema/postgres.js +1 -0
  17. package/dist/runtime/server/db/schema/sqlite.d.ts +32 -0
  18. package/dist/runtime/server/db/schema/sqlite.js +1 -0
  19. package/dist/runtime/server/db/schema-sql.d.ts +1 -1
  20. package/dist/runtime/server/db/schema-sql.js +2 -1
  21. package/dist/runtime/server/db/seed.d.ts +8 -0
  22. package/dist/runtime/server/db/seed.js +16 -0
  23. package/dist/runtime/server/db/shared.d.ts +3 -3
  24. package/dist/runtime/server/db/shared.js +19 -1
  25. package/dist/runtime/server/middleware/markdown.prerender.js +9 -1
  26. package/dist/runtime/server/plugins/content-signal-capture-nitro3.prerender.d.ts +2 -0
  27. package/dist/runtime/server/plugins/content-signal-capture-nitro3.prerender.js +13 -0
  28. package/dist/runtime/server/plugins/content-signal-capture.prerender.d.ts +2 -0
  29. package/dist/runtime/server/plugins/content-signal-capture.prerender.js +13 -0
  30. package/dist/runtime/server/plugins/sitemap-seeder.js +9 -1
  31. package/dist/runtime/server/routes/llms.txt.get.js +4 -2
  32. package/dist/runtime/server/utils/llms-full.js +31 -5
  33. package/dist/runtime/server/utils/negotiation-response.js +3 -0
  34. package/dist/runtime/server/utils/prerender-html.d.ts +3 -0
  35. package/dist/runtime/server/utils/prerender-html.js +12 -0
  36. package/dist/runtime/server/utils/runCron.js +1 -1
  37. package/dist/runtime/server/utils/sitemap-lastmod.d.ts +4 -0
  38. package/dist/runtime/server/utils/sitemap-lastmod.js +19 -0
  39. package/dist/runtime/server/utils/sitemap-md.js +3 -2
  40. package/dist/runtime/server/utils/sitemap.js +4 -2
  41. package/dist/runtime/server/utils.js +50 -2
  42. package/dist/shared/{nuxt-ai-ready.BwnSMqRn.mjs → nuxt-ai-ready.O6_9Egm8.mjs} +367 -246
  43. package/package.json +3 -3
@@ -563,6 +563,334 @@ async function readCloudflareAssetRouting(rootDir) {
563
563
  }
564
564
  }
565
565
 
566
+ function escapeRegExp(value) {
567
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
568
+ }
569
+ function ensureStaticHeader(contents, route, name, value, mode = "default") {
570
+ const eol = contents.includes("\r\n") ? "\r\n" : "\n";
571
+ const routePattern = new RegExp(`^${escapeRegExp(route)}[\\t ]*\\r?$`, "gm");
572
+ const routeMatches = [...contents.matchAll(routePattern)];
573
+ const routeMatch = routeMatches.at(-1);
574
+ if (!routeMatch || routeMatch.index === void 0) {
575
+ const separator = contents.length === 0 ? "" : contents.endsWith(`${eol}${eol}`) ? "" : contents.endsWith(eol) ? eol : `${eol}${eol}`;
576
+ return `${contents}${separator}${route}${eol} ${name}: ${value}${eol}`;
577
+ }
578
+ const routeLineEnd = routeMatch.index + routeMatch[0].length;
579
+ const blockStart = contents[routeLineEnd] === "\n" ? routeLineEnd + 1 : routeLineEnd;
580
+ const nextBlockPattern = /^(?![\t ]|\r?$).+/gm;
581
+ nextBlockPattern.lastIndex = blockStart;
582
+ const nextBlock = nextBlockPattern.exec(contents);
583
+ const blockEnd = nextBlock?.index ?? contents.length;
584
+ const block = contents.slice(blockStart, blockEnd);
585
+ const escapedName = escapeRegExp(name);
586
+ const existingHeaderPattern = new RegExp(
587
+ `^[\\t ]+(?:${escapedName}[\\t ]*:|![\\t ]*${escapedName}[\\t ]*\\r?$)`,
588
+ "im"
589
+ );
590
+ if (existingHeaderPattern.test(block)) {
591
+ if (mode === "append") {
592
+ const removalPattern = new RegExp(`^[\\t ]+![\\t ]*${escapedName}[\\t ]*\\r?$`, "im");
593
+ if (removalPattern.test(block))
594
+ return contents;
595
+ const valuePattern = new RegExp(`^([\\t ]+${escapedName}[\\t ]*:[\\t ]*)([^\\r\\n]*)(\\r?$)`, "im");
596
+ const match = block.match(valuePattern);
597
+ if (match) {
598
+ const merged = block.replace(valuePattern, (_line, prefix2, current, suffix) => {
599
+ const appended = name.toLowerCase() === "link" ? current.includes(value) ? current : [current, value].filter(Boolean).join(", ") : mergeVaryHeader(current, value);
600
+ return `${prefix2}${appended}${suffix}`;
601
+ });
602
+ return `${contents.slice(0, blockStart)}${merged}${contents.slice(blockEnd)}`;
603
+ }
604
+ }
605
+ return contents;
606
+ }
607
+ const prefix = blockStart === routeLineEnd ? eol : "";
608
+ return `${contents.slice(0, blockStart)}${prefix} ${name}: ${value}${eol}${contents.slice(blockStart)}`;
609
+ }
610
+ function mergeStaticDiscoveryHeaders(contents, baseURL, entries) {
611
+ if (entries.length === 0)
612
+ return contents;
613
+ const route = toDeployedRoute("/*", baseURL);
614
+ const blocks = splitHeaderBlocks(contents);
615
+ const matching = blocks.filter((block) => block.route === route);
616
+ let merged = matching[0]?.text || "";
617
+ for (const block of matching.slice(1)) {
618
+ for (const line of block.text.split(/(?<=\n)/).slice(1)) {
619
+ const header = line.match(/^[\t ]+([^:\s]+):[\t ]*([^\r\n]*)/);
620
+ if (header?.[1]?.toLowerCase() === "link") {
621
+ merged = ensureStaticHeader(merged, route, header[1], header[2], "append");
622
+ } else if (header) {
623
+ const existing = new RegExp(`^[\\t ]+${escapeRegExp(header[1])}[\\t ]*:[^\\r\\n]*(?:\\r?\\n|$)`, "gim");
624
+ merged = ensureStaticHeader(merged.replace(existing, ""), route, header[1], header[2]);
625
+ } else {
626
+ merged += line;
627
+ }
628
+ }
629
+ }
630
+ if (matching.length > 1) {
631
+ let inserted = false;
632
+ contents = blocks.map((block) => {
633
+ if (block.route !== route)
634
+ return block.text;
635
+ if (inserted)
636
+ return "";
637
+ inserted = true;
638
+ return merged;
639
+ }).join("");
640
+ }
641
+ return entries.reduce((headers, entry) => ensureStaticHeader(headers, route, "Link", entry, "append"), contents);
642
+ }
643
+ function parseHeaderLine(line) {
644
+ const colon = line.indexOf(":");
645
+ if (!/^[\t ]/.test(line) || colon < 0)
646
+ return void 0;
647
+ const name = line.slice(0, colon).trim();
648
+ if (!name || name.startsWith("!") || /\s/.test(name))
649
+ return void 0;
650
+ const eol = line.endsWith("\r\n") ? "\r\n" : line.endsWith("\n") ? "\n" : "";
651
+ return {
652
+ name,
653
+ value: line.slice(colon + 1, line.length - eol.length).trim(),
654
+ withValue: (value) => `${line.slice(0, colon + 1)} ${value}${eol}`
655
+ };
656
+ }
657
+ const RE_DETACH_LINE = /^[\t ]+![\t ]*([^\s:]+)/;
658
+ function patternToRegExp(pattern) {
659
+ const source = pattern.split(/(\*|:\w+)/).map((part) => part === "*" ? ".*" : part.startsWith(":") ? "[^/]+" : escapeRegExp(part)).join("");
660
+ return new RegExp(`^${source}$`);
661
+ }
662
+ function matchingStaticContentSignals(routeRules, route) {
663
+ return Object.entries(routeRules).flatMap(([pattern, rule]) => {
664
+ if (!patternToRegExp(pattern).test(route))
665
+ return [];
666
+ const headers = rule?.headers;
667
+ return Object.entries(headers || {}).filter(([name]) => name.toLowerCase() === "content-signal").map(([, value]) => value);
668
+ });
669
+ }
670
+ function normalizeStaticContentSignalHeaders(contents) {
671
+ const parsed = splitHeaderBlocks(contents);
672
+ if (!parsed.some((block) => /^\s+content-signal\s*:/im.test(block.text)))
673
+ return contents;
674
+ const policyLine = /^[\t ]+(?:content-signal\s*:.*|!\s*content-signal\s*)$/i;
675
+ const hasPolicy = (block) => block.text.split(/\r?\n/).slice(1).some((line) => policyLine.test(line));
676
+ const repeated = /* @__PURE__ */ new Map();
677
+ for (const block of parsed) {
678
+ if (block.route !== null)
679
+ repeated.set(block.route, [...repeated.get(block.route) || [], block]);
680
+ }
681
+ const combined = /* @__PURE__ */ new Set();
682
+ const blocks = parsed.flatMap((block) => {
683
+ const group = block.route === null ? [] : repeated.get(block.route);
684
+ if (group.length < 2 || !group.some(hasPolicy))
685
+ return [block];
686
+ if (combined.has(block.route))
687
+ return [];
688
+ combined.add(block.route);
689
+ const lines = group.at(-1).text.split(/\r?\n/).slice(1).filter((line) => !policyLine.test(line));
690
+ const policy = group.filter(hasPolicy).at(-1).text.split(/\r?\n/).slice(1).filter((line) => policyLine.test(line));
691
+ return [{ route: block.route, text: `${block.route}
692
+ ${[...lines, ...policy].join("\n")}
693
+ ` }];
694
+ });
695
+ const policyOnly = (block) => hasPolicy(block) && block.text.split(/\r?\n/).slice(1).every((line) => !line.trim() || line.trimStart().startsWith("#") || policyLine.test(line));
696
+ const policyValue = (block) => block.text.match(/^[\t ]+content-signal[\t ]*:(.*)$/im)?.[1]?.trim() ?? null;
697
+ const edges = blocks.map(() => /* @__PURE__ */ new Set());
698
+ const incoming = blocks.map(() => 0);
699
+ const connect = (before, after) => {
700
+ if (!edges[before].has(after)) {
701
+ edges[before].add(after);
702
+ incoming[after]++;
703
+ }
704
+ };
705
+ let previousUnrelated;
706
+ for (let index = 0; index < blocks.length; index++) {
707
+ if (!policyOnly(blocks[index])) {
708
+ if (previousUnrelated !== void 0)
709
+ connect(previousUnrelated, index);
710
+ previousUnrelated = index;
711
+ }
712
+ }
713
+ for (let broad = 0; broad < blocks.length; broad++) {
714
+ const source = blocks[broad];
715
+ if (!hasPolicy(source) || source.route === null)
716
+ continue;
717
+ for (let narrow = 0; narrow < blocks.length; narrow++) {
718
+ const target = blocks[narrow];
719
+ if (broad !== narrow && hasPolicy(target) && target.route !== null && policyValue(source) !== policyValue(target) && coversContentSignalPattern(source.route, target.route)) {
720
+ connect(broad, narrow);
721
+ }
722
+ }
723
+ }
724
+ const ordered = [];
725
+ const emitted = /* @__PURE__ */ new Set();
726
+ while (ordered.length < blocks.length) {
727
+ const next = incoming.findIndex((count, index) => count === 0 && !emitted.has(index));
728
+ if (next === -1)
729
+ throw new Error("Content-Signal rules conflict with other header ordering. Place broader mixed policy rules before narrower rules.");
730
+ emitted.add(next);
731
+ ordered.push(blocks[next]);
732
+ for (const target of edges[next])
733
+ incoming[target]--;
734
+ }
735
+ return ordered.map((block) => {
736
+ const text = block.text.trimEnd();
737
+ if (!/^\s+content-signal\s*:/im.test(text) || /^\s+!\s*content-signal\s*$/im.test(text))
738
+ return `${text}
739
+ `;
740
+ return `${text.replace(/^(\s+)(content-signal\s*:)/im, "$1! Content-Signal\n$1$2")}
741
+ `;
742
+ }).join("\n");
743
+ }
744
+ function coversPattern(broad, narrow) {
745
+ if (broad === narrow || !broad.startsWith("/") || !narrow.startsWith("/"))
746
+ return false;
747
+ if (!/[*:]/.test(narrow))
748
+ return patternToRegExp(broad).test(narrow);
749
+ return broad.indexOf("*") === broad.length - 1 && !broad.includes(":") && narrow.startsWith(broad.slice(0, -1));
750
+ }
751
+ function coversContentSignalPattern(broad, narrow) {
752
+ if (coversPattern(broad, narrow))
753
+ return true;
754
+ if (broad === narrow || !broad.startsWith("/") || !narrow.startsWith("/") || broad.indexOf("*") !== broad.length - 1 || !broad.includes(":")) {
755
+ return false;
756
+ }
757
+ const prefix = narrow.split("*")[0];
758
+ return !prefix.includes(":") && patternToRegExp(broad).test(prefix);
759
+ }
760
+ function splitLinkEntries(value) {
761
+ const entries = [];
762
+ let current = "";
763
+ let inUrl = false;
764
+ let inQuote = false;
765
+ for (const char of value) {
766
+ if (char === "<" && !inQuote)
767
+ inUrl = true;
768
+ else if (char === ">" && !inQuote)
769
+ inUrl = false;
770
+ else if (char === '"' && !inUrl)
771
+ inQuote = !inQuote;
772
+ if (char === "," && !inUrl && !inQuote) {
773
+ entries.push(current.trim());
774
+ current = "";
775
+ continue;
776
+ }
777
+ current += char;
778
+ }
779
+ entries.push(current.trim());
780
+ return entries.filter(Boolean);
781
+ }
782
+ function headerTokens(name, value) {
783
+ if (name === "link")
784
+ return splitLinkEntries(value);
785
+ if (name === "vary")
786
+ return value.split(",").map((token) => token.trim()).filter(Boolean);
787
+ return [value.trim()];
788
+ }
789
+ function tokenKey(name, token) {
790
+ return name === "vary" ? token.toLowerCase() : token;
791
+ }
792
+ function dedupeStaticHeaders(contents, names) {
793
+ const owned = new Set(names.map((name) => name.toLowerCase()));
794
+ const blocks = splitHeaderBlocks(contents);
795
+ const detached = /* @__PURE__ */ new Set();
796
+ const parsed = blocks.map((block) => {
797
+ const headers = [];
798
+ for (const line of block.text.split(/(?<=\n)/).slice(block.route === null ? 0 : 1)) {
799
+ const detach = line.match(RE_DETACH_LINE);
800
+ if (detach) {
801
+ detached.add(detach[1].toLowerCase());
802
+ continue;
803
+ }
804
+ const header = parseHeaderLine(line);
805
+ if (!header)
806
+ continue;
807
+ const name = header.name.toLowerCase();
808
+ if (owned.has(name))
809
+ headers.push({ name, tokens: headerTokens(name, header.value) });
810
+ }
811
+ return headers;
812
+ });
813
+ const lastRuleIndex = /* @__PURE__ */ new Map();
814
+ blocks.forEach((block, index) => {
815
+ if (block.route !== null)
816
+ lastRuleIndex.set(block.route, index);
817
+ });
818
+ let changed = false;
819
+ const output = blocks.map((block) => {
820
+ const route = block.route;
821
+ if (route === null)
822
+ return block.text;
823
+ const provided = /* @__PURE__ */ new Map();
824
+ lastRuleIndex.forEach((index, pattern) => {
825
+ if (!coversPattern(pattern, route))
826
+ return;
827
+ for (const { name, tokens } of parsed[index]) {
828
+ if (detached.has(name))
829
+ continue;
830
+ const values = provided.get(name) ?? /* @__PURE__ */ new Set();
831
+ tokens.forEach((token) => values.add(tokenKey(name, token)));
832
+ provided.set(name, values);
833
+ }
834
+ });
835
+ if (provided.size === 0)
836
+ return block.text;
837
+ const [routeLine, ...lines] = block.text.split(/(?<=\n)/);
838
+ let headerLines = 0;
839
+ const kept = lines.flatMap((line) => {
840
+ const header = parseHeaderLine(line);
841
+ if (!header) {
842
+ if (RE_DETACH_LINE.test(line))
843
+ headerLines++;
844
+ return [line];
845
+ }
846
+ headerLines++;
847
+ const name = header.name.toLowerCase();
848
+ const covered = provided.get(name);
849
+ if (!covered)
850
+ return [line];
851
+ const tokens = headerTokens(name, header.value);
852
+ const remaining = tokens.filter((token) => !covered.has(tokenKey(name, token)));
853
+ if (remaining.length === tokens.length)
854
+ return [line];
855
+ changed = true;
856
+ if (remaining.length > 0)
857
+ return [header.withValue(remaining.join(", "))];
858
+ headerLines--;
859
+ return [];
860
+ });
861
+ return headerLines === 0 ? kept.join("") : `${routeLine}${kept.join("")}`;
862
+ });
863
+ return changed ? output.join("") : contents;
864
+ }
865
+ const CLOUDFLARE_STATIC_HEADER_RULE_LIMIT = 100;
866
+ const RE_EXACT_MARKDOWN_ROUTE$1 = /^\/[^*\s]*\.md[\t ]*\r?$/;
867
+ function splitHeaderBlocks(contents) {
868
+ const blocks = [];
869
+ let current = { route: null, text: "" };
870
+ for (const line of contents.split(/(?<=\n)/)) {
871
+ const isRoute = /^[^\s#]/.test(line);
872
+ if (isRoute) {
873
+ blocks.push(current);
874
+ current = { route: line.trimEnd(), text: "" };
875
+ }
876
+ current.text += line;
877
+ }
878
+ blocks.push(current);
879
+ return blocks;
880
+ }
881
+ function enforceStaticHeaderBudget(contents, limit) {
882
+ const blocks = splitHeaderBlocks(contents);
883
+ const ruleCount = blocks.filter((block) => block.route !== null).length;
884
+ if (ruleCount <= limit)
885
+ return { contents, total: ruleCount, dropped: 0 };
886
+ const kept = blocks.filter((block) => block.route === null || !RE_EXACT_MARKDOWN_ROUTE$1.test(block.route) || /^\s+(?:content-signal\s*:|!\s*content-signal\s*$)/im.test(block.text));
887
+ return {
888
+ contents: kept.map((block) => block.text).join(""),
889
+ total: kept.filter((block) => block.route !== null).length,
890
+ dropped: ruleCount - kept.filter((block) => block.route !== null).length
891
+ };
892
+ }
893
+
566
894
  const RE_MD_EXT = /\.md$/;
567
895
  function isStaticMarkdownSourceRoute(route) {
568
896
  if (route.includes("*") || route.includes(":"))
@@ -607,41 +935,68 @@ function prerenderedMarkdownHeaderRules(prerenderedRoutes, baseURL, describedby,
607
935
  headers: {
608
936
  "Content-Type": "text/markdown; charset=utf-8",
609
937
  "Vary": "Accept",
610
- "Link": buildStaticMarkdownLinkHeader(pageRoute, baseURL, describedby)
938
+ "Link": buildStaticMarkdownLinkHeader(pageRoute, baseURL, describedby),
939
+ ...entry.contentSignal !== void 0 && { "Content-Signal": entry.contentSignal }
611
940
  }
612
941
  });
613
942
  }
614
943
  }
615
944
  return [...rules.values()];
616
945
  }
617
- const RE_EXACT_MARKDOWN_ROUTE$1 = /^\/[^*:\s]*\.md$/;
946
+ const RE_EXACT_MARKDOWN_ROUTE = /^\/[^*:\s]*\.md$/;
618
947
  function isStaticHeaderRouteRule([, rule]) {
619
948
  return Boolean(rule && "headers" in rule && rule.headers);
620
949
  }
950
+ function hasContentSignal$1(headers) {
951
+ return Object.keys(headers || {}).some((name) => name.toLowerCase() === "content-signal");
952
+ }
621
953
  function planStaticMarkdownHeaderRules(routeRules, rules, limit) {
954
+ rules = rules.map((rule) => {
955
+ if (!hasContentSignal$1(rule.headers) || matchingStaticContentSignals(routeRules, rule.route).length === 0)
956
+ return rule;
957
+ return { ...rule, headers: Object.fromEntries(Object.entries(rule.headers).filter(([name]) => name.toLowerCase() !== "content-signal")) };
958
+ });
622
959
  if (limit === null)
623
960
  return { _tag: "apply", rules };
624
961
  const entries = Object.entries(routeRules).filter(isStaticHeaderRouteRule);
625
- const registeredMarkdown = entries.map(([route]) => route).filter((route) => RE_EXACT_MARKDOWN_ROUTE$1.test(route));
962
+ const registeredMarkdown = entries.map(([route]) => route).filter((route) => RE_EXACT_MARKDOWN_ROUTE.test(route));
626
963
  const other = entries.length - registeredMarkdown.length;
627
964
  const markdown = /* @__PURE__ */ new Set([...registeredMarkdown, ...rules.map((rule) => rule.route)]);
628
965
  const total = other + markdown.size;
629
966
  if (total <= limit)
630
967
  return { _tag: "apply", rules };
631
- return { _tag: "skip", drop: registeredMarkdown, total, dropped: markdown.size };
968
+ const policyRules = rules.filter((rule) => hasContentSignal$1(rule.headers));
969
+ const policyRoutes = /* @__PURE__ */ new Set([
970
+ ...policyRules.map((rule) => rule.route),
971
+ ...registeredMarkdown.filter((route) => hasContentSignal$1(routeRules[route].headers))
972
+ ]);
973
+ if (other + policyRoutes.size > limit)
974
+ return { _tag: "blocked", total: other + policyRoutes.size, limit };
975
+ return { _tag: "skip", drop: registeredMarkdown.filter((route) => !policyRoutes.has(route)), total, dropped: markdown.size - policyRoutes.size, rules: policyRules };
632
976
  }
633
977
  function applyStaticMarkdownHeaderPlan(routeRules, plan) {
978
+ if (plan._tag === "blocked")
979
+ throw new Error(`Content-Signal policy rules exceed the static header limit (${plan.total}/${plan.limit}). Reduce per-page policies or use a shared policy.`);
634
980
  if (plan._tag === "skip") {
635
981
  for (const route of plan.drop) {
636
982
  const rule = routeRules[route];
637
983
  if (rule)
638
984
  rule.headers = void 0;
639
985
  }
640
- return;
641
986
  }
642
987
  for (const { route, headers } of plan.rules) {
643
988
  const existing = routeRules[route];
644
989
  const merged = defu({ headers: { ...headers } }, existing);
990
+ if (hasContentSignal$1(existing?.headers)) {
991
+ for (const name of Object.keys(merged.headers)) {
992
+ if (name.toLowerCase() === "content-signal")
993
+ delete merged.headers[name];
994
+ }
995
+ for (const [name, value] of Object.entries(existing.headers)) {
996
+ if (name.toLowerCase() === "content-signal")
997
+ merged.headers[name] = value;
998
+ }
999
+ }
645
1000
  const vary = Object.entries(existing?.headers || {}).filter(([name]) => name.toLowerCase() === "vary").map(([, value]) => value).join(", ");
646
1001
  if (headers.Vary) {
647
1002
  for (const name of Object.keys(merged.headers)) {
@@ -1256,243 +1611,6 @@ function opaqueJsonModule(exports) {
1256
1611
  ].join("\n");
1257
1612
  }
1258
1613
 
1259
- function escapeRegExp(value) {
1260
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1261
- }
1262
- function ensureStaticHeader(contents, route, name, value, mode = "default") {
1263
- const eol = contents.includes("\r\n") ? "\r\n" : "\n";
1264
- const routePattern = new RegExp(`^${escapeRegExp(route)}[\\t ]*\\r?$`, "gm");
1265
- const routeMatches = [...contents.matchAll(routePattern)];
1266
- const routeMatch = routeMatches.at(-1);
1267
- if (!routeMatch || routeMatch.index === void 0) {
1268
- const separator = contents.length === 0 ? "" : contents.endsWith(`${eol}${eol}`) ? "" : contents.endsWith(eol) ? eol : `${eol}${eol}`;
1269
- return `${contents}${separator}${route}${eol} ${name}: ${value}${eol}`;
1270
- }
1271
- const routeLineEnd = routeMatch.index + routeMatch[0].length;
1272
- const blockStart = contents[routeLineEnd] === "\n" ? routeLineEnd + 1 : routeLineEnd;
1273
- const nextBlockPattern = /^(?![\t ]|\r?$).+/gm;
1274
- nextBlockPattern.lastIndex = blockStart;
1275
- const nextBlock = nextBlockPattern.exec(contents);
1276
- const blockEnd = nextBlock?.index ?? contents.length;
1277
- const block = contents.slice(blockStart, blockEnd);
1278
- const escapedName = escapeRegExp(name);
1279
- const existingHeaderPattern = new RegExp(
1280
- `^[\\t ]+(?:${escapedName}[\\t ]*:|![\\t ]*${escapedName}[\\t ]*\\r?$)`,
1281
- "im"
1282
- );
1283
- if (existingHeaderPattern.test(block)) {
1284
- if (mode === "append") {
1285
- const removalPattern = new RegExp(`^[\\t ]+![\\t ]*${escapedName}[\\t ]*\\r?$`, "im");
1286
- if (removalPattern.test(block))
1287
- return contents;
1288
- const valuePattern = new RegExp(`^([\\t ]+${escapedName}[\\t ]*:[\\t ]*)([^\\r\\n]*)(\\r?$)`, "im");
1289
- const match = block.match(valuePattern);
1290
- if (match) {
1291
- const merged = block.replace(valuePattern, (_line, prefix2, current, suffix) => {
1292
- const appended = name.toLowerCase() === "link" ? current.includes(value) ? current : [current, value].filter(Boolean).join(", ") : mergeVaryHeader(current, value);
1293
- return `${prefix2}${appended}${suffix}`;
1294
- });
1295
- return `${contents.slice(0, blockStart)}${merged}${contents.slice(blockEnd)}`;
1296
- }
1297
- }
1298
- return contents;
1299
- }
1300
- const prefix = blockStart === routeLineEnd ? eol : "";
1301
- return `${contents.slice(0, blockStart)}${prefix} ${name}: ${value}${eol}${contents.slice(blockStart)}`;
1302
- }
1303
- function mergeStaticDiscoveryHeaders(contents, baseURL, entries) {
1304
- if (entries.length === 0)
1305
- return contents;
1306
- const route = toDeployedRoute("/*", baseURL);
1307
- const blocks = splitHeaderBlocks(contents);
1308
- const matching = blocks.filter((block) => block.route === route);
1309
- let merged = matching[0]?.text || "";
1310
- for (const block of matching.slice(1)) {
1311
- for (const line of block.text.split(/(?<=\n)/).slice(1)) {
1312
- const header = line.match(/^[\t ]+([^:\s]+):[\t ]*([^\r\n]*)/);
1313
- if (header?.[1]?.toLowerCase() === "link") {
1314
- merged = ensureStaticHeader(merged, route, header[1], header[2], "append");
1315
- } else if (header) {
1316
- const existing = new RegExp(`^[\\t ]+${escapeRegExp(header[1])}[\\t ]*:[^\\r\\n]*(?:\\r?\\n|$)`, "gim");
1317
- merged = ensureStaticHeader(merged.replace(existing, ""), route, header[1], header[2]);
1318
- } else {
1319
- merged += line;
1320
- }
1321
- }
1322
- }
1323
- if (matching.length > 1) {
1324
- let inserted = false;
1325
- contents = blocks.map((block) => {
1326
- if (block.route !== route)
1327
- return block.text;
1328
- if (inserted)
1329
- return "";
1330
- inserted = true;
1331
- return merged;
1332
- }).join("");
1333
- }
1334
- return entries.reduce((headers, entry) => ensureStaticHeader(headers, route, "Link", entry, "append"), contents);
1335
- }
1336
- function parseHeaderLine(line) {
1337
- const colon = line.indexOf(":");
1338
- if (!/^[\t ]/.test(line) || colon < 0)
1339
- return void 0;
1340
- const name = line.slice(0, colon).trim();
1341
- if (!name || name.startsWith("!") || /\s/.test(name))
1342
- return void 0;
1343
- const eol = line.endsWith("\r\n") ? "\r\n" : line.endsWith("\n") ? "\n" : "";
1344
- return {
1345
- name,
1346
- value: line.slice(colon + 1, line.length - eol.length).trim(),
1347
- withValue: (value) => `${line.slice(0, colon + 1)} ${value}${eol}`
1348
- };
1349
- }
1350
- const RE_DETACH_LINE = /^[\t ]+![\t ]*([^\s:]+)/;
1351
- function patternToRegExp(pattern) {
1352
- const source = pattern.split(/(\*|:\w+)/).map((part) => part === "*" ? ".*" : part.startsWith(":") ? "[^/]+" : escapeRegExp(part)).join("");
1353
- return new RegExp(`^${source}$`);
1354
- }
1355
- function coversPattern(broad, narrow) {
1356
- if (broad === narrow || !broad.startsWith("/") || !narrow.startsWith("/"))
1357
- return false;
1358
- if (!/[*:]/.test(narrow))
1359
- return patternToRegExp(broad).test(narrow);
1360
- return broad.indexOf("*") === broad.length - 1 && !broad.includes(":") && narrow.startsWith(broad.slice(0, -1));
1361
- }
1362
- function splitLinkEntries(value) {
1363
- const entries = [];
1364
- let current = "";
1365
- let inUrl = false;
1366
- let inQuote = false;
1367
- for (const char of value) {
1368
- if (char === "<" && !inQuote)
1369
- inUrl = true;
1370
- else if (char === ">" && !inQuote)
1371
- inUrl = false;
1372
- else if (char === '"' && !inUrl)
1373
- inQuote = !inQuote;
1374
- if (char === "," && !inUrl && !inQuote) {
1375
- entries.push(current.trim());
1376
- current = "";
1377
- continue;
1378
- }
1379
- current += char;
1380
- }
1381
- entries.push(current.trim());
1382
- return entries.filter(Boolean);
1383
- }
1384
- function headerTokens(name, value) {
1385
- if (name === "link")
1386
- return splitLinkEntries(value);
1387
- if (name === "vary")
1388
- return value.split(",").map((token) => token.trim()).filter(Boolean);
1389
- return [value.trim()];
1390
- }
1391
- function tokenKey(name, token) {
1392
- return name === "vary" ? token.toLowerCase() : token;
1393
- }
1394
- function dedupeStaticHeaders(contents, names) {
1395
- const owned = new Set(names.map((name) => name.toLowerCase()));
1396
- const blocks = splitHeaderBlocks(contents);
1397
- const detached = /* @__PURE__ */ new Set();
1398
- const parsed = blocks.map((block) => {
1399
- const headers = [];
1400
- for (const line of block.text.split(/(?<=\n)/).slice(block.route === null ? 0 : 1)) {
1401
- const detach = line.match(RE_DETACH_LINE);
1402
- if (detach) {
1403
- detached.add(detach[1].toLowerCase());
1404
- continue;
1405
- }
1406
- const header = parseHeaderLine(line);
1407
- if (!header)
1408
- continue;
1409
- const name = header.name.toLowerCase();
1410
- if (owned.has(name))
1411
- headers.push({ name, tokens: headerTokens(name, header.value) });
1412
- }
1413
- return headers;
1414
- });
1415
- const lastRuleIndex = /* @__PURE__ */ new Map();
1416
- blocks.forEach((block, index) => {
1417
- if (block.route !== null)
1418
- lastRuleIndex.set(block.route, index);
1419
- });
1420
- let changed = false;
1421
- const output = blocks.map((block) => {
1422
- const route = block.route;
1423
- if (route === null)
1424
- return block.text;
1425
- const provided = /* @__PURE__ */ new Map();
1426
- lastRuleIndex.forEach((index, pattern) => {
1427
- if (!coversPattern(pattern, route))
1428
- return;
1429
- for (const { name, tokens } of parsed[index]) {
1430
- if (detached.has(name))
1431
- continue;
1432
- const values = provided.get(name) ?? /* @__PURE__ */ new Set();
1433
- tokens.forEach((token) => values.add(tokenKey(name, token)));
1434
- provided.set(name, values);
1435
- }
1436
- });
1437
- if (provided.size === 0)
1438
- return block.text;
1439
- const [routeLine, ...lines] = block.text.split(/(?<=\n)/);
1440
- let headerLines = 0;
1441
- const kept = lines.flatMap((line) => {
1442
- const header = parseHeaderLine(line);
1443
- if (!header) {
1444
- if (RE_DETACH_LINE.test(line))
1445
- headerLines++;
1446
- return [line];
1447
- }
1448
- headerLines++;
1449
- const name = header.name.toLowerCase();
1450
- const covered = provided.get(name);
1451
- if (!covered)
1452
- return [line];
1453
- const tokens = headerTokens(name, header.value);
1454
- const remaining = tokens.filter((token) => !covered.has(tokenKey(name, token)));
1455
- if (remaining.length === tokens.length)
1456
- return [line];
1457
- changed = true;
1458
- if (remaining.length > 0)
1459
- return [header.withValue(remaining.join(", "))];
1460
- headerLines--;
1461
- return [];
1462
- });
1463
- return headerLines === 0 ? kept.join("") : `${routeLine}${kept.join("")}`;
1464
- });
1465
- return changed ? output.join("") : contents;
1466
- }
1467
- const CLOUDFLARE_STATIC_HEADER_RULE_LIMIT = 100;
1468
- const RE_EXACT_MARKDOWN_ROUTE = /^\/[^*\s]*\.md[\t ]*\r?$/;
1469
- function splitHeaderBlocks(contents) {
1470
- const blocks = [];
1471
- let current = { route: null, text: "" };
1472
- for (const line of contents.split(/(?<=\n)/)) {
1473
- const isRoute = /^[^\s#]/.test(line);
1474
- if (isRoute) {
1475
- blocks.push(current);
1476
- current = { route: line.trimEnd(), text: "" };
1477
- }
1478
- current.text += line;
1479
- }
1480
- blocks.push(current);
1481
- return blocks;
1482
- }
1483
- function enforceStaticHeaderBudget(contents, limit) {
1484
- const blocks = splitHeaderBlocks(contents);
1485
- const ruleCount = blocks.filter((block) => block.route !== null).length;
1486
- if (ruleCount <= limit)
1487
- return { contents, total: ruleCount, dropped: 0 };
1488
- const kept = blocks.filter((block) => block.route === null || !RE_EXACT_MARKDOWN_ROUTE.test(block.route));
1489
- return {
1490
- contents: kept.map((block) => block.text).join(""),
1491
- total: kept.filter((block) => block.route !== null).length,
1492
- dropped: ruleCount - kept.filter((block) => block.route !== null).length
1493
- };
1494
- }
1495
-
1496
1614
  const DEFAULT_MAX_OUTPUT_CHARS = 1500;
1497
1615
  const DEFAULT_LIST_LIMIT = 20;
1498
1616
  const DEFAULT_SEARCH_LIMIT = 10;
@@ -2335,9 +2453,10 @@ export function trackDrizzleWork(event, work) { return work }
2335
2453
  handler: resolve$1("./runtime/server/middleware/markdown.prerender")
2336
2454
  });
2337
2455
  const htmlCapturePlugin = resolve$1("./runtime/server/plugins/html-capture.prerender");
2456
+ const policyCapturePlugin = resolve$1(getNitroVersion(nuxt) === 3 ? "./runtime/server/plugins/content-signal-capture-nitro3.prerender" : "./runtime/server/plugins/content-signal-capture.prerender");
2338
2457
  nuxt.hook("nitro:init", (nitro) => {
2339
2458
  nitro.hooks.hook("prerender:config", (prerenderConfig) => {
2340
- prerenderConfig.plugins = [...prerenderConfig.plugins || [], htmlCapturePlugin];
2459
+ prerenderConfig.plugins = [...prerenderConfig.plugins || [], htmlCapturePlugin, policyCapturePlugin];
2341
2460
  });
2342
2461
  });
2343
2462
  addPlugin({
@@ -2361,8 +2480,7 @@ export function trackDrizzleWork(event, work) { return work }
2361
2480
  src: resolve$1("./runtime/app/plugins/md-alternate.server")
2362
2481
  });
2363
2482
  if (generateLlmsTxt) {
2364
- for (const route of ["/llms.txt", "/llms/**"])
2365
- addServerHandler({ route, method: void 0, handler: resolve$1("./runtime/server/routes/llms.txt.get"), lazy: true });
2483
+ addServerHandler({ middleware: true, method: void 0, handler: resolve$1("./runtime/server/routes/llms.txt.get"), lazy: true });
2366
2484
  }
2367
2485
  if (config.llmsFullTxt !== false)
2368
2486
  addServerHandler({ route: "/llms-full.txt", method: void 0, handler: resolve$1("./runtime/server/routes/llms-full.txt.get"), lazy: true });
@@ -2599,11 +2717,14 @@ export function trackDrizzleWork(event, work) { return work }
2599
2717
  discovery.push(`<${encodeURI(apiCatalogConfig.href)}>; rel="api-catalog"`);
2600
2718
  mergedHeaders = mergeStaticDiscoveryHeaders(mergedHeaders, staticBaseURL, discovery);
2601
2719
  mergedHeaders = dedupeStaticHeaders(mergedHeaders, ["Content-Type", "Link", "Vary"]);
2720
+ mergedHeaders = normalizeStaticContentSignalHeaders(mergedHeaders);
2602
2721
  const budget = enforceStaticHeaderBudget(mergedHeaders, CLOUDFLARE_STATIC_HEADER_RULE_LIMIT);
2603
2722
  if (budget.dropped > 0) {
2604
2723
  mergedHeaders = budget.contents;
2605
2724
  noteSharedMarkdownRule(budget.total + budget.dropped, budget.dropped);
2606
2725
  }
2726
+ if (budget.total > CLOUDFLARE_STATIC_HEADER_RULE_LIMIT && /^\s+(?:content-signal\s*:|!\s*content-signal\s*$)/im.test(budget.contents))
2727
+ throw new Error(`Content-Signal policy rules exceed Cloudflare's ${CLOUDFLARE_STATIC_HEADER_RULE_LIMIT}-rule limit. Reduce per-page policies or use a shared policy.`);
2607
2728
  if (budget.total > CLOUDFLARE_STATIC_HEADER_RULE_LIMIT)
2608
2729
  logger.warn(`_headers still has ${budget.total} rules after dropping the .md rules. Cloudflare will reject the deploy until the other rules fit under ${CLOUDFLARE_STATIC_HEADER_RULE_LIMIT}.`);
2609
2730
  }
@@ -2617,4 +2738,4 @@ export function trackDrizzleWork(event, work) { return work }
2617
2738
  }
2618
2739
  });
2619
2740
 
2620
- export { AGENT_SKILLS_SCHEMA as A, MARKDOWN_LINK_AVAILABILITY_FILE as M, logger as l, module$1 as m };
2741
+ export { AGENT_SKILLS_SCHEMA as A, MARKDOWN_LINK_AVAILABILITY_FILE as M, module$1 as a, logger as l, matchingStaticContentSignals as m };