@pas7/llm-seo 0.1.6
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/LICENSE +201 -0
- package/README.md +164 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.js +673 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/next/index.d.ts +292 -0
- package/dist/adapters/next/index.js +673 -0
- package/dist/adapters/next/index.js.map +1 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +2232 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/config.schema-DCnBx3Gm.d.ts +824 -0
- package/dist/core/index.d.ts +752 -0
- package/dist/core/index.js +1330 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1909 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.schema-B_z3rxRV.d.ts +384 -0
- package/dist/schema/index.d.ts +72 -0
- package/dist/schema/index.js +422 -0
- package/dist/schema/index.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { readFile, readdir } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
// src/adapters/next/manifest.ts
|
|
6
|
+
function fromNextContentManifest(manifest, options = {}) {
|
|
7
|
+
const { slugPrefix = "", defaultLocale } = options;
|
|
8
|
+
return manifest.items.map((item) => {
|
|
9
|
+
const result = {
|
|
10
|
+
slug: slugPrefix + item.slug
|
|
11
|
+
};
|
|
12
|
+
if (item.locales !== void 0) {
|
|
13
|
+
result.locales = item.locales;
|
|
14
|
+
} else if (defaultLocale !== void 0) {
|
|
15
|
+
result.locales = [defaultLocale];
|
|
16
|
+
}
|
|
17
|
+
if (item.publishedAt !== void 0) {
|
|
18
|
+
result.publishedAt = item.publishedAt;
|
|
19
|
+
}
|
|
20
|
+
if (item.updatedAt !== void 0) {
|
|
21
|
+
result.updatedAt = item.updatedAt;
|
|
22
|
+
}
|
|
23
|
+
if (item.title !== void 0) {
|
|
24
|
+
result.title = item.title;
|
|
25
|
+
}
|
|
26
|
+
if (item.description !== void 0) {
|
|
27
|
+
result.description = item.description;
|
|
28
|
+
}
|
|
29
|
+
if (item.priority !== void 0) {
|
|
30
|
+
result.priority = item.priority;
|
|
31
|
+
}
|
|
32
|
+
if (item.canonicalOverride !== void 0) {
|
|
33
|
+
result.canonicalOverride = item.canonicalOverride;
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function parseFrontmatter(content) {
|
|
39
|
+
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
|
|
40
|
+
const match = content.match(frontmatterRegex);
|
|
41
|
+
if (!match) {
|
|
42
|
+
return { frontmatter: {}, body: content };
|
|
43
|
+
}
|
|
44
|
+
const frontmatterText = match[1] ?? "";
|
|
45
|
+
const body = match[2] ?? content;
|
|
46
|
+
const frontmatter = {};
|
|
47
|
+
const lines = frontmatterText.split("\n");
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const colonIndex = line.indexOf(":");
|
|
50
|
+
if (colonIndex === -1) continue;
|
|
51
|
+
const key = line.slice(0, colonIndex).trim();
|
|
52
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
53
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
54
|
+
value = value.slice(1, -1).split(",").map((item) => item.trim().replace(/^['"]|['"]$/g, "")).filter((item) => item.length > 0);
|
|
55
|
+
} else {
|
|
56
|
+
value = value.replace(/^['"]|['"]$/g, "");
|
|
57
|
+
}
|
|
58
|
+
if (key === "title") frontmatter.title = value;
|
|
59
|
+
else if (key === "description") frontmatter.description = value;
|
|
60
|
+
else if (key === "date" || key === "publishedAt") frontmatter.date = value;
|
|
61
|
+
else if (key === "updated" || key === "updatedAt") frontmatter.updated = value;
|
|
62
|
+
else if (key === "locale") frontmatter.locale = value;
|
|
63
|
+
else if (key === "locales") frontmatter.locales = value;
|
|
64
|
+
else if (key === "priority") frontmatter.priority = parseInt(value, 10);
|
|
65
|
+
else frontmatter[key] = value;
|
|
66
|
+
}
|
|
67
|
+
return { frontmatter, body };
|
|
68
|
+
}
|
|
69
|
+
function filePathToSlug(filePath, extensions) {
|
|
70
|
+
let slug = filePath;
|
|
71
|
+
for (const ext of extensions) {
|
|
72
|
+
if (slug.endsWith(ext)) {
|
|
73
|
+
slug = slug.slice(0, -ext.length);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (slug.endsWith("/index")) {
|
|
78
|
+
slug = slug.slice(0, -6);
|
|
79
|
+
}
|
|
80
|
+
if (slug === "index") {
|
|
81
|
+
slug = "";
|
|
82
|
+
}
|
|
83
|
+
if (!slug.startsWith("/")) {
|
|
84
|
+
slug = "/" + slug;
|
|
85
|
+
}
|
|
86
|
+
return slug || "/";
|
|
87
|
+
}
|
|
88
|
+
async function scanDirectory(dir, extensions, basePath = "") {
|
|
89
|
+
const files = [];
|
|
90
|
+
if (!existsSync(dir)) {
|
|
91
|
+
return files;
|
|
92
|
+
}
|
|
93
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const fullPath = join(dir, entry.name);
|
|
96
|
+
const relativePath = basePath ? join(basePath, entry.name) : entry.name;
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
const subFiles = await scanDirectory(fullPath, extensions, relativePath);
|
|
99
|
+
files.push(...subFiles);
|
|
100
|
+
} else if (entry.isFile()) {
|
|
101
|
+
const hasValidExtension = extensions.some((ext) => entry.name.endsWith(ext));
|
|
102
|
+
if (hasValidExtension) {
|
|
103
|
+
files.push(relativePath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return files;
|
|
108
|
+
}
|
|
109
|
+
async function createManifestFromPagesDir(options) {
|
|
110
|
+
const {
|
|
111
|
+
pagesDir,
|
|
112
|
+
routePrefix = "",
|
|
113
|
+
defaultLocale,
|
|
114
|
+
extensions = [".mdx", ".md"]
|
|
115
|
+
} = options;
|
|
116
|
+
const files = await scanDirectory(pagesDir, extensions);
|
|
117
|
+
const items = [];
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
const fullPath = join(pagesDir, file);
|
|
120
|
+
const content = await readFile(fullPath, "utf-8");
|
|
121
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
122
|
+
const slug = routePrefix + filePathToSlug(file, extensions);
|
|
123
|
+
const item = {
|
|
124
|
+
slug
|
|
125
|
+
};
|
|
126
|
+
if (frontmatter.locales !== void 0) {
|
|
127
|
+
item.locales = frontmatter.locales;
|
|
128
|
+
} else if (frontmatter.locale !== void 0) {
|
|
129
|
+
item.locales = [frontmatter.locale];
|
|
130
|
+
} else if (defaultLocale !== void 0) {
|
|
131
|
+
item.locales = [defaultLocale];
|
|
132
|
+
}
|
|
133
|
+
if (frontmatter.date !== void 0) {
|
|
134
|
+
item.publishedAt = frontmatter.date;
|
|
135
|
+
}
|
|
136
|
+
if (frontmatter.updated !== void 0) {
|
|
137
|
+
item.updatedAt = frontmatter.updated;
|
|
138
|
+
}
|
|
139
|
+
if (frontmatter.title !== void 0) {
|
|
140
|
+
item.title = frontmatter.title;
|
|
141
|
+
}
|
|
142
|
+
if (frontmatter.description !== void 0) {
|
|
143
|
+
item.description = frontmatter.description;
|
|
144
|
+
}
|
|
145
|
+
if (frontmatter.priority !== void 0) {
|
|
146
|
+
item.priority = frontmatter.priority;
|
|
147
|
+
}
|
|
148
|
+
items.push(item);
|
|
149
|
+
}
|
|
150
|
+
return items.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
151
|
+
}
|
|
152
|
+
function createManifestFromData(items, options = {}) {
|
|
153
|
+
const { slugPrefix = "", defaultLocale } = options;
|
|
154
|
+
return items.map((item) => {
|
|
155
|
+
const slugPath = Array.isArray(item.params.slug) ? "/" + item.params.slug.join("/") : item.params.slug.startsWith("/") ? item.params.slug : "/" + item.params.slug;
|
|
156
|
+
const result = {
|
|
157
|
+
slug: slugPrefix + slugPath
|
|
158
|
+
};
|
|
159
|
+
if (item.locale !== void 0) {
|
|
160
|
+
result.locales = [item.locale];
|
|
161
|
+
} else if (defaultLocale !== void 0) {
|
|
162
|
+
result.locales = [defaultLocale];
|
|
163
|
+
}
|
|
164
|
+
if (item.publishedAt !== void 0) {
|
|
165
|
+
result.publishedAt = item.publishedAt;
|
|
166
|
+
}
|
|
167
|
+
if (item.updatedAt !== void 0) {
|
|
168
|
+
result.updatedAt = item.updatedAt;
|
|
169
|
+
}
|
|
170
|
+
if (item.title !== void 0) {
|
|
171
|
+
result.title = item.title;
|
|
172
|
+
}
|
|
173
|
+
if (item.description !== void 0) {
|
|
174
|
+
result.description = item.description;
|
|
175
|
+
}
|
|
176
|
+
if (item.priority !== void 0) {
|
|
177
|
+
result.priority = item.priority;
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function extractPagePaths(buildManifest) {
|
|
183
|
+
const pages = [];
|
|
184
|
+
for (const pagePath of Object.keys(buildManifest.pages)) {
|
|
185
|
+
if (pagePath.startsWith("/_")) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const normalizedPath = normalizeNextPath(pagePath);
|
|
189
|
+
pages.push(normalizedPath);
|
|
190
|
+
}
|
|
191
|
+
return pages.sort((a, b) => a.localeCompare(b));
|
|
192
|
+
}
|
|
193
|
+
function normalizeNextPath(path) {
|
|
194
|
+
let normalized = path.replace(/\.(html|json)$/, "");
|
|
195
|
+
if (normalized === "/index" || normalized === "") {
|
|
196
|
+
normalized = "/";
|
|
197
|
+
}
|
|
198
|
+
if (!normalized.startsWith("/")) {
|
|
199
|
+
normalized = `/${normalized}`;
|
|
200
|
+
}
|
|
201
|
+
return normalized;
|
|
202
|
+
}
|
|
203
|
+
function generateNextManifest(options, buildManifest) {
|
|
204
|
+
const pages = [];
|
|
205
|
+
if (buildManifest) {
|
|
206
|
+
const pagePaths = extractPagePaths(buildManifest);
|
|
207
|
+
for (const path of pagePaths) {
|
|
208
|
+
pages.push({
|
|
209
|
+
path,
|
|
210
|
+
title: void 0,
|
|
211
|
+
// Will be filled by crawler or manually
|
|
212
|
+
description: void 0,
|
|
213
|
+
optional: false
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
baseUrl: options.baseUrl,
|
|
219
|
+
title: options.title,
|
|
220
|
+
description: options.description,
|
|
221
|
+
pages
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/core/normalize/sort.ts
|
|
226
|
+
function compareStrings(a, b) {
|
|
227
|
+
return a.localeCompare(b, "en", { sensitivity: "case", numeric: true });
|
|
228
|
+
}
|
|
229
|
+
function sortStrings(items) {
|
|
230
|
+
return [...items].sort(compareStrings);
|
|
231
|
+
}
|
|
232
|
+
function sortBy(items, keyFn) {
|
|
233
|
+
return [...items].sort((a, b) => compareStrings(keyFn(a), keyFn(b)));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/core/normalize/text.ts
|
|
237
|
+
function normalizeLineEndings(text, lineEndings) {
|
|
238
|
+
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
239
|
+
return lineEndings === "crlf" ? normalized.replace(/\n/g, "\r\n") : normalized;
|
|
240
|
+
}
|
|
241
|
+
function normalizeLineWhitespace(text) {
|
|
242
|
+
const lines = text.split(/\r?\n/);
|
|
243
|
+
return lines.map((line) => line.trimEnd().replace(/[ \t]+/g, (match) => " ".repeat(match.length === 0 ? 0 : match.length))).map((line) => line.replace(/ +/g, " ")).join("\n");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/core/generate/llms-txt.ts
|
|
247
|
+
function createLlmsTxt(options) {
|
|
248
|
+
const { config, canonicalUrls } = options;
|
|
249
|
+
const lineEndings = config.format?.lineEndings ?? "lf";
|
|
250
|
+
const lines = [];
|
|
251
|
+
lines.push(`# ${config.brand.name}`);
|
|
252
|
+
lines.push("");
|
|
253
|
+
if (config.brand.tagline) {
|
|
254
|
+
lines.push(`> ${config.brand.tagline}`);
|
|
255
|
+
lines.push("");
|
|
256
|
+
}
|
|
257
|
+
if (config.brand.description) {
|
|
258
|
+
lines.push(config.brand.description);
|
|
259
|
+
lines.push("");
|
|
260
|
+
}
|
|
261
|
+
const hubs = config.sections?.hubs ?? [];
|
|
262
|
+
if (hubs.length > 0) {
|
|
263
|
+
lines.push("## Sections");
|
|
264
|
+
lines.push("");
|
|
265
|
+
const sortedHubs = sortStrings(hubs);
|
|
266
|
+
for (const hub of sortedHubs) {
|
|
267
|
+
const hubLabel = getHubLabel(hub);
|
|
268
|
+
lines.push(`- [${hub}](${hub}) - ${hubLabel}`);
|
|
269
|
+
}
|
|
270
|
+
lines.push("");
|
|
271
|
+
}
|
|
272
|
+
if (canonicalUrls.length > 0) {
|
|
273
|
+
lines.push("## URLs");
|
|
274
|
+
lines.push("");
|
|
275
|
+
const sortedUrls = sortStrings(canonicalUrls);
|
|
276
|
+
for (const url of sortedUrls) {
|
|
277
|
+
lines.push(`- ${url}`);
|
|
278
|
+
}
|
|
279
|
+
lines.push("");
|
|
280
|
+
}
|
|
281
|
+
const hasPolicies = config.policy?.geoPolicy || config.policy?.citationRules || config.policy?.restrictedClaims;
|
|
282
|
+
if (hasPolicies) {
|
|
283
|
+
lines.push("## Policies");
|
|
284
|
+
lines.push("");
|
|
285
|
+
if (config.policy?.geoPolicy) {
|
|
286
|
+
lines.push(`- GEO: ${config.policy.geoPolicy}`);
|
|
287
|
+
}
|
|
288
|
+
if (config.policy?.citationRules) {
|
|
289
|
+
lines.push(`- Citations: ${config.policy.citationRules}`);
|
|
290
|
+
}
|
|
291
|
+
if (config.policy?.restrictedClaims) {
|
|
292
|
+
const status = config.policy.restrictedClaims.enable ? "Enabled" : "Disabled";
|
|
293
|
+
lines.push(`- Restricted Claims: ${status}`);
|
|
294
|
+
}
|
|
295
|
+
lines.push("");
|
|
296
|
+
}
|
|
297
|
+
const hasContact = config.contact?.email || config.contact?.social || config.contact?.phone;
|
|
298
|
+
const hasBooking = config.booking?.url;
|
|
299
|
+
if (hasContact || hasBooking) {
|
|
300
|
+
lines.push("## Contact");
|
|
301
|
+
lines.push("");
|
|
302
|
+
if (config.contact?.email) {
|
|
303
|
+
lines.push(`- Email: ${config.contact.email}`);
|
|
304
|
+
}
|
|
305
|
+
if (config.contact?.phone) {
|
|
306
|
+
lines.push(`- Phone: ${config.contact.phone}`);
|
|
307
|
+
}
|
|
308
|
+
if (config.contact?.social) {
|
|
309
|
+
if (config.contact.social.twitter) {
|
|
310
|
+
lines.push(`- Twitter: ${config.contact.social.twitter}`);
|
|
311
|
+
}
|
|
312
|
+
if (config.contact.social.linkedin) {
|
|
313
|
+
lines.push(`- LinkedIn: ${config.contact.social.linkedin}`);
|
|
314
|
+
}
|
|
315
|
+
if (config.contact.social.github) {
|
|
316
|
+
lines.push(`- GitHub: ${config.contact.social.github}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (config.booking?.url) {
|
|
320
|
+
const label = config.booking.label ?? "Book consultation";
|
|
321
|
+
lines.push(`- Booking: ${config.booking.url} (${label})`);
|
|
322
|
+
}
|
|
323
|
+
lines.push("");
|
|
324
|
+
}
|
|
325
|
+
const hasMachineHints = config.machineHints?.robots || config.machineHints?.sitemap || config.machineHints?.llmsTxt || config.machineHints?.llmsFullTxt;
|
|
326
|
+
if (hasMachineHints) {
|
|
327
|
+
lines.push("## Machine Hints");
|
|
328
|
+
lines.push("");
|
|
329
|
+
if (config.machineHints?.robots) {
|
|
330
|
+
lines.push(`- robots.txt: ${config.machineHints.robots}`);
|
|
331
|
+
}
|
|
332
|
+
if (config.machineHints?.sitemap) {
|
|
333
|
+
lines.push(`- sitemap.xml: ${config.machineHints.sitemap}`);
|
|
334
|
+
}
|
|
335
|
+
if (config.machineHints?.llmsTxt) {
|
|
336
|
+
lines.push(`- llms.txt: ${config.machineHints.llmsTxt}`);
|
|
337
|
+
}
|
|
338
|
+
if (config.machineHints?.llmsFullTxt) {
|
|
339
|
+
lines.push(`- llms-full.txt: ${config.machineHints.llmsFullTxt}`);
|
|
340
|
+
}
|
|
341
|
+
lines.push("");
|
|
342
|
+
}
|
|
343
|
+
let content = lines.join("\n");
|
|
344
|
+
content = normalizeLineWhitespace(content);
|
|
345
|
+
content = normalizeLineEndings(content, lineEndings);
|
|
346
|
+
const finalLines = content.split(lineEndings === "crlf" ? "\r\n" : "\n");
|
|
347
|
+
return {
|
|
348
|
+
content,
|
|
349
|
+
byteSize: Buffer.byteLength(content, "utf-8"),
|
|
350
|
+
lineCount: finalLines.length
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function getHubLabel(hub) {
|
|
354
|
+
const labels = {
|
|
355
|
+
"/services": "Services overview",
|
|
356
|
+
"/blog": "Blog posts",
|
|
357
|
+
"/projects": "Our projects",
|
|
358
|
+
"/cases": "Case studies",
|
|
359
|
+
"/contact": "Contact us",
|
|
360
|
+
"/about": "About us",
|
|
361
|
+
"/products": "Products",
|
|
362
|
+
"/docs": "Documentation",
|
|
363
|
+
"/faq": "Frequently asked questions",
|
|
364
|
+
"/pricing": "Pricing information",
|
|
365
|
+
"/team": "Our team",
|
|
366
|
+
"/careers": "Career opportunities",
|
|
367
|
+
"/news": "News and updates",
|
|
368
|
+
"/resources": "Resources",
|
|
369
|
+
"/support": "Support center"
|
|
370
|
+
};
|
|
371
|
+
return labels[hub] ?? formatHubLabel(hub);
|
|
372
|
+
}
|
|
373
|
+
function formatHubLabel(hub) {
|
|
374
|
+
const clean = hub.replace(/^\//, "");
|
|
375
|
+
return clean.replace(/[-_]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
376
|
+
}
|
|
377
|
+
function generateLlmsTxt(manifest, _options) {
|
|
378
|
+
const canonicalUrls = manifest.pages.map((page) => `${manifest.baseUrl}${page.path}`);
|
|
379
|
+
const config = {
|
|
380
|
+
site: { baseUrl: manifest.baseUrl },
|
|
381
|
+
brand: {
|
|
382
|
+
name: manifest.title,
|
|
383
|
+
locales: ["en"],
|
|
384
|
+
...manifest.description && { description: manifest.description }
|
|
385
|
+
},
|
|
386
|
+
manifests: {},
|
|
387
|
+
output: {
|
|
388
|
+
paths: {
|
|
389
|
+
llmsTxt: "public/llms.txt",
|
|
390
|
+
llmsFullTxt: "public/llms-full.txt"
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
return createLlmsTxt({ config, canonicalUrls }).content;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/core/generate/llms-full-txt.ts
|
|
398
|
+
function createLlmsFullTxt(options) {
|
|
399
|
+
const { config, canonicalUrls, manifestItems } = options;
|
|
400
|
+
const lineEndings = config.format?.lineEndings ?? "lf";
|
|
401
|
+
const lines = [];
|
|
402
|
+
lines.push(`# ${config.brand.name} - Full LLM Context`);
|
|
403
|
+
lines.push("");
|
|
404
|
+
if (config.brand.tagline) {
|
|
405
|
+
lines.push(`> ${config.brand.tagline}`);
|
|
406
|
+
lines.push("");
|
|
407
|
+
}
|
|
408
|
+
if (config.brand.description) {
|
|
409
|
+
lines.push(config.brand.description);
|
|
410
|
+
lines.push("");
|
|
411
|
+
}
|
|
412
|
+
if (config.brand.org) {
|
|
413
|
+
lines.push(`Organization: ${config.brand.org}`);
|
|
414
|
+
}
|
|
415
|
+
lines.push(`Locales: ${config.brand.locales.join(", ")}`);
|
|
416
|
+
lines.push("");
|
|
417
|
+
if (canonicalUrls.length > 0) {
|
|
418
|
+
lines.push("## All Canonical URLs");
|
|
419
|
+
lines.push("");
|
|
420
|
+
const sortedUrls = sortStrings(canonicalUrls);
|
|
421
|
+
for (const url of sortedUrls) {
|
|
422
|
+
lines.push(`- ${url}`);
|
|
423
|
+
}
|
|
424
|
+
lines.push("");
|
|
425
|
+
}
|
|
426
|
+
const hasPolicies = config.policy?.geoPolicy || config.policy?.citationRules || config.policy?.restrictedClaims;
|
|
427
|
+
if (hasPolicies) {
|
|
428
|
+
lines.push("## Policies");
|
|
429
|
+
lines.push("");
|
|
430
|
+
if (config.policy?.geoPolicy) {
|
|
431
|
+
lines.push("### GEO Policy");
|
|
432
|
+
lines.push(config.policy.geoPolicy);
|
|
433
|
+
lines.push("");
|
|
434
|
+
}
|
|
435
|
+
if (config.policy?.citationRules) {
|
|
436
|
+
lines.push("### Citation Rules");
|
|
437
|
+
lines.push(config.policy.citationRules);
|
|
438
|
+
lines.push("");
|
|
439
|
+
}
|
|
440
|
+
if (config.policy?.restrictedClaims) {
|
|
441
|
+
lines.push("### Restricted Claims");
|
|
442
|
+
const status = config.policy.restrictedClaims.enable ? "Enabled" : "Disabled";
|
|
443
|
+
lines.push(`Status: ${status}`);
|
|
444
|
+
if (config.policy.restrictedClaims.forbidden && config.policy.restrictedClaims.forbidden.length > 0) {
|
|
445
|
+
lines.push(`Forbidden terms: ${config.policy.restrictedClaims.forbidden.join(", ")}`);
|
|
446
|
+
}
|
|
447
|
+
if (config.policy.restrictedClaims.whitelist && config.policy.restrictedClaims.whitelist.length > 0) {
|
|
448
|
+
lines.push(`Exceptions: ${config.policy.restrictedClaims.whitelist.join(", ")}`);
|
|
449
|
+
}
|
|
450
|
+
lines.push("");
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const hasSocial = config.contact?.social?.twitter || config.contact?.social?.linkedin || config.contact?.social?.github;
|
|
454
|
+
const hasBooking = config.booking?.url;
|
|
455
|
+
if (hasSocial || hasBooking) {
|
|
456
|
+
lines.push("## Social & Booking");
|
|
457
|
+
lines.push("");
|
|
458
|
+
if (config.contact?.social?.twitter) {
|
|
459
|
+
lines.push(`- Twitter: ${config.contact.social.twitter}`);
|
|
460
|
+
}
|
|
461
|
+
if (config.contact?.social?.linkedin) {
|
|
462
|
+
lines.push(`- LinkedIn: ${config.contact.social.linkedin}`);
|
|
463
|
+
}
|
|
464
|
+
if (config.contact?.social?.github) {
|
|
465
|
+
lines.push(`- GitHub: ${config.contact.social.github}`);
|
|
466
|
+
}
|
|
467
|
+
if (config.booking?.url) {
|
|
468
|
+
const label = config.booking.label ?? "Book consultation";
|
|
469
|
+
lines.push(`- Booking: ${config.booking.url} (${label})`);
|
|
470
|
+
}
|
|
471
|
+
lines.push("");
|
|
472
|
+
}
|
|
473
|
+
const hasMachineHints = config.machineHints?.robots || config.machineHints?.sitemap || config.machineHints?.llmsTxt || config.machineHints?.llmsFullTxt;
|
|
474
|
+
if (hasMachineHints) {
|
|
475
|
+
lines.push("## Machine Hints");
|
|
476
|
+
lines.push("");
|
|
477
|
+
if (config.machineHints?.robots) {
|
|
478
|
+
lines.push(`- robots.txt: ${config.machineHints.robots}`);
|
|
479
|
+
}
|
|
480
|
+
if (config.machineHints?.sitemap) {
|
|
481
|
+
lines.push(`- sitemap.xml: ${config.machineHints.sitemap}`);
|
|
482
|
+
}
|
|
483
|
+
if (config.machineHints?.llmsTxt) {
|
|
484
|
+
lines.push(`- llms.txt: ${config.machineHints.llmsTxt}`);
|
|
485
|
+
}
|
|
486
|
+
if (config.machineHints?.llmsFullTxt) {
|
|
487
|
+
lines.push(`- llms-full.txt: ${config.machineHints.llmsFullTxt}`);
|
|
488
|
+
}
|
|
489
|
+
lines.push("");
|
|
490
|
+
}
|
|
491
|
+
const hubs = config.sections?.hubs ?? [];
|
|
492
|
+
if (hubs.length > 0 || manifestItems.length > 0) {
|
|
493
|
+
lines.push("## Sitemap");
|
|
494
|
+
lines.push("");
|
|
495
|
+
if (hubs.length > 0) {
|
|
496
|
+
const sortedHubs = sortStrings(hubs);
|
|
497
|
+
for (const hub of sortedHubs) {
|
|
498
|
+
lines.push(`- [${hub}](${hub}) - ${getHubLabel2(hub)}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (manifestItems.length > 0) {
|
|
502
|
+
const sortedItems = sortBy(manifestItems, (item) => item.slug);
|
|
503
|
+
for (const item of sortedItems) {
|
|
504
|
+
const url = item.canonicalOverride ?? `${config.site.baseUrl}${item.slug}`;
|
|
505
|
+
const title = item.title ?? item.slug;
|
|
506
|
+
const locales = item.locales?.join(", ") ?? config.brand.locales[0] ?? "en";
|
|
507
|
+
lines.push(`- [${title}](${url}) (${locales})`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
lines.push("");
|
|
511
|
+
}
|
|
512
|
+
let content = lines.join("\n");
|
|
513
|
+
content = normalizeLineWhitespace(content);
|
|
514
|
+
content = normalizeLineEndings(content, lineEndings);
|
|
515
|
+
const finalLines = content.split(lineEndings === "crlf" ? "\r\n" : "\n");
|
|
516
|
+
return {
|
|
517
|
+
content,
|
|
518
|
+
byteSize: Buffer.byteLength(content, "utf-8"),
|
|
519
|
+
lineCount: finalLines.length
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function getHubLabel2(hub) {
|
|
523
|
+
const labels = {
|
|
524
|
+
"/services": "Services overview",
|
|
525
|
+
"/blog": "Blog posts",
|
|
526
|
+
"/projects": "Our projects",
|
|
527
|
+
"/cases": "Case studies",
|
|
528
|
+
"/contact": "Contact us",
|
|
529
|
+
"/about": "About us",
|
|
530
|
+
"/products": "Products",
|
|
531
|
+
"/docs": "Documentation",
|
|
532
|
+
"/faq": "Frequently asked questions",
|
|
533
|
+
"/pricing": "Pricing information",
|
|
534
|
+
"/team": "Our team",
|
|
535
|
+
"/careers": "Career opportunities",
|
|
536
|
+
"/news": "News and updates",
|
|
537
|
+
"/resources": "Resources",
|
|
538
|
+
"/support": "Support center"
|
|
539
|
+
};
|
|
540
|
+
return labels[hub] ?? formatHubLabel2(hub);
|
|
541
|
+
}
|
|
542
|
+
function formatHubLabel2(hub) {
|
|
543
|
+
const clean = hub.replace(/^\//, "");
|
|
544
|
+
return clean.replace(/[-_]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
545
|
+
}
|
|
546
|
+
function generateLlmsFullTxt(manifest, _options) {
|
|
547
|
+
const canonicalUrls = manifest.pages.map((page) => `${manifest.baseUrl}${page.path}`);
|
|
548
|
+
const manifestItems = manifest.pages.map((page) => ({
|
|
549
|
+
slug: page.path,
|
|
550
|
+
title: page.title,
|
|
551
|
+
description: page.description
|
|
552
|
+
}));
|
|
553
|
+
const config = {
|
|
554
|
+
site: { baseUrl: manifest.baseUrl },
|
|
555
|
+
brand: {
|
|
556
|
+
name: manifest.title,
|
|
557
|
+
locales: ["en"],
|
|
558
|
+
...manifest.description && { description: manifest.description }
|
|
559
|
+
},
|
|
560
|
+
manifests: {},
|
|
561
|
+
output: {
|
|
562
|
+
paths: {
|
|
563
|
+
llmsTxt: "public/llms.txt",
|
|
564
|
+
llmsFullTxt: "public/llms-full.txt"
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
return createLlmsFullTxt({ config, canonicalUrls, manifestItems }).content;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/adapters/next/build-hooks.ts
|
|
572
|
+
function generateBuildScripts(options = {}) {
|
|
573
|
+
const {
|
|
574
|
+
configPath = "llm-seo.config.ts",
|
|
575
|
+
emitCitations = false,
|
|
576
|
+
packageManager = "pnpm"
|
|
577
|
+
} = options;
|
|
578
|
+
const configFlag = `--config ${configPath}`;
|
|
579
|
+
const citationsFlag = emitCitations ? " --emit-citations" : "";
|
|
580
|
+
const runCommand = packageManager === "npm" ? "npm run" : packageManager;
|
|
581
|
+
return {
|
|
582
|
+
"build:seo": `llm-seo generate ${configFlag}${citationsFlag}`,
|
|
583
|
+
postbuild: `${runCommand} build:seo && llm-seo check --fail-on error`,
|
|
584
|
+
"check:seo": "llm-seo check --fail-on warn"
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function createRobotsLlmsPolicySnippet(options) {
|
|
588
|
+
const {
|
|
589
|
+
baseUrl,
|
|
590
|
+
allowLlmsTxt = true,
|
|
591
|
+
allowLlmsFullTxt = true,
|
|
592
|
+
allowSitemap = true,
|
|
593
|
+
additionalPaths = [],
|
|
594
|
+
userAgent = "*"
|
|
595
|
+
} = options;
|
|
596
|
+
const lines = ["# LLM SEO"];
|
|
597
|
+
try {
|
|
598
|
+
const url = new URL(baseUrl);
|
|
599
|
+
lines.push(`Host: ${url.host}`);
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
lines.push(`User-agent: ${userAgent}`);
|
|
603
|
+
if (allowLlmsTxt) {
|
|
604
|
+
lines.push("Allow: /llms.txt");
|
|
605
|
+
}
|
|
606
|
+
if (allowLlmsFullTxt) {
|
|
607
|
+
lines.push("Allow: /llms-full.txt");
|
|
608
|
+
}
|
|
609
|
+
if (allowSitemap) {
|
|
610
|
+
lines.push("Allow: /sitemap.xml");
|
|
611
|
+
}
|
|
612
|
+
for (const path of additionalPaths) {
|
|
613
|
+
lines.push(`Allow: ${path}`);
|
|
614
|
+
}
|
|
615
|
+
return lines.join("\n");
|
|
616
|
+
}
|
|
617
|
+
function createNextConfig(options) {
|
|
618
|
+
const {
|
|
619
|
+
locales = [],
|
|
620
|
+
defaultLocale = "en",
|
|
621
|
+
trailingSlash = false,
|
|
622
|
+
unoptimizedImages = true
|
|
623
|
+
} = options;
|
|
624
|
+
const config = {
|
|
625
|
+
output: "export",
|
|
626
|
+
trailingSlash,
|
|
627
|
+
images: {
|
|
628
|
+
unoptimized: unoptimizedImages
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
if (locales.length > 0) {
|
|
632
|
+
config.i18n = {
|
|
633
|
+
locales,
|
|
634
|
+
defaultLocale
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return config;
|
|
638
|
+
}
|
|
639
|
+
async function postBuildHook(options) {
|
|
640
|
+
const { outputDir, manifest, generateFull = false } = options;
|
|
641
|
+
const files = [];
|
|
642
|
+
try {
|
|
643
|
+
generateLlmsTxt(manifest);
|
|
644
|
+
const llmsTxtPath = `${outputDir}/llms.txt`;
|
|
645
|
+
files.push(llmsTxtPath);
|
|
646
|
+
if (generateFull) {
|
|
647
|
+
generateLlmsFullTxt(manifest);
|
|
648
|
+
const llmsFullTxtPath = `${outputDir}/llms-full.txt`;
|
|
649
|
+
files.push(llmsFullTxtPath);
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
files,
|
|
653
|
+
success: true
|
|
654
|
+
};
|
|
655
|
+
} catch (error) {
|
|
656
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
657
|
+
return {
|
|
658
|
+
files,
|
|
659
|
+
success: false,
|
|
660
|
+
error: message
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function createNextPlugin() {
|
|
665
|
+
return {
|
|
666
|
+
name: "llm-seo",
|
|
667
|
+
postBuild: postBuildHook
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export { createManifestFromData, createManifestFromPagesDir, createNextConfig, createNextPlugin, createRobotsLlmsPolicySnippet, extractPagePaths, fromNextContentManifest, generateBuildScripts, generateNextManifest, postBuildHook };
|
|
672
|
+
//# sourceMappingURL=index.js.map
|
|
673
|
+
//# sourceMappingURL=index.js.map
|