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