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.
- package/README.md +132 -17
- package/dist/angular.d.mts +29 -0
- package/dist/angular.d.ts +29 -0
- package/dist/angular.js +1314 -0
- package/dist/angular.js.map +1 -0
- package/dist/angular.mjs +1310 -0
- package/dist/angular.mjs.map +1 -0
- package/dist/astro.d.mts +8 -2
- package/dist/astro.d.ts +8 -2
- package/dist/astro.js +400 -100
- package/dist/astro.js.map +1 -1
- package/dist/astro.mjs +400 -100
- package/dist/astro.mjs.map +1 -1
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1880 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +1878 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +183 -4
- package/dist/index.d.ts +183 -4
- package/dist/index.js +974 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +952 -20
- package/dist/index.mjs.map +1 -1
- package/dist/next.d.mts +2 -17
- package/dist/next.d.ts +2 -17
- package/dist/next.js +262 -73
- package/dist/next.js.map +1 -1
- package/dist/next.mjs +262 -73
- package/dist/next.mjs.map +1 -1
- package/dist/nuxt.d.mts +13 -0
- package/dist/nuxt.d.ts +13 -0
- package/dist/nuxt.js +1344 -0
- package/dist/nuxt.js.map +1 -0
- package/dist/nuxt.mjs +1337 -0
- package/dist/nuxt.mjs.map +1 -0
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +330 -4
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +330 -4
- package/dist/react.mjs.map +1 -1
- package/dist/{types-BTY-v-7i.d.mts → types-Cn_Qbkmg.d.mts} +34 -0
- package/dist/{types-BTY-v-7i.d.ts → types-Cn_Qbkmg.d.ts} +34 -0
- package/dist/vite.d.mts +5 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +1370 -0
- package/dist/vite.js.map +1 -0
- package/dist/vite.mjs +1366 -0
- package/dist/vite.mjs.map +1 -0
- package/dist/vue.d.mts +19 -0
- package/dist/vue.d.ts +19 -0
- package/dist/vue.js +1404 -0
- package/dist/vue.js.map +1 -0
- package/dist/vue.mjs +1398 -0
- package/dist/vue.mjs.map +1 -0
- package/dist/webpack.d.mts +1 -1
- package/dist/webpack.d.ts +1 -1
- package/dist/webpack.js +178 -18
- package/dist/webpack.js.map +1 -1
- package/dist/webpack.mjs +178 -18
- package/dist/webpack.mjs.map +1 -1
- package/dist/widget.d.mts +11 -1
- package/dist/widget.d.ts +11 -1
- package/dist/widget.js +330 -4
- package/dist/widget.js.map +1 -1
- package/dist/widget.mjs +330 -4
- package/dist/widget.mjs.map +1 -1
- package/package.json +48 -2
package/dist/angular.js
ADDED
|
@@ -0,0 +1,1314 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
require('minimatch');
|
|
6
|
+
var crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
// src/core/robots.ts
|
|
9
|
+
var AI_CRAWLERS = [
|
|
10
|
+
"GPTBot",
|
|
11
|
+
"OAI-SearchBot",
|
|
12
|
+
"ChatGPT-User",
|
|
13
|
+
"ClaudeBot",
|
|
14
|
+
"Claude-Web",
|
|
15
|
+
"anthropic-ai",
|
|
16
|
+
"PerplexityBot",
|
|
17
|
+
"Google-Extended",
|
|
18
|
+
"Gemini-Deep-Research",
|
|
19
|
+
"Bingbot",
|
|
20
|
+
"FacebookBot",
|
|
21
|
+
"meta-externalagent",
|
|
22
|
+
"Amazonbot",
|
|
23
|
+
"Applebot",
|
|
24
|
+
"DeepSeekBot",
|
|
25
|
+
"Bytespider",
|
|
26
|
+
"cohere-ai",
|
|
27
|
+
"CCBot",
|
|
28
|
+
"DiffBot",
|
|
29
|
+
"YouBot",
|
|
30
|
+
"FirecrawlAgent",
|
|
31
|
+
"Crawl4AI",
|
|
32
|
+
"BraveBot",
|
|
33
|
+
"SemrushBot",
|
|
34
|
+
"AhrefsBot",
|
|
35
|
+
"MJ12bot",
|
|
36
|
+
"DotBot",
|
|
37
|
+
"DataForSeoBot",
|
|
38
|
+
"Screaming Frog SEO Spider",
|
|
39
|
+
"SEOkicks",
|
|
40
|
+
"SEMrushBot",
|
|
41
|
+
"BLEXBot",
|
|
42
|
+
"Yandex",
|
|
43
|
+
"Baiduspider",
|
|
44
|
+
"Sogou",
|
|
45
|
+
"Exabot",
|
|
46
|
+
"facebookexternalhit",
|
|
47
|
+
"LinkedInBot",
|
|
48
|
+
"WhatsApp",
|
|
49
|
+
"Slackbot",
|
|
50
|
+
"TwitterBot",
|
|
51
|
+
"TelegramBot",
|
|
52
|
+
"Discordbot",
|
|
53
|
+
"PinterestBot",
|
|
54
|
+
"TumblrBot",
|
|
55
|
+
"ViberBot",
|
|
56
|
+
"SkypeUriPreview",
|
|
57
|
+
"redditbot",
|
|
58
|
+
"Snapchat",
|
|
59
|
+
"TikTok"
|
|
60
|
+
];
|
|
61
|
+
function generateRobotsTxt(config) {
|
|
62
|
+
const lines = [
|
|
63
|
+
"# robots.txt generated by aeo.js",
|
|
64
|
+
"# Allow AI crawlers to index this site",
|
|
65
|
+
"",
|
|
66
|
+
"# Traditional search engines",
|
|
67
|
+
"User-agent: Googlebot",
|
|
68
|
+
"Allow: /",
|
|
69
|
+
"",
|
|
70
|
+
"User-agent: Bingbot",
|
|
71
|
+
"Allow: /",
|
|
72
|
+
"",
|
|
73
|
+
"# AI crawlers and answer engines"
|
|
74
|
+
];
|
|
75
|
+
for (const crawler of AI_CRAWLERS) {
|
|
76
|
+
lines.push(`User-agent: ${crawler}`);
|
|
77
|
+
lines.push("Allow: /");
|
|
78
|
+
lines.push("");
|
|
79
|
+
}
|
|
80
|
+
lines.push("# Default for all other bots");
|
|
81
|
+
lines.push("User-agent: *");
|
|
82
|
+
lines.push("Allow: /");
|
|
83
|
+
lines.push("");
|
|
84
|
+
if (config.url) {
|
|
85
|
+
lines.push(`Sitemap: ${config.url}/sitemap.xml`);
|
|
86
|
+
}
|
|
87
|
+
lines.push("");
|
|
88
|
+
lines.push("# AEO (Answer Engine Optimization) files");
|
|
89
|
+
lines.push("# These help LLMs understand your content better");
|
|
90
|
+
lines.push(`# ${config.url}/llms.txt`);
|
|
91
|
+
lines.push(`# ${config.url}/llms-full.txt`);
|
|
92
|
+
lines.push(`# ${config.url}/docs.json`);
|
|
93
|
+
lines.push(`# ${config.url}/ai-index.json`);
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/core/detect.ts
|
|
98
|
+
function detectFramework(projectRoot = process.cwd()) {
|
|
99
|
+
const packageJson = readPackageJson(projectRoot);
|
|
100
|
+
const dependencies = {
|
|
101
|
+
...packageJson.dependencies,
|
|
102
|
+
...packageJson.devDependencies
|
|
103
|
+
};
|
|
104
|
+
if (dependencies["next"]) {
|
|
105
|
+
return {
|
|
106
|
+
framework: "next",
|
|
107
|
+
contentDir: "app",
|
|
108
|
+
outDir: "out"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (dependencies["nuxt"] || dependencies["@nuxt/kit"]) {
|
|
112
|
+
return {
|
|
113
|
+
framework: "nuxt",
|
|
114
|
+
contentDir: "content",
|
|
115
|
+
outDir: ".output/public"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (dependencies["astro"] || dependencies["@astrojs/astro"]) {
|
|
119
|
+
return {
|
|
120
|
+
framework: "astro",
|
|
121
|
+
contentDir: "src/content",
|
|
122
|
+
outDir: "dist"
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (dependencies["@remix-run/dev"]) {
|
|
126
|
+
return {
|
|
127
|
+
framework: "remix",
|
|
128
|
+
contentDir: "app",
|
|
129
|
+
outDir: "build/client"
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (dependencies["@sveltejs/kit"]) {
|
|
133
|
+
return {
|
|
134
|
+
framework: "sveltekit",
|
|
135
|
+
contentDir: "src",
|
|
136
|
+
outDir: "build"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (dependencies["@angular/core"]) {
|
|
140
|
+
return {
|
|
141
|
+
framework: "angular",
|
|
142
|
+
contentDir: "src",
|
|
143
|
+
outDir: "dist"
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (dependencies["@docusaurus/core"]) {
|
|
147
|
+
return {
|
|
148
|
+
framework: "docusaurus",
|
|
149
|
+
contentDir: "docs",
|
|
150
|
+
outDir: "build"
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (dependencies["vite"]) {
|
|
154
|
+
return {
|
|
155
|
+
framework: "vite",
|
|
156
|
+
contentDir: "src",
|
|
157
|
+
outDir: "dist"
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
framework: "unknown",
|
|
162
|
+
contentDir: "src",
|
|
163
|
+
outDir: "dist"
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function resolveConfig(config = {}) {
|
|
167
|
+
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;
|
|
168
|
+
const frameworkInfo = detectFramework();
|
|
169
|
+
return {
|
|
170
|
+
title: config.title || "My Site",
|
|
171
|
+
description: config.description || "",
|
|
172
|
+
url: config.url || "https://example.com",
|
|
173
|
+
contentDir: config.contentDir || frameworkInfo.contentDir,
|
|
174
|
+
outDir: config.outDir || frameworkInfo.outDir,
|
|
175
|
+
pages: config.pages || [],
|
|
176
|
+
generators: {
|
|
177
|
+
robotsTxt: ((_a = config.generators) == null ? void 0 : _a.robotsTxt) !== false,
|
|
178
|
+
llmsTxt: ((_b = config.generators) == null ? void 0 : _b.llmsTxt) !== false,
|
|
179
|
+
llmsFullTxt: ((_c = config.generators) == null ? void 0 : _c.llmsFullTxt) !== false,
|
|
180
|
+
rawMarkdown: ((_d = config.generators) == null ? void 0 : _d.rawMarkdown) !== false,
|
|
181
|
+
manifest: ((_e = config.generators) == null ? void 0 : _e.manifest) !== false,
|
|
182
|
+
sitemap: ((_f = config.generators) == null ? void 0 : _f.sitemap) !== false,
|
|
183
|
+
aiIndex: ((_g = config.generators) == null ? void 0 : _g.aiIndex) !== false,
|
|
184
|
+
schema: ((_h = config.generators) == null ? void 0 : _h.schema) !== false
|
|
185
|
+
},
|
|
186
|
+
robots: {
|
|
187
|
+
allow: ((_i = config.robots) == null ? void 0 : _i.allow) || ["/"],
|
|
188
|
+
disallow: ((_j = config.robots) == null ? void 0 : _j.disallow) || [],
|
|
189
|
+
crawlDelay: ((_k = config.robots) == null ? void 0 : _k.crawlDelay) || 0,
|
|
190
|
+
sitemap: ((_l = config.robots) == null ? void 0 : _l.sitemap) || ""
|
|
191
|
+
},
|
|
192
|
+
schema: {
|
|
193
|
+
enabled: ((_m = config.schema) == null ? void 0 : _m.enabled) !== false,
|
|
194
|
+
organization: {
|
|
195
|
+
name: ((_o = (_n = config.schema) == null ? void 0 : _n.organization) == null ? void 0 : _o.name) || config.title || "My Site",
|
|
196
|
+
url: ((_q = (_p = config.schema) == null ? void 0 : _p.organization) == null ? void 0 : _q.url) || config.url || "https://example.com",
|
|
197
|
+
logo: ((_s = (_r = config.schema) == null ? void 0 : _r.organization) == null ? void 0 : _s.logo) || "",
|
|
198
|
+
sameAs: ((_u = (_t = config.schema) == null ? void 0 : _t.organization) == null ? void 0 : _u.sameAs) || []
|
|
199
|
+
},
|
|
200
|
+
defaultType: ((_v = config.schema) == null ? void 0 : _v.defaultType) || "WebPage"
|
|
201
|
+
},
|
|
202
|
+
og: {
|
|
203
|
+
enabled: ((_w = config.og) == null ? void 0 : _w.enabled) !== false,
|
|
204
|
+
image: ((_x = config.og) == null ? void 0 : _x.image) || "",
|
|
205
|
+
twitterHandle: ((_y = config.og) == null ? void 0 : _y.twitterHandle) || "",
|
|
206
|
+
type: ((_z = config.og) == null ? void 0 : _z.type) || "website"
|
|
207
|
+
},
|
|
208
|
+
widget: {
|
|
209
|
+
enabled: ((_A = config.widget) == null ? void 0 : _A.enabled) !== false,
|
|
210
|
+
position: ((_B = config.widget) == null ? void 0 : _B.position) || "bottom-right",
|
|
211
|
+
theme: {
|
|
212
|
+
background: ((_D = (_C = config.widget) == null ? void 0 : _C.theme) == null ? void 0 : _D.background) || "rgba(18, 18, 24, 0.9)",
|
|
213
|
+
text: ((_F = (_E = config.widget) == null ? void 0 : _E.theme) == null ? void 0 : _F.text) || "#C0C0C5",
|
|
214
|
+
accent: ((_H = (_G = config.widget) == null ? void 0 : _G.theme) == null ? void 0 : _H.accent) || "#E8E8EA",
|
|
215
|
+
badge: ((_J = (_I = config.widget) == null ? void 0 : _I.theme) == null ? void 0 : _J.badge) || "#4ADE80"
|
|
216
|
+
},
|
|
217
|
+
humanLabel: ((_K = config.widget) == null ? void 0 : _K.humanLabel) || "Human",
|
|
218
|
+
aiLabel: ((_L = config.widget) == null ? void 0 : _L.aiLabel) || "AI",
|
|
219
|
+
showBadge: ((_M = config.widget) == null ? void 0 : _M.showBadge) !== false
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function parseFrontmatter(content) {
|
|
224
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)/);
|
|
225
|
+
if (frontmatterMatch) {
|
|
226
|
+
const frontmatterStr = frontmatterMatch[1];
|
|
227
|
+
const contentWithoutFrontmatter = frontmatterMatch[2];
|
|
228
|
+
const frontmatter = {};
|
|
229
|
+
const lines = frontmatterStr.split("\n");
|
|
230
|
+
for (const line of lines) {
|
|
231
|
+
const [key, ...valueParts] = line.split(":");
|
|
232
|
+
if (key && valueParts.length > 0) {
|
|
233
|
+
const value = valueParts.join(":").trim();
|
|
234
|
+
frontmatter[key.trim()] = value.replace(/^["']|["']$/g, "");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { frontmatter, content: contentWithoutFrontmatter };
|
|
238
|
+
}
|
|
239
|
+
return { frontmatter: {}, content };
|
|
240
|
+
}
|
|
241
|
+
function bumpHeadings(content, levels = 1) {
|
|
242
|
+
return content.replace(/^(#{1,6})\s/gm, (match, hashes) => {
|
|
243
|
+
const newLevel = Math.min(hashes.length + levels, 6);
|
|
244
|
+
return "#".repeat(newLevel) + " ";
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function extractTitle(content) {
|
|
248
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
249
|
+
if (h1Match) return h1Match[1];
|
|
250
|
+
const h2Match = content.match(/^##\s+(.+)$/m);
|
|
251
|
+
if (h2Match) return h2Match[1];
|
|
252
|
+
const firstLine = content.split("\n")[0];
|
|
253
|
+
return firstLine.slice(0, 100);
|
|
254
|
+
}
|
|
255
|
+
function readPackageJson(projectRoot = process.cwd()) {
|
|
256
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
257
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
258
|
+
return {};
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
|
262
|
+
return JSON.parse(content);
|
|
263
|
+
} catch {
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/core/llms-txt.ts
|
|
269
|
+
function collectMarkdownFiles(dir, base = dir) {
|
|
270
|
+
const files = [];
|
|
271
|
+
try {
|
|
272
|
+
const entries = fs.readdirSync(dir);
|
|
273
|
+
for (const entry of entries) {
|
|
274
|
+
const fullPath = path.join(dir, entry);
|
|
275
|
+
const stat = fs.statSync(fullPath);
|
|
276
|
+
if (stat.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
|
|
277
|
+
files.push(...collectMarkdownFiles(fullPath, base));
|
|
278
|
+
} else if (stat.isFile() && (path.extname(entry) === ".md" || path.extname(entry) === ".mdx")) {
|
|
279
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
280
|
+
const { frontmatter, content: mainContent } = parseFrontmatter(content);
|
|
281
|
+
const relativePath = path.relative(base, fullPath);
|
|
282
|
+
files.push({
|
|
283
|
+
path: relativePath,
|
|
284
|
+
content: mainContent,
|
|
285
|
+
title: frontmatter.title || extractTitle(mainContent),
|
|
286
|
+
description: frontmatter.description,
|
|
287
|
+
frontmatter
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.warn(`Warning: Could not read directory ${dir}:`, error);
|
|
293
|
+
}
|
|
294
|
+
return files;
|
|
295
|
+
}
|
|
296
|
+
function generateLlmsTxt(config) {
|
|
297
|
+
const lines = [
|
|
298
|
+
`# ${config.title}`,
|
|
299
|
+
""
|
|
300
|
+
];
|
|
301
|
+
if (config.description) {
|
|
302
|
+
lines.push(`> ${config.description}`);
|
|
303
|
+
lines.push("");
|
|
304
|
+
}
|
|
305
|
+
lines.push("## About");
|
|
306
|
+
lines.push("");
|
|
307
|
+
lines.push("This file provides a structured overview of the documentation and content available on this site,");
|
|
308
|
+
lines.push("optimized for consumption by Large Language Models (LLMs) and AI assistants.");
|
|
309
|
+
lines.push("");
|
|
310
|
+
if (config.pages && config.pages.length > 0) {
|
|
311
|
+
lines.push("## Pages");
|
|
312
|
+
lines.push("");
|
|
313
|
+
for (const page of config.pages) {
|
|
314
|
+
const url = `${config.url}${page.pathname === "/" ? "" : page.pathname}`;
|
|
315
|
+
const title = page.title || page.pathname;
|
|
316
|
+
lines.push(`- [${title}](${url})`);
|
|
317
|
+
if (page.description) {
|
|
318
|
+
lines.push(` ${page.description}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
lines.push("");
|
|
322
|
+
}
|
|
323
|
+
const markdownFiles = collectMarkdownFiles(config.contentDir);
|
|
324
|
+
if (markdownFiles.length > 0) {
|
|
325
|
+
lines.push("## Documentation");
|
|
326
|
+
lines.push("");
|
|
327
|
+
const grouped = {};
|
|
328
|
+
for (const file of markdownFiles) {
|
|
329
|
+
const dir = file.path.split("/")[0] || "root";
|
|
330
|
+
if (!grouped[dir]) grouped[dir] = [];
|
|
331
|
+
grouped[dir].push(file);
|
|
332
|
+
}
|
|
333
|
+
for (const [dir, files] of Object.entries(grouped)) {
|
|
334
|
+
lines.push(`### ${dir === "root" ? "Main Documentation" : dir}`);
|
|
335
|
+
lines.push("");
|
|
336
|
+
for (const file of files) {
|
|
337
|
+
const url = `${config.url}/${file.path.replace(/\.mdx?$/, "")}`;
|
|
338
|
+
lines.push(`- [${file.title}](${url})`);
|
|
339
|
+
if (file.description) {
|
|
340
|
+
lines.push(` ${file.description}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
lines.push("");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
lines.push("## Quick Links");
|
|
347
|
+
lines.push("");
|
|
348
|
+
lines.push(`- Full Documentation: ${config.url}/llms-full.txt`);
|
|
349
|
+
lines.push(`- Documentation Manifest: ${config.url}/docs.json`);
|
|
350
|
+
lines.push(`- AI-Optimized Index: ${config.url}/ai-index.json`);
|
|
351
|
+
lines.push(`- Sitemap: ${config.url}/sitemap.xml`);
|
|
352
|
+
lines.push("");
|
|
353
|
+
lines.push("## For LLMs");
|
|
354
|
+
lines.push("");
|
|
355
|
+
lines.push("To get the complete documentation in a single file, request:");
|
|
356
|
+
lines.push(`${config.url}/llms-full.txt`);
|
|
357
|
+
lines.push("");
|
|
358
|
+
lines.push("For structured access to individual pages with metadata:");
|
|
359
|
+
lines.push(`${config.url}/docs.json`);
|
|
360
|
+
lines.push("");
|
|
361
|
+
lines.push("For RAG (Retrieval Augmented Generation) systems:");
|
|
362
|
+
lines.push(`${config.url}/ai-index.json`);
|
|
363
|
+
lines.push("");
|
|
364
|
+
lines.push("---");
|
|
365
|
+
lines.push("Generated by aeo.js - Answer Engine Optimization for the modern web");
|
|
366
|
+
lines.push("Learn more at https://aeojs.org");
|
|
367
|
+
return lines.join("\n");
|
|
368
|
+
}
|
|
369
|
+
function collectAndConcatenateMarkdown(dir, base = dir) {
|
|
370
|
+
const sections = [];
|
|
371
|
+
try {
|
|
372
|
+
const entries = fs.readdirSync(dir).sort();
|
|
373
|
+
for (const entry of entries) {
|
|
374
|
+
const fullPath = path.join(dir, entry);
|
|
375
|
+
const stat = fs.statSync(fullPath);
|
|
376
|
+
if (stat.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
|
|
377
|
+
const subSections = collectAndConcatenateMarkdown(fullPath, base);
|
|
378
|
+
if (subSections.length > 0) {
|
|
379
|
+
sections.push(...subSections);
|
|
380
|
+
}
|
|
381
|
+
} else if (stat.isFile() && (path.extname(entry) === ".md" || path.extname(entry) === ".mdx")) {
|
|
382
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
383
|
+
const { frontmatter, content: mainContent } = parseFrontmatter(content);
|
|
384
|
+
const relativePath = path.relative(base, fullPath);
|
|
385
|
+
const sectionLines = [
|
|
386
|
+
"---",
|
|
387
|
+
"",
|
|
388
|
+
`# ${frontmatter.title || relativePath}`,
|
|
389
|
+
"",
|
|
390
|
+
`Source: ${relativePath}`,
|
|
391
|
+
""
|
|
392
|
+
];
|
|
393
|
+
if (frontmatter.description) {
|
|
394
|
+
sectionLines.push(`> ${frontmatter.description}`);
|
|
395
|
+
sectionLines.push("");
|
|
396
|
+
}
|
|
397
|
+
const bumpedContent = bumpHeadings(mainContent, 1);
|
|
398
|
+
sectionLines.push(bumpedContent);
|
|
399
|
+
sectionLines.push("");
|
|
400
|
+
sections.push(sectionLines.join("\n"));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.warn(`Warning: Could not read directory ${dir}:`, error);
|
|
405
|
+
}
|
|
406
|
+
return sections;
|
|
407
|
+
}
|
|
408
|
+
function generateLlmsFullTxt(config) {
|
|
409
|
+
const lines = [
|
|
410
|
+
`# ${config.title} - Complete Documentation`,
|
|
411
|
+
"",
|
|
412
|
+
`This file contains all documentation concatenated into a single file for easy consumption by LLMs.`,
|
|
413
|
+
""
|
|
414
|
+
];
|
|
415
|
+
if (config.description) {
|
|
416
|
+
lines.push(`> ${config.description}`);
|
|
417
|
+
lines.push("");
|
|
418
|
+
}
|
|
419
|
+
lines.push("## Table of Contents");
|
|
420
|
+
lines.push("");
|
|
421
|
+
lines.push("This document includes all content from this project.");
|
|
422
|
+
lines.push("Each section is separated by a horizontal rule (---) for easy parsing.");
|
|
423
|
+
lines.push("");
|
|
424
|
+
let hasContent = false;
|
|
425
|
+
if (config.pages && config.pages.length > 0) {
|
|
426
|
+
for (const page of config.pages) {
|
|
427
|
+
const url = `${config.url}${page.pathname === "/" ? "" : page.pathname}`;
|
|
428
|
+
const title = page.title || page.pathname;
|
|
429
|
+
const sectionLines = [
|
|
430
|
+
"---",
|
|
431
|
+
"",
|
|
432
|
+
`# ${title}`,
|
|
433
|
+
"",
|
|
434
|
+
`URL: ${url}`,
|
|
435
|
+
""
|
|
436
|
+
];
|
|
437
|
+
if (page.description) {
|
|
438
|
+
sectionLines.push(`> ${page.description}`);
|
|
439
|
+
sectionLines.push("");
|
|
440
|
+
}
|
|
441
|
+
if (page.content) {
|
|
442
|
+
sectionLines.push(page.content);
|
|
443
|
+
sectionLines.push("");
|
|
444
|
+
}
|
|
445
|
+
lines.push(sectionLines.join("\n"));
|
|
446
|
+
hasContent = true;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const sections = collectAndConcatenateMarkdown(config.contentDir);
|
|
450
|
+
if (sections.length > 0) {
|
|
451
|
+
lines.push(...sections);
|
|
452
|
+
hasContent = true;
|
|
453
|
+
}
|
|
454
|
+
if (!hasContent) {
|
|
455
|
+
lines.push("---");
|
|
456
|
+
lines.push("");
|
|
457
|
+
lines.push(`# ${config.title}`);
|
|
458
|
+
lines.push("");
|
|
459
|
+
lines.push(`URL: ${config.url}`);
|
|
460
|
+
lines.push("");
|
|
461
|
+
if (config.description) {
|
|
462
|
+
lines.push(config.description);
|
|
463
|
+
lines.push("");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
lines.push("---");
|
|
467
|
+
lines.push("");
|
|
468
|
+
lines.push("## About This Document");
|
|
469
|
+
lines.push("");
|
|
470
|
+
lines.push("This concatenated documentation file is generated automatically by aeo.js");
|
|
471
|
+
lines.push("to make it easier for AI systems to understand the complete context of this project.");
|
|
472
|
+
lines.push("");
|
|
473
|
+
lines.push(`For a structured index, see: ${config.url}/llms.txt`);
|
|
474
|
+
lines.push(`For individual files, see: ${config.url}/docs.json`);
|
|
475
|
+
lines.push("");
|
|
476
|
+
lines.push("Generated by aeo.js - https://aeojs.org");
|
|
477
|
+
return lines.join("\n");
|
|
478
|
+
}
|
|
479
|
+
function ensureDir(path) {
|
|
480
|
+
fs.mkdirSync(path, { recursive: true });
|
|
481
|
+
}
|
|
482
|
+
function copyMarkdownFiles(config) {
|
|
483
|
+
const copiedFiles = [];
|
|
484
|
+
function copyRecursive(dir, base = config.contentDir) {
|
|
485
|
+
try {
|
|
486
|
+
const entries = fs.readdirSync(dir);
|
|
487
|
+
for (const entry of entries) {
|
|
488
|
+
const fullPath = path.join(dir, entry);
|
|
489
|
+
const stat = fs.statSync(fullPath);
|
|
490
|
+
if (stat.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
|
|
491
|
+
copyRecursive(fullPath, base);
|
|
492
|
+
} else if (stat.isFile() && path.extname(entry) === ".md") {
|
|
493
|
+
const relativePath = path.relative(base, fullPath);
|
|
494
|
+
const destPath = path.join(config.outDir, relativePath);
|
|
495
|
+
ensureDir(path.dirname(destPath));
|
|
496
|
+
try {
|
|
497
|
+
fs.copyFileSync(fullPath, destPath);
|
|
498
|
+
copiedFiles.push({
|
|
499
|
+
source: fullPath,
|
|
500
|
+
destination: destPath
|
|
501
|
+
});
|
|
502
|
+
} catch (error) {
|
|
503
|
+
console.warn(`Warning: Could not copy ${fullPath}:`, error);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.warn(`Warning: Could not read directory ${dir}:`, error);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
copyRecursive(config.contentDir);
|
|
512
|
+
return copiedFiles;
|
|
513
|
+
}
|
|
514
|
+
function generatePageMarkdownFiles(config) {
|
|
515
|
+
const generated = [];
|
|
516
|
+
const pages = config.pages || [];
|
|
517
|
+
for (const page of pages) {
|
|
518
|
+
if (!page.content) continue;
|
|
519
|
+
const pageTitle = page.title || (page.pathname === "/" ? config.title : void 0);
|
|
520
|
+
let filename;
|
|
521
|
+
if (page.pathname === "/") {
|
|
522
|
+
filename = "index.md";
|
|
523
|
+
} else {
|
|
524
|
+
const clean = page.pathname.replace(/^\//, "").replace(/\/$/, "");
|
|
525
|
+
filename = clean.includes("/") ? `${clean}.md` : `${clean}.md`;
|
|
526
|
+
}
|
|
527
|
+
const destPath = path.join(config.outDir, filename);
|
|
528
|
+
const pageUrl = page.pathname === "/" ? config.url : `${config.url.replace(/\/$/, "")}${page.pathname}`;
|
|
529
|
+
const lines = [];
|
|
530
|
+
lines.push("---");
|
|
531
|
+
if (pageTitle) lines.push(`title: "${pageTitle}"`);
|
|
532
|
+
if (page.description) lines.push(`description: "${page.description}"`);
|
|
533
|
+
lines.push(`url: ${pageUrl}`);
|
|
534
|
+
lines.push(`source: ${pageUrl}`);
|
|
535
|
+
lines.push(`generated_by: aeo.js`);
|
|
536
|
+
lines.push("---", "");
|
|
537
|
+
if (pageTitle) {
|
|
538
|
+
lines.push(`# ${pageTitle}`, "");
|
|
539
|
+
}
|
|
540
|
+
if (page.description) {
|
|
541
|
+
lines.push(`${page.description}`, "");
|
|
542
|
+
}
|
|
543
|
+
if (page.content) {
|
|
544
|
+
lines.push(page.content);
|
|
545
|
+
}
|
|
546
|
+
const content = lines.join("\n");
|
|
547
|
+
ensureDir(path.dirname(destPath));
|
|
548
|
+
try {
|
|
549
|
+
fs.writeFileSync(destPath, content, "utf-8");
|
|
550
|
+
generated.push({ pathname: page.pathname, destination: destPath });
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return generated;
|
|
555
|
+
}
|
|
556
|
+
function collectManifestEntries(dir, config, base = dir) {
|
|
557
|
+
const entries = [];
|
|
558
|
+
try {
|
|
559
|
+
const files = fs.readdirSync(dir);
|
|
560
|
+
for (const file of files) {
|
|
561
|
+
const fullPath = path.join(dir, file);
|
|
562
|
+
const stat = fs.statSync(fullPath);
|
|
563
|
+
if (stat.isDirectory() && !file.startsWith(".") && file !== "node_modules") {
|
|
564
|
+
entries.push(...collectManifestEntries(fullPath, config, base));
|
|
565
|
+
} else if (stat.isFile() && (path.extname(file) === ".md" || path.extname(file) === ".mdx")) {
|
|
566
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
567
|
+
const { frontmatter, content: mainContent } = parseFrontmatter(content);
|
|
568
|
+
const relativePath = path.relative(base, fullPath);
|
|
569
|
+
const urlPath = relativePath.replace(/\.mdx?$/, "");
|
|
570
|
+
entries.push({
|
|
571
|
+
url: `${config.url}/${urlPath}`,
|
|
572
|
+
title: frontmatter.title || extractTitle(mainContent),
|
|
573
|
+
description: frontmatter.description,
|
|
574
|
+
lastModified: stat.mtime.toISOString()
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.warn(`Warning: Could not read directory ${dir}:`, error);
|
|
580
|
+
}
|
|
581
|
+
return entries;
|
|
582
|
+
}
|
|
583
|
+
function generateManifest(config) {
|
|
584
|
+
const entries = [];
|
|
585
|
+
if (config.pages && config.pages.length > 0) {
|
|
586
|
+
for (const page of config.pages) {
|
|
587
|
+
entries.push({
|
|
588
|
+
url: `${config.url}${page.pathname === "/" ? "" : page.pathname}`,
|
|
589
|
+
title: page.title || page.pathname,
|
|
590
|
+
description: page.description
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
entries.push(...collectManifestEntries(config.contentDir, config));
|
|
595
|
+
const manifest = {
|
|
596
|
+
version: "1.0",
|
|
597
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
598
|
+
site: {
|
|
599
|
+
title: config.title,
|
|
600
|
+
description: config.description,
|
|
601
|
+
url: config.url
|
|
602
|
+
},
|
|
603
|
+
documents: entries.sort((a, b) => a.url.localeCompare(b.url)),
|
|
604
|
+
metadata: {
|
|
605
|
+
totalDocuments: entries.length,
|
|
606
|
+
generator: "aeo.js",
|
|
607
|
+
generatorUrl: "https://aeojs.org"
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
return JSON.stringify(manifest, null, 2);
|
|
611
|
+
}
|
|
612
|
+
function collectUrls(dir, config, base = dir) {
|
|
613
|
+
const urls = [];
|
|
614
|
+
try {
|
|
615
|
+
const entries = fs.readdirSync(dir);
|
|
616
|
+
for (const entry of entries) {
|
|
617
|
+
const fullPath = path.join(dir, entry);
|
|
618
|
+
const stat = fs.statSync(fullPath);
|
|
619
|
+
if (stat.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
|
|
620
|
+
urls.push(...collectUrls(fullPath, config, base));
|
|
621
|
+
} else if (stat.isFile() && (path.extname(entry) === ".md" || path.extname(entry) === ".mdx" || path.extname(entry) === ".html")) {
|
|
622
|
+
const relativePath = path.relative(base, fullPath);
|
|
623
|
+
const urlPath = relativePath.replace(/\.(md|mdx|html)$/, "");
|
|
624
|
+
urls.push(`${config.url}/${urlPath}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
} catch (error) {
|
|
628
|
+
console.warn(`Warning: Could not read directory ${dir}:`, error);
|
|
629
|
+
}
|
|
630
|
+
return urls;
|
|
631
|
+
}
|
|
632
|
+
function escapeXml(str) {
|
|
633
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
634
|
+
}
|
|
635
|
+
function generateSitemap(config) {
|
|
636
|
+
const urls = [];
|
|
637
|
+
if (config.pages && config.pages.length > 0) {
|
|
638
|
+
for (const page of config.pages) {
|
|
639
|
+
urls.push(`${config.url}${page.pathname === "/" ? "" : page.pathname}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (config.contentDir) {
|
|
643
|
+
urls.push(...collectUrls(config.contentDir, config));
|
|
644
|
+
}
|
|
645
|
+
const lines = [
|
|
646
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
647
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
|
|
648
|
+
];
|
|
649
|
+
urls.push(config.url);
|
|
650
|
+
const uniqueUrls = [...new Set(urls)].sort();
|
|
651
|
+
for (const url of uniqueUrls) {
|
|
652
|
+
lines.push(" <url>");
|
|
653
|
+
lines.push(` <loc>${escapeXml(url)}</loc>`);
|
|
654
|
+
lines.push(` <lastmod>${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}</lastmod>`);
|
|
655
|
+
lines.push(" <changefreq>weekly</changefreq>");
|
|
656
|
+
lines.push(" <priority>0.8</priority>");
|
|
657
|
+
lines.push(" </url>");
|
|
658
|
+
}
|
|
659
|
+
lines.push("</urlset>");
|
|
660
|
+
return lines.join("\n");
|
|
661
|
+
}
|
|
662
|
+
function extractKeywords(content) {
|
|
663
|
+
const words = content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((word) => word.length > 3);
|
|
664
|
+
const wordCount = {};
|
|
665
|
+
for (const word of words) {
|
|
666
|
+
wordCount[word] = (wordCount[word] || 0) + 1;
|
|
667
|
+
}
|
|
668
|
+
return Object.entries(wordCount).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([word]) => word);
|
|
669
|
+
}
|
|
670
|
+
function chunkContent(content, maxLength = 2e3) {
|
|
671
|
+
const chunks = [];
|
|
672
|
+
const paragraphs = content.split("\n\n");
|
|
673
|
+
let currentChunk = "";
|
|
674
|
+
for (const paragraph of paragraphs) {
|
|
675
|
+
if (currentChunk.length + paragraph.length > maxLength && currentChunk.length > 0) {
|
|
676
|
+
chunks.push(currentChunk.trim());
|
|
677
|
+
currentChunk = "";
|
|
678
|
+
}
|
|
679
|
+
currentChunk += paragraph + "\n\n";
|
|
680
|
+
}
|
|
681
|
+
if (currentChunk.trim()) {
|
|
682
|
+
chunks.push(currentChunk.trim());
|
|
683
|
+
}
|
|
684
|
+
return chunks;
|
|
685
|
+
}
|
|
686
|
+
function collectAIIndexEntries(dir, config, base = dir) {
|
|
687
|
+
const entries = [];
|
|
688
|
+
try {
|
|
689
|
+
const files = fs.readdirSync(dir);
|
|
690
|
+
for (const file of files) {
|
|
691
|
+
const fullPath = path.join(dir, file);
|
|
692
|
+
const stat = fs.statSync(fullPath);
|
|
693
|
+
if (stat.isDirectory() && !file.startsWith(".") && file !== "node_modules") {
|
|
694
|
+
entries.push(...collectAIIndexEntries(fullPath, config, base));
|
|
695
|
+
} else if (stat.isFile() && (path.extname(file) === ".md" || path.extname(file) === ".mdx")) {
|
|
696
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
697
|
+
const { frontmatter, content: mainContent } = parseFrontmatter(content);
|
|
698
|
+
const relativePath = path.relative(base, fullPath);
|
|
699
|
+
const urlPath = relativePath.replace(/\.mdx?$/, "");
|
|
700
|
+
const url = `${config.url}/${urlPath}`;
|
|
701
|
+
const chunks = chunkContent(mainContent);
|
|
702
|
+
const title = frontmatter.title || extractTitle(mainContent);
|
|
703
|
+
const keywords = extractKeywords(mainContent);
|
|
704
|
+
chunks.forEach((chunk, index) => {
|
|
705
|
+
const id = crypto.createHash("sha256").update(`${url}-${index}`).digest("hex").slice(0, 16);
|
|
706
|
+
entries.push({
|
|
707
|
+
id,
|
|
708
|
+
url,
|
|
709
|
+
title: chunks.length > 1 ? `${title} (Part ${index + 1})` : title,
|
|
710
|
+
content: chunk,
|
|
711
|
+
description: frontmatter.description,
|
|
712
|
+
keywords,
|
|
713
|
+
metadata: {
|
|
714
|
+
...frontmatter,
|
|
715
|
+
chunkIndex: index,
|
|
716
|
+
totalChunks: chunks.length,
|
|
717
|
+
sourcePath: relativePath
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.warn(`Warning: Could not read directory ${dir}:`, error);
|
|
725
|
+
}
|
|
726
|
+
return entries;
|
|
727
|
+
}
|
|
728
|
+
function generateAIIndex(config) {
|
|
729
|
+
const entries = [];
|
|
730
|
+
if (config.pages && config.pages.length > 0) {
|
|
731
|
+
for (const page of config.pages) {
|
|
732
|
+
const url = `${config.url}${page.pathname === "/" ? "" : page.pathname}`;
|
|
733
|
+
const title = page.title || page.pathname;
|
|
734
|
+
const content = page.content || "";
|
|
735
|
+
if (content) {
|
|
736
|
+
const chunks = chunkContent(content);
|
|
737
|
+
const keywords = extractKeywords(content);
|
|
738
|
+
chunks.forEach((chunk, index2) => {
|
|
739
|
+
const id = crypto.createHash("sha256").update(`${url}-${index2}`).digest("hex").slice(0, 16);
|
|
740
|
+
entries.push({
|
|
741
|
+
id,
|
|
742
|
+
url,
|
|
743
|
+
title: chunks.length > 1 ? `${title} (Part ${index2 + 1})` : title,
|
|
744
|
+
content: chunk,
|
|
745
|
+
description: page.description,
|
|
746
|
+
keywords,
|
|
747
|
+
metadata: {
|
|
748
|
+
chunkIndex: index2,
|
|
749
|
+
totalChunks: chunks.length,
|
|
750
|
+
sourcePath: page.pathname
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
} else {
|
|
755
|
+
const id = crypto.createHash("sha256").update(url).digest("hex").slice(0, 16);
|
|
756
|
+
entries.push({
|
|
757
|
+
id,
|
|
758
|
+
url,
|
|
759
|
+
title,
|
|
760
|
+
content: page.description || title,
|
|
761
|
+
description: page.description,
|
|
762
|
+
keywords: []
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
entries.push(...collectAIIndexEntries(config.contentDir, config));
|
|
768
|
+
const index = {
|
|
769
|
+
version: "1.0",
|
|
770
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
771
|
+
site: {
|
|
772
|
+
title: config.title,
|
|
773
|
+
description: config.description,
|
|
774
|
+
url: config.url
|
|
775
|
+
},
|
|
776
|
+
entries: entries.sort((a, b) => a.id.localeCompare(b.id)),
|
|
777
|
+
metadata: {
|
|
778
|
+
totalEntries: entries.length,
|
|
779
|
+
generator: "aeo.js",
|
|
780
|
+
generatorUrl: "https://aeojs.org",
|
|
781
|
+
embedding: {
|
|
782
|
+
recommended: "text-embedding-ada-002",
|
|
783
|
+
dimensions: 1536
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
return JSON.stringify(index, null, 2);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/core/schema.ts
|
|
791
|
+
function generateSchema(config) {
|
|
792
|
+
const output = generateSchemaObjects(config);
|
|
793
|
+
return JSON.stringify(output, null, 2);
|
|
794
|
+
}
|
|
795
|
+
function generateSchemaObjects(config) {
|
|
796
|
+
const siteSchemas = generateSiteSchemas(config);
|
|
797
|
+
const pageSchemas = {};
|
|
798
|
+
for (const page of config.pages) {
|
|
799
|
+
const schemas = generatePageSchemas(page, config);
|
|
800
|
+
if (schemas.length > 0) {
|
|
801
|
+
pageSchemas[page.pathname] = schemas;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return { site: siteSchemas, pages: pageSchemas };
|
|
805
|
+
}
|
|
806
|
+
function generateSiteSchemas(config) {
|
|
807
|
+
const schemas = [];
|
|
808
|
+
schemas.push({
|
|
809
|
+
"@context": "https://schema.org",
|
|
810
|
+
"@type": "WebSite",
|
|
811
|
+
name: config.title,
|
|
812
|
+
description: config.description || void 0,
|
|
813
|
+
url: config.url
|
|
814
|
+
});
|
|
815
|
+
const org = config.schema.organization;
|
|
816
|
+
if (org.name || org.sameAs.length > 0) {
|
|
817
|
+
const orgSchema = {
|
|
818
|
+
"@context": "https://schema.org",
|
|
819
|
+
"@type": "Organization",
|
|
820
|
+
name: org.name,
|
|
821
|
+
url: org.url
|
|
822
|
+
};
|
|
823
|
+
if (org.logo) orgSchema.logo = org.logo;
|
|
824
|
+
if (org.sameAs.length > 0) orgSchema.sameAs = org.sameAs;
|
|
825
|
+
schemas.push(orgSchema);
|
|
826
|
+
}
|
|
827
|
+
return schemas;
|
|
828
|
+
}
|
|
829
|
+
function generatePageSchemas(page, config) {
|
|
830
|
+
const schemas = [];
|
|
831
|
+
const pageUrl = page.pathname === "/" ? config.url : `${config.url.replace(/\/$/, "")}${page.pathname}`;
|
|
832
|
+
const faqItems = detectFaqPatterns(page.content || "");
|
|
833
|
+
if (faqItems.length > 0) {
|
|
834
|
+
schemas.push({
|
|
835
|
+
"@context": "https://schema.org",
|
|
836
|
+
"@type": "FAQPage",
|
|
837
|
+
mainEntity: faqItems.map(({ question, answer }) => ({
|
|
838
|
+
"@type": "Question",
|
|
839
|
+
name: question,
|
|
840
|
+
acceptedAnswer: {
|
|
841
|
+
"@type": "Answer",
|
|
842
|
+
text: answer
|
|
843
|
+
}
|
|
844
|
+
}))
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
const pageType = config.schema.defaultType;
|
|
848
|
+
const pageSchema = {
|
|
849
|
+
"@context": "https://schema.org",
|
|
850
|
+
"@type": pageType,
|
|
851
|
+
name: page.title || config.title,
|
|
852
|
+
url: pageUrl
|
|
853
|
+
};
|
|
854
|
+
if (page.description) pageSchema.description = page.description;
|
|
855
|
+
if (pageType === "Article") {
|
|
856
|
+
pageSchema.headline = page.title || config.title;
|
|
857
|
+
pageSchema.author = {
|
|
858
|
+
"@type": "Organization",
|
|
859
|
+
name: config.schema.organization.name
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
schemas.push(pageSchema);
|
|
863
|
+
if (page.pathname !== "/") {
|
|
864
|
+
const breadcrumbs = generateBreadcrumbs(page.pathname, config);
|
|
865
|
+
if (breadcrumbs.length > 1) {
|
|
866
|
+
schemas.push({
|
|
867
|
+
"@context": "https://schema.org",
|
|
868
|
+
"@type": "BreadcrumbList",
|
|
869
|
+
itemListElement: breadcrumbs.map((crumb, i) => ({
|
|
870
|
+
"@type": "ListItem",
|
|
871
|
+
position: i + 1,
|
|
872
|
+
name: crumb.name,
|
|
873
|
+
item: crumb.url
|
|
874
|
+
}))
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return schemas;
|
|
879
|
+
}
|
|
880
|
+
function generateBreadcrumbs(pathname, config) {
|
|
881
|
+
const baseUrl = config.url.replace(/\/$/, "");
|
|
882
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
883
|
+
const crumbs = [
|
|
884
|
+
{ name: "Home", url: baseUrl + "/" }
|
|
885
|
+
];
|
|
886
|
+
let currentPath = "";
|
|
887
|
+
for (const part of parts) {
|
|
888
|
+
currentPath += "/" + part;
|
|
889
|
+
crumbs.push({
|
|
890
|
+
name: part.charAt(0).toUpperCase() + part.slice(1).replace(/-/g, " "),
|
|
891
|
+
url: baseUrl + currentPath
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
return crumbs;
|
|
895
|
+
}
|
|
896
|
+
function detectFaqPatterns(content) {
|
|
897
|
+
const items = [];
|
|
898
|
+
const lines = content.split("\n");
|
|
899
|
+
for (let i = 0; i < lines.length; i++) {
|
|
900
|
+
const line = lines[i].trim();
|
|
901
|
+
const headingMatch = line.match(/^#{1,6}\s+(.+\?)\s*$/);
|
|
902
|
+
if (headingMatch) {
|
|
903
|
+
const answerLines = [];
|
|
904
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
905
|
+
const nextLine = lines[j].trim();
|
|
906
|
+
if (!nextLine) {
|
|
907
|
+
if (answerLines.length > 0) break;
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (/^#{1,6}\s/.test(nextLine)) break;
|
|
911
|
+
answerLines.push(nextLine);
|
|
912
|
+
}
|
|
913
|
+
if (answerLines.length > 0) {
|
|
914
|
+
items.push({
|
|
915
|
+
question: headingMatch[1],
|
|
916
|
+
answer: answerLines.join(" ").slice(0, 500)
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return items;
|
|
922
|
+
}
|
|
923
|
+
async function generateAEOFiles(configOrRoot, maybeConfig) {
|
|
924
|
+
var _a;
|
|
925
|
+
let config;
|
|
926
|
+
if (typeof configOrRoot === "string") {
|
|
927
|
+
config = resolveConfig({ ...maybeConfig, outDir: configOrRoot });
|
|
928
|
+
} else if (configOrRoot && typeof configOrRoot === "object" && "generators" in configOrRoot && typeof ((_a = configOrRoot.generators) == null ? void 0 : _a.robotsTxt) === "boolean") {
|
|
929
|
+
config = configOrRoot;
|
|
930
|
+
} else {
|
|
931
|
+
config = resolveConfig(configOrRoot);
|
|
932
|
+
}
|
|
933
|
+
const outDir = config.outDir;
|
|
934
|
+
const files = [];
|
|
935
|
+
const errors = [];
|
|
936
|
+
if (!fs.existsSync(outDir)) {
|
|
937
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
938
|
+
}
|
|
939
|
+
if (config.generators.robotsTxt) {
|
|
940
|
+
try {
|
|
941
|
+
const content = generateRobotsTxt(config);
|
|
942
|
+
fs.writeFileSync(path.join(outDir, "robots.txt"), content, "utf-8");
|
|
943
|
+
files.push("robots.txt");
|
|
944
|
+
} catch (e) {
|
|
945
|
+
errors.push(`robots.txt: ${e.message}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (config.generators.llmsTxt) {
|
|
949
|
+
try {
|
|
950
|
+
const content = generateLlmsTxt(config);
|
|
951
|
+
fs.writeFileSync(path.join(outDir, "llms.txt"), content, "utf-8");
|
|
952
|
+
files.push("llms.txt");
|
|
953
|
+
} catch (e) {
|
|
954
|
+
errors.push(`llms.txt: ${e.message}`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (config.generators.llmsFullTxt) {
|
|
958
|
+
try {
|
|
959
|
+
const content = generateLlmsFullTxt(config);
|
|
960
|
+
fs.writeFileSync(path.join(outDir, "llms-full.txt"), content, "utf-8");
|
|
961
|
+
files.push("llms-full.txt");
|
|
962
|
+
} catch (e) {
|
|
963
|
+
errors.push(`llms-full.txt: ${e.message}`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (config.generators.rawMarkdown) {
|
|
967
|
+
try {
|
|
968
|
+
const generated = generatePageMarkdownFiles(config);
|
|
969
|
+
for (const f of generated) {
|
|
970
|
+
files.push(f.destination);
|
|
971
|
+
}
|
|
972
|
+
} catch (e) {
|
|
973
|
+
errors.push(`page-markdown: ${e.message}`);
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const copied = copyMarkdownFiles(config);
|
|
977
|
+
for (const f of copied) {
|
|
978
|
+
files.push(f.destination);
|
|
979
|
+
}
|
|
980
|
+
} catch (e) {
|
|
981
|
+
errors.push(`raw-markdown: ${e.message}`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (config.generators.manifest) {
|
|
985
|
+
try {
|
|
986
|
+
const content = generateManifest(config);
|
|
987
|
+
fs.writeFileSync(path.join(outDir, "docs.json"), content, "utf-8");
|
|
988
|
+
files.push("docs.json");
|
|
989
|
+
} catch (e) {
|
|
990
|
+
errors.push(`docs.json: ${e.message}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
if (config.generators.sitemap) {
|
|
994
|
+
try {
|
|
995
|
+
const content = generateSitemap(config);
|
|
996
|
+
fs.writeFileSync(path.join(outDir, "sitemap.xml"), content, "utf-8");
|
|
997
|
+
files.push("sitemap.xml");
|
|
998
|
+
} catch (e) {
|
|
999
|
+
errors.push(`sitemap.xml: ${e.message}`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (config.generators.aiIndex) {
|
|
1003
|
+
try {
|
|
1004
|
+
const content = generateAIIndex(config);
|
|
1005
|
+
fs.writeFileSync(path.join(outDir, "ai-index.json"), content, "utf-8");
|
|
1006
|
+
files.push("ai-index.json");
|
|
1007
|
+
} catch (e) {
|
|
1008
|
+
errors.push(`ai-index.json: ${e.message}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (config.generators.schema && config.schema.enabled) {
|
|
1012
|
+
try {
|
|
1013
|
+
const content = generateSchema(config);
|
|
1014
|
+
fs.writeFileSync(path.join(outDir, "schema.json"), content, "utf-8");
|
|
1015
|
+
files.push("schema.json");
|
|
1016
|
+
} catch (e) {
|
|
1017
|
+
errors.push(`schema.json: ${e.message}`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return { files, errors };
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/core/html-extract.ts
|
|
1024
|
+
function extractTextFromHtml(html) {
|
|
1025
|
+
let text = html;
|
|
1026
|
+
text = text.replace(/<script[\s\S]*?<\/script>/gi, "");
|
|
1027
|
+
text = text.replace(/<style[\s\S]*?<\/style>/gi, "");
|
|
1028
|
+
text = text.replace(/<svg[\s\S]*?<\/svg>/gi, "");
|
|
1029
|
+
const mainMatch = text.match(/<main[^>]*>([\s\S]*)<\/main>/i);
|
|
1030
|
+
if (mainMatch) {
|
|
1031
|
+
text = mainMatch[1];
|
|
1032
|
+
} else {
|
|
1033
|
+
text = text.replace(/<nav[\s\S]*?<\/nav>/gi, "");
|
|
1034
|
+
text = text.replace(/<header[\s\S]*?<\/header>/gi, "");
|
|
1035
|
+
text = text.replace(/<footer[\s\S]*?<\/footer>/gi, "");
|
|
1036
|
+
}
|
|
1037
|
+
text = text.replace(/<a[^>]+href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, url, inner) => {
|
|
1038
|
+
if (/<(?:h[1-6]|div|p|section)[^>]*>/i.test(inner)) {
|
|
1039
|
+
const cleanInner = inner.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
1040
|
+
return `
|
|
1041
|
+
[${cleanInner.slice(0, 120).trim()}](${url})
|
|
1042
|
+
`;
|
|
1043
|
+
}
|
|
1044
|
+
return `[${inner}](${url})`;
|
|
1045
|
+
});
|
|
1046
|
+
text = text.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "\n\n## $1\n\n");
|
|
1047
|
+
text = text.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, "\n\n## $1\n\n");
|
|
1048
|
+
text = text.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, "\n\n### $1\n\n");
|
|
1049
|
+
text = text.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, "\n\n#### $1\n\n");
|
|
1050
|
+
text = text.replace(/<h5[^>]*>([\s\S]*?)<\/h5>/gi, "\n\n##### $1\n\n");
|
|
1051
|
+
text = text.replace(/<h6[^>]*>([\s\S]*?)<\/h6>/gi, "\n\n###### $1\n\n");
|
|
1052
|
+
text = text.replace(/<a[^>]+href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)");
|
|
1053
|
+
text = text.replace(/<(?:strong|b)[^>]*>([\s\S]*?)<\/(?:strong|b)>/gi, "**$1**");
|
|
1054
|
+
text = text.replace(/<(?:em|i)[^>]*>([\s\S]*?)<\/(?:em|i)>/gi, "*$1*");
|
|
1055
|
+
text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "\n- $1");
|
|
1056
|
+
text = text.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, "\n\n> $1\n\n");
|
|
1057
|
+
text = text.replace(/<hr[^>]*\/?>/gi, "\n\n---\n\n");
|
|
1058
|
+
text = text.replace(/<br[^>]*\/?>/gi, "\n");
|
|
1059
|
+
text = text.replace(/<\/p>/gi, "\n\n");
|
|
1060
|
+
text = text.replace(/<p[^>]*>/gi, "");
|
|
1061
|
+
text = text.replace(/<\/?(?:div|section|article|header|main|aside|figure|figcaption|table|thead|tbody|tr|td|th|ul|ol|dl|dt|dd)[^>]*>/gi, "\n");
|
|
1062
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
1063
|
+
text = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ").replace(/©/g, "(c)");
|
|
1064
|
+
text = text.replace(/[\u{1F1E0}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE00}-\u{FE0F}\u{200D}\u{20E3}]/gu, "");
|
|
1065
|
+
text = text.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).join("\n");
|
|
1066
|
+
text = text.replace(/\n{3,}/g, "\n\n");
|
|
1067
|
+
text = text.replace(/\[[\s\n]+/g, "[").replace(/[\s\n]+\]/g, "]");
|
|
1068
|
+
text = text.replace(/(#{2,6})\s*\n+\s*/g, "$1 ");
|
|
1069
|
+
text = text.replace(/^#{2,6}\s*$/gm, "");
|
|
1070
|
+
text = text.replace(/\n{3,}/g, "\n\n");
|
|
1071
|
+
return text.trim().slice(0, 8e3);
|
|
1072
|
+
}
|
|
1073
|
+
function extractTitle2(html) {
|
|
1074
|
+
var _a, _b;
|
|
1075
|
+
const match = html.match(/<title>([^<]*)<\/title>/i);
|
|
1076
|
+
if (!match) return void 0;
|
|
1077
|
+
return ((_b = (_a = match[1]) == null ? void 0 : _a.split("|")[0]) == null ? void 0 : _b.trim()) || match[1];
|
|
1078
|
+
}
|
|
1079
|
+
function extractDescription(html) {
|
|
1080
|
+
const match = html.match(/<meta\s+name=["']description["']\s+content=["']([^"']*)["']/i);
|
|
1081
|
+
return match == null ? void 0 : match[1];
|
|
1082
|
+
}
|
|
1083
|
+
function scanAngularRoutes(projectRoot) {
|
|
1084
|
+
const pages = [];
|
|
1085
|
+
const srcDir = path.join(projectRoot, "src", "app");
|
|
1086
|
+
if (!fs.existsSync(srcDir)) {
|
|
1087
|
+
pages.push({ pathname: "/" });
|
|
1088
|
+
return pages;
|
|
1089
|
+
}
|
|
1090
|
+
function walk(dir, basePath = "") {
|
|
1091
|
+
try {
|
|
1092
|
+
const entries = fs.readdirSync(dir);
|
|
1093
|
+
for (const entry of entries) {
|
|
1094
|
+
const fullPath = path.join(dir, entry);
|
|
1095
|
+
const stat = fs.statSync(fullPath);
|
|
1096
|
+
if (stat.isDirectory() && !entry.startsWith(".") && !entry.startsWith("_") && entry !== "node_modules") {
|
|
1097
|
+
walk(fullPath, `${basePath}/${entry}`);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
for (const entry of entries) {
|
|
1101
|
+
if (entry.match(/\.routes\.ts$/)) {
|
|
1102
|
+
try {
|
|
1103
|
+
const content = fs.readFileSync(path.join(dir, entry), "utf-8");
|
|
1104
|
+
const pathMatches = content.matchAll(/path:\s*['"]([^'"]*)['"]/g);
|
|
1105
|
+
for (const match of pathMatches) {
|
|
1106
|
+
const routePath = match[1];
|
|
1107
|
+
if (routePath === "**" || routePath.startsWith(":")) continue;
|
|
1108
|
+
const pathname = routePath === "" ? "/" : `${basePath}/${routePath}`.replace(/\/+/g, "/");
|
|
1109
|
+
const name = routePath || "home";
|
|
1110
|
+
if (!pages.some((p) => p.pathname === pathname)) {
|
|
1111
|
+
pages.push({
|
|
1112
|
+
pathname,
|
|
1113
|
+
title: name === "home" ? void 0 : name.charAt(0).toUpperCase() + name.slice(1).replace(/-/g, " ")
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
} catch {
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
for (const entry of entries) {
|
|
1122
|
+
const fullPath = path.join(dir, entry);
|
|
1123
|
+
const stat = fs.statSync(fullPath);
|
|
1124
|
+
if (stat.isDirectory() && !entry.startsWith(".")) {
|
|
1125
|
+
const componentFile = fs.readdirSync(fullPath).find((f) => f.match(/\.component\.ts$/));
|
|
1126
|
+
if (componentFile && entry !== "app" && entry !== "shared" && entry !== "core" && entry !== "components" && entry !== "services" && entry !== "models" && entry !== "guards" && entry !== "interceptors" && entry !== "pipes" && entry !== "directives") {
|
|
1127
|
+
const pathname = `${basePath}/${entry}`.replace(/\/+/g, "/");
|
|
1128
|
+
if (!pages.some((p) => p.pathname === pathname)) {
|
|
1129
|
+
pages.push({
|
|
1130
|
+
pathname,
|
|
1131
|
+
title: entry.charAt(0).toUpperCase() + entry.slice(1).replace(/-/g, " ")
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
} catch {
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
walk(srcDir);
|
|
1141
|
+
if (!pages.some((p) => p.pathname === "/")) {
|
|
1142
|
+
pages.unshift({ pathname: "/" });
|
|
1143
|
+
}
|
|
1144
|
+
return pages;
|
|
1145
|
+
}
|
|
1146
|
+
function detectAngularOutputDir(projectRoot) {
|
|
1147
|
+
var _a, _b, _c;
|
|
1148
|
+
const angularJsonPath = path.join(projectRoot, "angular.json");
|
|
1149
|
+
if (!fs.existsSync(angularJsonPath)) return path.join(projectRoot, "dist");
|
|
1150
|
+
try {
|
|
1151
|
+
const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, "utf-8"));
|
|
1152
|
+
const defaultProject = angularJson.defaultProject || Object.keys(angularJson.projects || {})[0];
|
|
1153
|
+
if (!defaultProject) return path.join(projectRoot, "dist");
|
|
1154
|
+
const project = angularJson.projects[defaultProject];
|
|
1155
|
+
const buildTarget = ((_a = project == null ? void 0 : project.architect) == null ? void 0 : _a.build) || ((_b = project == null ? void 0 : project.targets) == null ? void 0 : _b.build);
|
|
1156
|
+
if ((_c = buildTarget == null ? void 0 : buildTarget.options) == null ? void 0 : _c.outputPath) {
|
|
1157
|
+
const outputPath = buildTarget.options.outputPath;
|
|
1158
|
+
if (typeof outputPath === "object" && outputPath.base) {
|
|
1159
|
+
return path.join(projectRoot, outputPath.base, "browser");
|
|
1160
|
+
}
|
|
1161
|
+
return path.join(projectRoot, outputPath);
|
|
1162
|
+
}
|
|
1163
|
+
return path.join(projectRoot, "dist", defaultProject);
|
|
1164
|
+
} catch {
|
|
1165
|
+
return path.join(projectRoot, "dist");
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function scanAngularBuildOutput(outputDir) {
|
|
1169
|
+
const pages = [];
|
|
1170
|
+
if (!fs.existsSync(outputDir)) return pages;
|
|
1171
|
+
function walk(dir, basePath = "") {
|
|
1172
|
+
try {
|
|
1173
|
+
const entries = fs.readdirSync(dir);
|
|
1174
|
+
for (const entry of entries) {
|
|
1175
|
+
const fullPath = path.join(dir, entry);
|
|
1176
|
+
const stat = fs.statSync(fullPath);
|
|
1177
|
+
if (stat.isDirectory() && !entry.startsWith(".") && entry !== "assets" && entry !== "media") {
|
|
1178
|
+
walk(fullPath, `${basePath}/${entry}`);
|
|
1179
|
+
} else if (entry.endsWith(".html") && entry !== "404.html" && entry !== "500.html") {
|
|
1180
|
+
try {
|
|
1181
|
+
const html = fs.readFileSync(fullPath, "utf-8");
|
|
1182
|
+
const title = extractTitle2(html);
|
|
1183
|
+
const description = extractDescription(html);
|
|
1184
|
+
const textContent = extractTextFromHtml(html);
|
|
1185
|
+
let pathname;
|
|
1186
|
+
if (entry === "index.html") {
|
|
1187
|
+
pathname = basePath || "/";
|
|
1188
|
+
} else {
|
|
1189
|
+
pathname = `${basePath}/${entry.replace(".html", "")}`;
|
|
1190
|
+
}
|
|
1191
|
+
pages.push({
|
|
1192
|
+
pathname,
|
|
1193
|
+
title,
|
|
1194
|
+
description,
|
|
1195
|
+
content: textContent
|
|
1196
|
+
});
|
|
1197
|
+
} catch {
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
} catch {
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
walk(outputDir);
|
|
1205
|
+
return pages;
|
|
1206
|
+
}
|
|
1207
|
+
function getWidgetScript(config = {}) {
|
|
1208
|
+
const resolvedConfig = resolveConfig(config);
|
|
1209
|
+
if (!resolvedConfig.widget.enabled) return "";
|
|
1210
|
+
const widgetConfig = JSON.stringify({
|
|
1211
|
+
title: resolvedConfig.title,
|
|
1212
|
+
description: resolvedConfig.description,
|
|
1213
|
+
url: resolvedConfig.url,
|
|
1214
|
+
widget: resolvedConfig.widget
|
|
1215
|
+
});
|
|
1216
|
+
return `<script type="module">
|
|
1217
|
+
import('aeo.js/widget').then(({ AeoWidget }) => {
|
|
1218
|
+
try {
|
|
1219
|
+
new AeoWidget({ config: ${widgetConfig} });
|
|
1220
|
+
} catch (e) {
|
|
1221
|
+
console.warn('[aeo.js] Widget initialization failed:', e);
|
|
1222
|
+
}
|
|
1223
|
+
}).catch(() => {});
|
|
1224
|
+
</script>`;
|
|
1225
|
+
}
|
|
1226
|
+
async function postBuild(config = {}) {
|
|
1227
|
+
const projectRoot = process.cwd();
|
|
1228
|
+
const outputDir = config.outDir || detectAngularOutputDir(projectRoot);
|
|
1229
|
+
console.log(`[aeo.js] Scanning Angular build output: ${outputDir}`);
|
|
1230
|
+
const buildPages = scanAngularBuildOutput(outputDir);
|
|
1231
|
+
if (buildPages.length > 0) {
|
|
1232
|
+
console.log(`[aeo.js] Discovered ${buildPages.length} pages from Angular build output`);
|
|
1233
|
+
}
|
|
1234
|
+
const sourcePages = scanAngularRoutes(projectRoot);
|
|
1235
|
+
const allPages = [...buildPages, ...sourcePages, ...config.pages || []];
|
|
1236
|
+
const pageMap = /* @__PURE__ */ new Map();
|
|
1237
|
+
for (const page of allPages) {
|
|
1238
|
+
const existing = pageMap.get(page.pathname);
|
|
1239
|
+
if (!existing || page.content && !existing.content) {
|
|
1240
|
+
pageMap.set(page.pathname, page);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
for (const page of pageMap.values()) {
|
|
1244
|
+
if (page.pathname === "/" && !page.title && config.title) {
|
|
1245
|
+
page.title = config.title;
|
|
1246
|
+
}
|
|
1247
|
+
if (!page.description && config.description) {
|
|
1248
|
+
page.description = config.description;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const resolvedConfig = resolveConfig({
|
|
1252
|
+
...config,
|
|
1253
|
+
outDir: outputDir,
|
|
1254
|
+
pages: Array.from(pageMap.values())
|
|
1255
|
+
});
|
|
1256
|
+
const result = await generateAEOFiles(resolvedConfig);
|
|
1257
|
+
if (result.files.length > 0) {
|
|
1258
|
+
console.log(`[aeo.js] Generated ${result.files.length} files`);
|
|
1259
|
+
}
|
|
1260
|
+
if (result.errors.length > 0) {
|
|
1261
|
+
console.error("[aeo.js] Errors:", result.errors);
|
|
1262
|
+
}
|
|
1263
|
+
if (config.injectWidget !== false && resolvedConfig.widget.enabled) {
|
|
1264
|
+
const indexPath = path.join(outputDir, "index.html");
|
|
1265
|
+
if (fs.existsSync(indexPath)) {
|
|
1266
|
+
try {
|
|
1267
|
+
let html = fs.readFileSync(indexPath, "utf-8");
|
|
1268
|
+
if (!html.includes("aeo.js/widget")) {
|
|
1269
|
+
const script = getWidgetScript(config);
|
|
1270
|
+
html = html.replace("</body>", `${script}
|
|
1271
|
+
</body>`);
|
|
1272
|
+
const { writeFileSync: writeFileSync3 } = await import('fs');
|
|
1273
|
+
writeFileSync3(indexPath, html, "utf-8");
|
|
1274
|
+
console.log("[aeo.js] Injected widget into index.html");
|
|
1275
|
+
}
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
console.warn("[aeo.js] Could not inject widget into index.html:", error);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
async function generate(config = {}) {
|
|
1283
|
+
const projectRoot = process.cwd();
|
|
1284
|
+
const discoveredPages = scanAngularRoutes(projectRoot);
|
|
1285
|
+
if (discoveredPages.length > 0) {
|
|
1286
|
+
console.log(`[aeo.js] Discovered ${discoveredPages.length} routes from Angular source`);
|
|
1287
|
+
}
|
|
1288
|
+
for (const page of discoveredPages) {
|
|
1289
|
+
if (page.pathname === "/" && !page.title && config.title) {
|
|
1290
|
+
page.title = config.title;
|
|
1291
|
+
}
|
|
1292
|
+
if (!page.description && config.description) {
|
|
1293
|
+
page.description = config.description;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const resolvedConfig = resolveConfig({
|
|
1297
|
+
...config,
|
|
1298
|
+
outDir: config.outDir || "public",
|
|
1299
|
+
pages: [...config.pages || [], ...discoveredPages]
|
|
1300
|
+
});
|
|
1301
|
+
const result = await generateAEOFiles(resolvedConfig);
|
|
1302
|
+
if (result.files.length > 0) {
|
|
1303
|
+
console.log(`[aeo.js] Generated ${result.files.length} files`);
|
|
1304
|
+
}
|
|
1305
|
+
if (result.errors.length > 0) {
|
|
1306
|
+
console.error("[aeo.js] Errors:", result.errors);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
exports.generate = generate;
|
|
1311
|
+
exports.getWidgetScript = getWidgetScript;
|
|
1312
|
+
exports.postBuild = postBuild;
|
|
1313
|
+
//# sourceMappingURL=angular.js.map
|
|
1314
|
+
//# sourceMappingURL=angular.js.map
|