aeo.js 0.0.2 → 0.0.4

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 (70) hide show
  1. package/README.md +132 -17
  2. package/dist/angular.d.mts +29 -0
  3. package/dist/angular.d.ts +29 -0
  4. package/dist/angular.js +1314 -0
  5. package/dist/angular.js.map +1 -0
  6. package/dist/angular.mjs +1310 -0
  7. package/dist/angular.mjs.map +1 -0
  8. package/dist/astro.d.mts +8 -2
  9. package/dist/astro.d.ts +8 -2
  10. package/dist/astro.js +400 -100
  11. package/dist/astro.js.map +1 -1
  12. package/dist/astro.mjs +400 -100
  13. package/dist/astro.mjs.map +1 -1
  14. package/dist/cli.d.mts +1 -0
  15. package/dist/cli.d.ts +1 -0
  16. package/dist/cli.js +1880 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/cli.mjs +1878 -0
  19. package/dist/cli.mjs.map +1 -0
  20. package/dist/index.d.mts +183 -4
  21. package/dist/index.d.ts +183 -4
  22. package/dist/index.js +974 -19
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +952 -20
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/next.d.mts +2 -17
  27. package/dist/next.d.ts +2 -17
  28. package/dist/next.js +262 -73
  29. package/dist/next.js.map +1 -1
  30. package/dist/next.mjs +262 -73
  31. package/dist/next.mjs.map +1 -1
  32. package/dist/nuxt.d.mts +13 -0
  33. package/dist/nuxt.d.ts +13 -0
  34. package/dist/nuxt.js +1344 -0
  35. package/dist/nuxt.js.map +1 -0
  36. package/dist/nuxt.mjs +1337 -0
  37. package/dist/nuxt.mjs.map +1 -0
  38. package/dist/react.d.mts +1 -1
  39. package/dist/react.d.ts +1 -1
  40. package/dist/react.js +330 -4
  41. package/dist/react.js.map +1 -1
  42. package/dist/react.mjs +330 -4
  43. package/dist/react.mjs.map +1 -1
  44. package/dist/{types-BTY-v-7i.d.mts → types-Cn_Qbkmg.d.mts} +34 -0
  45. package/dist/{types-BTY-v-7i.d.ts → types-Cn_Qbkmg.d.ts} +34 -0
  46. package/dist/vite.d.mts +5 -0
  47. package/dist/vite.d.ts +5 -0
  48. package/dist/vite.js +1370 -0
  49. package/dist/vite.js.map +1 -0
  50. package/dist/vite.mjs +1366 -0
  51. package/dist/vite.mjs.map +1 -0
  52. package/dist/vue.d.mts +19 -0
  53. package/dist/vue.d.ts +19 -0
  54. package/dist/vue.js +1404 -0
  55. package/dist/vue.js.map +1 -0
  56. package/dist/vue.mjs +1398 -0
  57. package/dist/vue.mjs.map +1 -0
  58. package/dist/webpack.d.mts +1 -1
  59. package/dist/webpack.d.ts +1 -1
  60. package/dist/webpack.js +178 -18
  61. package/dist/webpack.js.map +1 -1
  62. package/dist/webpack.mjs +178 -18
  63. package/dist/webpack.mjs.map +1 -1
  64. package/dist/widget.d.mts +11 -1
  65. package/dist/widget.d.ts +11 -1
  66. package/dist/widget.js +330 -4
  67. package/dist/widget.js.map +1 -1
  68. package/dist/widget.mjs +330 -4
  69. package/dist/widget.mjs.map +1 -1
  70. package/package.json +48 -2
package/dist/astro.js CHANGED
@@ -166,7 +166,7 @@ function detectFramework(projectRoot = process.cwd()) {
166
166
  };
167
167
  }
168
168
  function resolveConfig(config = {}) {
169
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
169
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M;
170
170
  const frameworkInfo = detectFramework();
171
171
  return {
172
172
  title: config.title || "My Site",
@@ -182,26 +182,43 @@ function resolveConfig(config = {}) {
182
182
  rawMarkdown: ((_d = config.generators) == null ? void 0 : _d.rawMarkdown) !== false,
183
183
  manifest: ((_e = config.generators) == null ? void 0 : _e.manifest) !== false,
184
184
  sitemap: ((_f = config.generators) == null ? void 0 : _f.sitemap) !== false,
185
- aiIndex: ((_g = config.generators) == null ? void 0 : _g.aiIndex) !== false
185
+ aiIndex: ((_g = config.generators) == null ? void 0 : _g.aiIndex) !== false,
186
+ schema: ((_h = config.generators) == null ? void 0 : _h.schema) !== false
186
187
  },
187
188
  robots: {
188
- allow: ((_h = config.robots) == null ? void 0 : _h.allow) || ["/"],
189
- disallow: ((_i = config.robots) == null ? void 0 : _i.disallow) || [],
190
- crawlDelay: ((_j = config.robots) == null ? void 0 : _j.crawlDelay) || 0,
191
- sitemap: ((_k = config.robots) == null ? void 0 : _k.sitemap) || ""
189
+ allow: ((_i = config.robots) == null ? void 0 : _i.allow) || ["/"],
190
+ disallow: ((_j = config.robots) == null ? void 0 : _j.disallow) || [],
191
+ crawlDelay: ((_k = config.robots) == null ? void 0 : _k.crawlDelay) || 0,
192
+ sitemap: ((_l = config.robots) == null ? void 0 : _l.sitemap) || ""
193
+ },
194
+ schema: {
195
+ enabled: ((_m = config.schema) == null ? void 0 : _m.enabled) !== false,
196
+ organization: {
197
+ name: ((_o = (_n = config.schema) == null ? void 0 : _n.organization) == null ? void 0 : _o.name) || config.title || "My Site",
198
+ url: ((_q = (_p = config.schema) == null ? void 0 : _p.organization) == null ? void 0 : _q.url) || config.url || "https://example.com",
199
+ logo: ((_s = (_r = config.schema) == null ? void 0 : _r.organization) == null ? void 0 : _s.logo) || "",
200
+ sameAs: ((_u = (_t = config.schema) == null ? void 0 : _t.organization) == null ? void 0 : _u.sameAs) || []
201
+ },
202
+ defaultType: ((_v = config.schema) == null ? void 0 : _v.defaultType) || "WebPage"
203
+ },
204
+ og: {
205
+ enabled: ((_w = config.og) == null ? void 0 : _w.enabled) !== false,
206
+ image: ((_x = config.og) == null ? void 0 : _x.image) || "",
207
+ twitterHandle: ((_y = config.og) == null ? void 0 : _y.twitterHandle) || "",
208
+ type: ((_z = config.og) == null ? void 0 : _z.type) || "website"
192
209
  },
193
210
  widget: {
194
- enabled: ((_l = config.widget) == null ? void 0 : _l.enabled) !== false,
195
- position: ((_m = config.widget) == null ? void 0 : _m.position) || "bottom-right",
211
+ enabled: ((_A = config.widget) == null ? void 0 : _A.enabled) !== false,
212
+ position: ((_B = config.widget) == null ? void 0 : _B.position) || "bottom-right",
196
213
  theme: {
197
- background: ((_o = (_n = config.widget) == null ? void 0 : _n.theme) == null ? void 0 : _o.background) || "rgba(18, 18, 24, 0.9)",
198
- text: ((_q = (_p = config.widget) == null ? void 0 : _p.theme) == null ? void 0 : _q.text) || "#C0C0C5",
199
- accent: ((_s = (_r = config.widget) == null ? void 0 : _r.theme) == null ? void 0 : _s.accent) || "#E8E8EA",
200
- badge: ((_u = (_t = config.widget) == null ? void 0 : _t.theme) == null ? void 0 : _u.badge) || "#4ADE80"
214
+ background: ((_D = (_C = config.widget) == null ? void 0 : _C.theme) == null ? void 0 : _D.background) || "rgba(18, 18, 24, 0.9)",
215
+ text: ((_F = (_E = config.widget) == null ? void 0 : _E.theme) == null ? void 0 : _F.text) || "#C0C0C5",
216
+ accent: ((_H = (_G = config.widget) == null ? void 0 : _G.theme) == null ? void 0 : _H.accent) || "#E8E8EA",
217
+ badge: ((_J = (_I = config.widget) == null ? void 0 : _I.theme) == null ? void 0 : _J.badge) || "#4ADE80"
201
218
  },
202
- humanLabel: ((_v = config.widget) == null ? void 0 : _v.humanLabel) || "Human",
203
- aiLabel: ((_w = config.widget) == null ? void 0 : _w.aiLabel) || "AI",
204
- showBadge: ((_x = config.widget) == null ? void 0 : _x.showBadge) !== false
219
+ humanLabel: ((_K = config.widget) == null ? void 0 : _K.humanLabel) || "Human",
220
+ aiLabel: ((_L = config.widget) == null ? void 0 : _L.aiLabel) || "AI",
221
+ showBadge: ((_M = config.widget) == null ? void 0 : _M.showBadge) !== false
205
222
  }
206
223
  };
207
224
  }
@@ -500,8 +517,8 @@ function generatePageMarkdownFiles(config) {
500
517
  const generated = [];
501
518
  const pages = config.pages || [];
502
519
  for (const page of pages) {
520
+ if (!page.content) continue;
503
521
  const pageTitle = page.title || (page.pathname === "/" ? config.title : void 0);
504
- if (!page.content && !pageTitle) continue;
505
522
  let filename;
506
523
  if (page.pathname === "/") {
507
524
  filename = "index.md";
@@ -771,6 +788,147 @@ function generateAIIndex(config) {
771
788
  };
772
789
  return JSON.stringify(index, null, 2);
773
790
  }
791
+
792
+ // src/core/schema.ts
793
+ function generateSchema(config) {
794
+ const output = generateSchemaObjects(config);
795
+ return JSON.stringify(output, null, 2);
796
+ }
797
+ function generateSchemaObjects(config) {
798
+ const siteSchemas = generateSiteSchemas(config);
799
+ const pageSchemas = {};
800
+ for (const page of config.pages) {
801
+ const schemas = generatePageSchemas(page, config);
802
+ if (schemas.length > 0) {
803
+ pageSchemas[page.pathname] = schemas;
804
+ }
805
+ }
806
+ return { site: siteSchemas, pages: pageSchemas };
807
+ }
808
+ function generateSiteSchemas(config) {
809
+ const schemas = [];
810
+ schemas.push({
811
+ "@context": "https://schema.org",
812
+ "@type": "WebSite",
813
+ name: config.title,
814
+ description: config.description || void 0,
815
+ url: config.url
816
+ });
817
+ const org = config.schema.organization;
818
+ if (org.name || org.sameAs.length > 0) {
819
+ const orgSchema = {
820
+ "@context": "https://schema.org",
821
+ "@type": "Organization",
822
+ name: org.name,
823
+ url: org.url
824
+ };
825
+ if (org.logo) orgSchema.logo = org.logo;
826
+ if (org.sameAs.length > 0) orgSchema.sameAs = org.sameAs;
827
+ schemas.push(orgSchema);
828
+ }
829
+ return schemas;
830
+ }
831
+ function generatePageSchemas(page, config) {
832
+ const schemas = [];
833
+ const pageUrl = page.pathname === "/" ? config.url : `${config.url.replace(/\/$/, "")}${page.pathname}`;
834
+ const faqItems = detectFaqPatterns(page.content || "");
835
+ if (faqItems.length > 0) {
836
+ schemas.push({
837
+ "@context": "https://schema.org",
838
+ "@type": "FAQPage",
839
+ mainEntity: faqItems.map(({ question, answer }) => ({
840
+ "@type": "Question",
841
+ name: question,
842
+ acceptedAnswer: {
843
+ "@type": "Answer",
844
+ text: answer
845
+ }
846
+ }))
847
+ });
848
+ }
849
+ const pageType = config.schema.defaultType;
850
+ const pageSchema = {
851
+ "@context": "https://schema.org",
852
+ "@type": pageType,
853
+ name: page.title || config.title,
854
+ url: pageUrl
855
+ };
856
+ if (page.description) pageSchema.description = page.description;
857
+ if (pageType === "Article") {
858
+ pageSchema.headline = page.title || config.title;
859
+ pageSchema.author = {
860
+ "@type": "Organization",
861
+ name: config.schema.organization.name
862
+ };
863
+ }
864
+ schemas.push(pageSchema);
865
+ if (page.pathname !== "/") {
866
+ const breadcrumbs = generateBreadcrumbs(page.pathname, config);
867
+ if (breadcrumbs.length > 1) {
868
+ schemas.push({
869
+ "@context": "https://schema.org",
870
+ "@type": "BreadcrumbList",
871
+ itemListElement: breadcrumbs.map((crumb, i) => ({
872
+ "@type": "ListItem",
873
+ position: i + 1,
874
+ name: crumb.name,
875
+ item: crumb.url
876
+ }))
877
+ });
878
+ }
879
+ }
880
+ return schemas;
881
+ }
882
+ function generateBreadcrumbs(pathname, config) {
883
+ const baseUrl = config.url.replace(/\/$/, "");
884
+ const parts = pathname.split("/").filter(Boolean);
885
+ const crumbs = [
886
+ { name: "Home", url: baseUrl + "/" }
887
+ ];
888
+ let currentPath = "";
889
+ for (const part of parts) {
890
+ currentPath += "/" + part;
891
+ crumbs.push({
892
+ name: part.charAt(0).toUpperCase() + part.slice(1).replace(/-/g, " "),
893
+ url: baseUrl + currentPath
894
+ });
895
+ }
896
+ return crumbs;
897
+ }
898
+ function detectFaqPatterns(content) {
899
+ const items = [];
900
+ const lines = content.split("\n");
901
+ for (let i = 0; i < lines.length; i++) {
902
+ const line = lines[i].trim();
903
+ const headingMatch = line.match(/^#{1,6}\s+(.+\?)\s*$/);
904
+ if (headingMatch) {
905
+ const answerLines = [];
906
+ for (let j = i + 1; j < lines.length; j++) {
907
+ const nextLine = lines[j].trim();
908
+ if (!nextLine) {
909
+ if (answerLines.length > 0) break;
910
+ continue;
911
+ }
912
+ if (/^#{1,6}\s/.test(nextLine)) break;
913
+ answerLines.push(nextLine);
914
+ }
915
+ if (answerLines.length > 0) {
916
+ items.push({
917
+ question: headingMatch[1],
918
+ answer: answerLines.join(" ").slice(0, 500)
919
+ });
920
+ }
921
+ }
922
+ }
923
+ return items;
924
+ }
925
+ function generateJsonLdScript(schemas) {
926
+ if (schemas.length === 0) return "";
927
+ if (schemas.length === 1) {
928
+ return `<script type="application/ld+json">${JSON.stringify(schemas[0])}</script>`;
929
+ }
930
+ return schemas.map((s) => `<script type="application/ld+json">${JSON.stringify(s)}</script>`).join("\n");
931
+ }
774
932
  async function generateAEOFiles(configOrRoot, maybeConfig) {
775
933
  var _a;
776
934
  let config;
@@ -799,7 +957,7 @@ async function generateAEOFiles(configOrRoot, maybeConfig) {
799
957
  if (config.generators.llmsTxt) {
800
958
  try {
801
959
  const content = generateLlmsTxt(config);
802
- fs.writeFileSync(path.join(outDir, "llms.txt"), "\uFEFF" + content, "utf-8");
960
+ fs.writeFileSync(path.join(outDir, "llms.txt"), content, "utf-8");
803
961
  files.push("llms.txt");
804
962
  } catch (e) {
805
963
  errors.push(`llms.txt: ${e.message}`);
@@ -808,7 +966,7 @@ async function generateAEOFiles(configOrRoot, maybeConfig) {
808
966
  if (config.generators.llmsFullTxt) {
809
967
  try {
810
968
  const content = generateLlmsFullTxt(config);
811
- fs.writeFileSync(path.join(outDir, "llms-full.txt"), "\uFEFF" + content, "utf-8");
969
+ fs.writeFileSync(path.join(outDir, "llms-full.txt"), content, "utf-8");
812
970
  files.push("llms-full.txt");
813
971
  } catch (e) {
814
972
  errors.push(`llms-full.txt: ${e.message}`);
@@ -859,12 +1017,131 @@ async function generateAEOFiles(configOrRoot, maybeConfig) {
859
1017
  errors.push(`ai-index.json: ${e.message}`);
860
1018
  }
861
1019
  }
1020
+ if (config.generators.schema && config.schema.enabled) {
1021
+ try {
1022
+ const content = generateSchema(config);
1023
+ fs.writeFileSync(path.join(outDir, "schema.json"), content, "utf-8");
1024
+ files.push("schema.json");
1025
+ } catch (e) {
1026
+ errors.push(`schema.json: ${e.message}`);
1027
+ }
1028
+ }
862
1029
  return { files, errors };
863
1030
  }
864
- function scanBuiltPages(dir, baseUrl) {
1031
+
1032
+ // src/core/html-extract.ts
1033
+ function extractTextFromHtml(html) {
1034
+ let text = html;
1035
+ text = text.replace(/<script[\s\S]*?<\/script>/gi, "");
1036
+ text = text.replace(/<style[\s\S]*?<\/style>/gi, "");
1037
+ text = text.replace(/<svg[\s\S]*?<\/svg>/gi, "");
1038
+ const mainMatch = text.match(/<main[^>]*>([\s\S]*)<\/main>/i);
1039
+ if (mainMatch) {
1040
+ text = mainMatch[1];
1041
+ } else {
1042
+ text = text.replace(/<nav[\s\S]*?<\/nav>/gi, "");
1043
+ text = text.replace(/<header[\s\S]*?<\/header>/gi, "");
1044
+ text = text.replace(/<footer[\s\S]*?<\/footer>/gi, "");
1045
+ }
1046
+ text = text.replace(/<a[^>]+href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, url, inner) => {
1047
+ if (/<(?:h[1-6]|div|p|section)[^>]*>/i.test(inner)) {
1048
+ const cleanInner = inner.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
1049
+ return `
1050
+ [${cleanInner.slice(0, 120).trim()}](${url})
1051
+ `;
1052
+ }
1053
+ return `[${inner}](${url})`;
1054
+ });
1055
+ text = text.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "\n\n## $1\n\n");
1056
+ text = text.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, "\n\n## $1\n\n");
1057
+ text = text.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, "\n\n### $1\n\n");
1058
+ text = text.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, "\n\n#### $1\n\n");
1059
+ text = text.replace(/<h5[^>]*>([\s\S]*?)<\/h5>/gi, "\n\n##### $1\n\n");
1060
+ text = text.replace(/<h6[^>]*>([\s\S]*?)<\/h6>/gi, "\n\n###### $1\n\n");
1061
+ text = text.replace(/<a[^>]+href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)");
1062
+ text = text.replace(/<(?:strong|b)[^>]*>([\s\S]*?)<\/(?:strong|b)>/gi, "**$1**");
1063
+ text = text.replace(/<(?:em|i)[^>]*>([\s\S]*?)<\/(?:em|i)>/gi, "*$1*");
1064
+ text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "\n- $1");
1065
+ text = text.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, "\n\n> $1\n\n");
1066
+ text = text.replace(/<hr[^>]*\/?>/gi, "\n\n---\n\n");
1067
+ text = text.replace(/<br[^>]*\/?>/gi, "\n");
1068
+ text = text.replace(/<\/p>/gi, "\n\n");
1069
+ text = text.replace(/<p[^>]*>/gi, "");
1070
+ text = text.replace(/<\/?(?:div|section|article|header|main|aside|figure|figcaption|table|thead|tbody|tr|td|th|ul|ol|dl|dt|dd)[^>]*>/gi, "\n");
1071
+ text = text.replace(/<[^>]+>/g, "");
1072
+ text = text.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/&copy;/g, "(c)");
1073
+ text = text.replace(/[\u{1F1E0}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE00}-\u{FE0F}\u{200D}\u{20E3}]/gu, "");
1074
+ text = text.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).join("\n");
1075
+ text = text.replace(/\n{3,}/g, "\n\n");
1076
+ text = text.replace(/\[[\s\n]+/g, "[").replace(/[\s\n]+\]/g, "]");
1077
+ text = text.replace(/(#{2,6})\s*\n+\s*/g, "$1 ");
1078
+ text = text.replace(/^#{2,6}\s*$/gm, "");
1079
+ text = text.replace(/\n{3,}/g, "\n\n");
1080
+ return text.trim().slice(0, 8e3);
1081
+ }
1082
+ function extractTitle2(html) {
1083
+ var _a, _b;
1084
+ const match = html.match(/<title>([^<]*)<\/title>/i);
1085
+ if (!match) return void 0;
1086
+ return ((_b = (_a = match[1]) == null ? void 0 : _a.split("|")[0]) == null ? void 0 : _b.trim()) || match[1];
1087
+ }
1088
+ function extractDescription(html) {
1089
+ const match = html.match(/<meta\s+name=["']description["']\s+content=["']([^"']*)["']/i);
1090
+ return match == null ? void 0 : match[1];
1091
+ }
1092
+ function htmlToMarkdown(html, pagePath, config) {
1093
+ const rawTitle = extractTitle2(html);
1094
+ const description = extractDescription(html);
1095
+ const textContent = extractTextFromHtml(html);
1096
+ const pageUrl = pagePath === "/" ? config.url : `${config.url.replace(/\/$/, "")}${pagePath}`;
1097
+ const lines = [];
1098
+ lines.push("---");
1099
+ if (rawTitle) lines.push(`title: "${rawTitle}"`);
1100
+ if (description) lines.push(`description: "${description}"`);
1101
+ lines.push(`url: ${pageUrl}`);
1102
+ lines.push(`source: ${pageUrl}`);
1103
+ lines.push(`generated_by: aeo.js`);
1104
+ lines.push("---", "");
1105
+ if (rawTitle) lines.push(`# ${rawTitle}`, "");
1106
+ if (description) lines.push(`${description}`, "");
1107
+ if (textContent) lines.push(textContent);
1108
+ return lines.join("\n");
1109
+ }
1110
+
1111
+ // src/core/opengraph.ts
1112
+ function generateOGTags(page, config) {
1113
+ const tags = [];
1114
+ const pageUrl = page.pathname === "/" ? config.url : `${config.url.replace(/\/$/, "")}${page.pathname}`;
1115
+ const title = page.title || config.title;
1116
+ const description = page.description || config.description;
1117
+ tags.push({ property: "og:type", content: config.og.type });
1118
+ tags.push({ property: "og:title", content: title });
1119
+ if (description) tags.push({ property: "og:description", content: description });
1120
+ tags.push({ property: "og:url", content: pageUrl });
1121
+ tags.push({ property: "og:site_name", content: config.title });
1122
+ if (config.og.image) tags.push({ property: "og:image", content: config.og.image });
1123
+ tags.push({ name: "twitter:card", content: config.og.image ? "summary_large_image" : "summary" });
1124
+ tags.push({ name: "twitter:title", content: title });
1125
+ if (description) tags.push({ name: "twitter:description", content: description });
1126
+ if (config.og.twitterHandle) tags.push({ name: "twitter:site", content: config.og.twitterHandle });
1127
+ if (config.og.image) tags.push({ name: "twitter:image", content: config.og.image });
1128
+ return tags;
1129
+ }
1130
+ function generateOGTagsHtml(page, config) {
1131
+ const tags = generateOGTags(page, config);
1132
+ return tags.map((tag) => {
1133
+ if (tag.property) return `<meta property="${tag.property}" content="${escapeAttr(tag.content)}" />`;
1134
+ return `<meta name="${tag.name}" content="${escapeAttr(tag.content)}" />`;
1135
+ }).join("\n ");
1136
+ }
1137
+ function escapeAttr(str) {
1138
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1139
+ }
1140
+
1141
+ // src/plugins/astro.ts
1142
+ function scanBuiltPages(dir, _baseUrl) {
865
1143
  const pages = [];
866
1144
  function walk(currentDir) {
867
- var _a;
868
1145
  try {
869
1146
  const entries = fs.readdirSync(currentDir);
870
1147
  for (const entry of entries) {
@@ -875,8 +1152,8 @@ function scanBuiltPages(dir, baseUrl) {
875
1152
  } else if (entry === "index.html" || entry.endsWith(".html") && entry !== "404.html" && entry !== "500.html") {
876
1153
  try {
877
1154
  const html = fs.readFileSync(fullPath, "utf-8");
878
- const titleMatch = html.match(/<title>([^<]*)<\/title>/i);
879
- const descMatch = html.match(/<meta\s+name=["']description["']\s+content=["']([^"']*)["']/i);
1155
+ const title = extractTitle2(html);
1156
+ const description = extractDescription(html);
880
1157
  const textContent = extractTextFromHtml(html);
881
1158
  let pathname;
882
1159
  const relative8 = fullPath.slice(dir.length);
@@ -887,13 +1164,12 @@ function scanBuiltPages(dir, baseUrl) {
887
1164
  pathname = "/" + relative8.replace(/\.html$/, "");
888
1165
  }
889
1166
  pathname = pathname.replace(/\/+/g, "/") || "/";
890
- const rawTitle = titleMatch ? titleMatch[1] : void 0;
891
- const title = ((_a = rawTitle == null ? void 0 : rawTitle.split("|")[0]) == null ? void 0 : _a.trim()) || rawTitle;
892
1167
  pages.push({
893
1168
  pathname,
894
1169
  title,
895
- description: descMatch ? descMatch[1] : void 0,
896
- content: textContent
1170
+ description,
1171
+ content: textContent,
1172
+ filePath: fullPath
897
1173
  });
898
1174
  } catch {
899
1175
  }
@@ -937,75 +1213,54 @@ function scanDevPages(pagesDir) {
937
1213
  }
938
1214
  return pages;
939
1215
  }
940
- function extractTextFromHtml(html) {
941
- let text = html;
942
- text = text.replace(/<script[\s\S]*?<\/script>/gi, "");
943
- text = text.replace(/<style[\s\S]*?<\/style>/gi, "");
944
- text = text.replace(/<svg[\s\S]*?<\/svg>/gi, "");
945
- const mainMatch = text.match(/<main[^>]*>([\s\S]*)<\/main>/i);
946
- if (mainMatch) {
947
- text = mainMatch[1];
948
- } else {
949
- text = text.replace(/<nav[\s\S]*?<\/nav>/gi, "");
950
- text = text.replace(/<header[\s\S]*?<\/header>/gi, "");
951
- text = text.replace(/<footer[\s\S]*?<\/footer>/gi, "");
952
- }
953
- text = text.replace(/<a[^>]+href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, url, inner) => {
954
- if (/<(?:h[1-6]|div|p|section)[^>]*>/i.test(inner)) {
955
- const cleanInner = inner.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
956
- return `
957
- [${cleanInner.slice(0, 120).trim()}](${url})
958
- `;
959
- }
960
- return `[${inner}](${url})`;
961
- });
962
- text = text.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "\n\n## $1\n\n");
963
- text = text.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, "\n\n## $1\n\n");
964
- text = text.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, "\n\n### $1\n\n");
965
- text = text.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, "\n\n#### $1\n\n");
966
- text = text.replace(/<h5[^>]*>([\s\S]*?)<\/h5>/gi, "\n\n##### $1\n\n");
967
- text = text.replace(/<h6[^>]*>([\s\S]*?)<\/h6>/gi, "\n\n###### $1\n\n");
968
- text = text.replace(/<a[^>]+href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)");
969
- text = text.replace(/<(?:strong|b)[^>]*>([\s\S]*?)<\/(?:strong|b)>/gi, "**$1**");
970
- text = text.replace(/<(?:em|i)[^>]*>([\s\S]*?)<\/(?:em|i)>/gi, "*$1*");
971
- text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "\n- $1");
972
- text = text.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, "\n\n> $1\n\n");
973
- text = text.replace(/<hr[^>]*\/?>/gi, "\n\n---\n\n");
974
- text = text.replace(/<br[^>]*\/?>/gi, "\n");
975
- text = text.replace(/<\/p>/gi, "\n\n");
976
- text = text.replace(/<p[^>]*>/gi, "");
977
- text = text.replace(/<\/?(?:div|section|article|header|main|aside|figure|figcaption|table|thead|tbody|tr|td|th|ul|ol|dl|dt|dd)[^>]*>/gi, "\n");
978
- text = text.replace(/<[^>]+>/g, "");
979
- text = text.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/&copy;/g, "(c)");
980
- text = text.replace(/[\u{1F1E0}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE00}-\u{FE0F}\u{200D}\u{20E3}]/gu, "");
981
- text = text.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).join("\n");
982
- text = text.replace(/\n{3,}/g, "\n\n");
983
- text = text.replace(/\[[\s\n]+/g, "[").replace(/[\s\n]+\]/g, "]");
984
- text = text.replace(/(#{2,6})\s*\n+\s*/g, "$1 ");
985
- text = text.replace(/^#{2,6}\s*$/gm, "");
986
- text = text.replace(/\n{3,}/g, "\n\n");
987
- return text.trim().slice(0, 8e3);
1216
+ function escapeAttr2(str) {
1217
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
988
1218
  }
989
- function htmlToMarkdown(html, pagePath, config) {
990
- var _a, _b;
991
- const titleMatch = html.match(/<title>([^<]*)<\/title>/i);
992
- const descMatch = html.match(/<meta\s+name=["']description["']\s+content=["']([^"']*)["']/i);
993
- const textContent = extractTextFromHtml(html);
994
- const rawTitle = titleMatch ? (_b = (_a = titleMatch[1]) == null ? void 0 : _a.split("|")[0]) == null ? void 0 : _b.trim() : void 0;
995
- const description = descMatch == null ? void 0 : descMatch[1];
996
- const pageUrl = pagePath === "/" ? config.url : `${config.url.replace(/\/$/, "")}${pagePath}`;
997
- const lines = [];
998
- lines.push("---");
999
- if (rawTitle) lines.push(`title: "${rawTitle}"`);
1000
- if (description) lines.push(`description: "${description}"`);
1001
- lines.push(`url: ${pageUrl}`);
1002
- lines.push(`source: ${pageUrl}`);
1003
- lines.push(`generated_by: aeo.js`);
1004
- lines.push("---", "");
1005
- if (rawTitle) lines.push(`# ${rawTitle}`, "");
1006
- if (description) lines.push(`${description}`, "");
1007
- if (textContent) lines.push(textContent);
1008
- return lines.join("\n");
1219
+ function injectHeadTags(pages, config) {
1220
+ let injectedCount = 0;
1221
+ for (const page of pages) {
1222
+ let html;
1223
+ try {
1224
+ html = fs.readFileSync(page.filePath, "utf-8");
1225
+ } catch {
1226
+ continue;
1227
+ }
1228
+ const tags = [];
1229
+ if (!/name=["']description["']/i.test(html)) {
1230
+ const desc = page.description || config.description;
1231
+ if (desc) {
1232
+ tags.push(`<meta name="description" content="${escapeAttr2(desc)}" />`);
1233
+ }
1234
+ }
1235
+ if (!/rel=["']canonical["']/i.test(html)) {
1236
+ const pageUrl = page.pathname === "/" ? config.url : `${config.url.replace(/\/$/, "")}${page.pathname}`;
1237
+ tags.push(`<link rel="canonical" href="${escapeAttr2(pageUrl)}" />`);
1238
+ }
1239
+ if (config.og.enabled && !/property=["']og:/i.test(html)) {
1240
+ const ogHtml = generateOGTagsHtml(page, config);
1241
+ if (ogHtml) tags.push(ogHtml);
1242
+ }
1243
+ if (config.schema.enabled && !/application\/ld\+json/i.test(html)) {
1244
+ const siteSchemas = generateSiteSchemas(config);
1245
+ const pageSchemas = generatePageSchemas(page, config);
1246
+ const jsonLdHtml = generateJsonLdScript([...siteSchemas, ...pageSchemas]);
1247
+ if (jsonLdHtml) tags.push(jsonLdHtml);
1248
+ }
1249
+ if (!/rel=["']alternate["'][^>]*llms\.txt/i.test(html)) {
1250
+ tags.push(`<link rel="alternate" type="text/plain" href="/llms.txt" title="LLM Summary" />`);
1251
+ tags.push(`<link rel="alternate" type="text/plain" href="/llms-full.txt" title="Full Content for LLMs" />`);
1252
+ tags.push(`<link rel="alternate" type="application/json" href="/docs.json" title="Documentation Manifest" />`);
1253
+ tags.push(`<link rel="alternate" type="application/json" href="/ai-index.json" title="AI-Optimized Index" />`);
1254
+ }
1255
+ if (tags.length === 0) continue;
1256
+ const injection = "\n " + tags.join("\n ") + "\n ";
1257
+ const newHtml = html.replace("</head>", injection + "</head>");
1258
+ if (newHtml !== html) {
1259
+ fs.writeFileSync(page.filePath, newHtml, "utf-8");
1260
+ injectedCount++;
1261
+ }
1262
+ }
1263
+ return injectedCount;
1009
1264
  }
1010
1265
  function aeoAstroIntegration(options = {}) {
1011
1266
  let resolvedConfig = resolveConfig(options);
@@ -1027,14 +1282,33 @@ function aeoAstroIntegration(options = {}) {
1027
1282
  }
1028
1283
  }
1029
1284
  if (resolvedConfig.widget.enabled && injectScript) {
1030
- const widgetOpts = JSON.stringify(resolvedConfig.widget);
1285
+ const widgetConfig = JSON.stringify({
1286
+ title: resolvedConfig.title,
1287
+ description: resolvedConfig.description,
1288
+ url: resolvedConfig.url,
1289
+ widget: resolvedConfig.widget
1290
+ });
1031
1291
  injectScript(
1032
1292
  "page",
1033
1293
  `import { AeoWidget } from 'aeo.js/widget';
1034
- if (document.readyState === 'loading') {
1035
- document.addEventListener('DOMContentLoaded', () => { new AeoWidget(${widgetOpts}); });
1036
- } else {
1037
- new AeoWidget(${widgetOpts});
1294
+ let __aeoWidget;
1295
+ function __initAeoWidget() {
1296
+ if (__aeoWidget) __aeoWidget.destroy();
1297
+ try {
1298
+ __aeoWidget = new AeoWidget({ config: ${widgetConfig} });
1299
+ } catch (e) {
1300
+ console.warn('[aeo.js] Widget initialization failed:', e);
1301
+ }
1302
+ }
1303
+ // astro:page-load fires on initial load AND after every View Transition navigation
1304
+ document.addEventListener('astro:page-load', __initAeoWidget);
1305
+ // Fallback for Astro sites without View Transitions
1306
+ if (!document.querySelector('meta[name="astro-view-transitions-enabled"]')) {
1307
+ if (document.readyState === 'loading') {
1308
+ document.addEventListener('DOMContentLoaded', __initAeoWidget);
1309
+ } else {
1310
+ __initAeoWidget();
1311
+ }
1038
1312
  }`
1039
1313
  );
1040
1314
  }
@@ -1068,6 +1342,14 @@ if (document.readyState === 'loading') {
1068
1342
  } catch (error) {
1069
1343
  buildLogger.error(`Failed to generate AEO files: ${error}`);
1070
1344
  }
1345
+ try {
1346
+ const injected = injectHeadTags(discoveredPages, resolvedConfig);
1347
+ if (injected > 0) {
1348
+ buildLogger.info(`Injected head tags into ${injected} pages`);
1349
+ }
1350
+ } catch (error) {
1351
+ buildLogger.error(`Failed to inject head tags: ${error}`);
1352
+ }
1071
1353
  },
1072
1354
  "astro:server:setup": async ({ server, logger }) => {
1073
1355
  const devLogger = logger.fork("aeo.js");
@@ -1154,8 +1436,24 @@ if (document.readyState === 'loading') {
1154
1436
  }
1155
1437
  };
1156
1438
  }
1157
- var AeoMetaTags = ({ config }) => {
1439
+ var AeoMetaTags = ({ config, page }) => {
1158
1440
  const resolvedConfig = resolveConfig(config);
1441
+ const currentPage = page || { pathname: "/" };
1442
+ const pageEntry = {
1443
+ pathname: currentPage.pathname || "/",
1444
+ title: currentPage.title,
1445
+ description: currentPage.description,
1446
+ content: currentPage.content
1447
+ };
1448
+ let jsonLd = "";
1449
+ if (resolvedConfig.schema.enabled) {
1450
+ const schemas = generatePageSchemas(pageEntry, resolvedConfig);
1451
+ jsonLd = generateJsonLdScript(schemas);
1452
+ }
1453
+ let ogTags = "";
1454
+ if (resolvedConfig.og.enabled) {
1455
+ ogTags = generateOGTagsHtml(pageEntry, resolvedConfig);
1456
+ }
1159
1457
  return `
1160
1458
  <link rel="alternate" type="text/plain" href="/llms.txt" title="LLM Summary" />
1161
1459
  <link rel="alternate" type="text/plain" href="/llms-full.txt" title="Full Content for LLMs" />
@@ -1164,6 +1462,8 @@ var AeoMetaTags = ({ config }) => {
1164
1462
  <meta name="aeo:title" content="${resolvedConfig.title}" />
1165
1463
  <meta name="aeo:description" content="${resolvedConfig.description}" />
1166
1464
  <meta name="aeo:url" content="${resolvedConfig.url}" />
1465
+ ${ogTags}
1466
+ ${jsonLd}
1167
1467
  `;
1168
1468
  };
1169
1469
  function defineAeoConfig(config) {