@fullstackdatasolutions/articles 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +313 -1
- package/dist/index.cjs +308 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +267 -16
- package/dist/index.d.ts +267 -16
- package/dist/index.js +300 -79
- package/dist/index.js.map +1 -1
- package/dist/nextjs.cjs +324 -13
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.d.cts +179 -2
- package/dist/nextjs.d.ts +179 -2
- package/dist/nextjs.js +324 -13
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +677 -51
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +333 -12
- package/dist/server.d.ts +333 -12
- package/dist/server.js +662 -51
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/ArticleAnswer.tsx +35 -0
- package/src/ArticleSchemas.tsx +263 -23
- package/src/AuthorArticlesPage.tsx +38 -8
- package/src/__tests__/ArticleAnswer.test.tsx +25 -0
- package/src/__tests__/ArticleSchemas.test.tsx +516 -0
- package/src/__tests__/AuthorArticlesPage.test.tsx +76 -0
- package/src/__tests__/authorUtils.test.ts +50 -0
- package/src/__tests__/linkClassification.test.ts +55 -0
- package/src/__tests__/markdown.test.ts +77 -1
- package/src/__tests__/nextjs.test.ts +31 -15
- package/src/__tests__/renderMdx.test.tsx +162 -3
- package/src/__tests__/seoUtils.test.ts +279 -0
- package/src/__tests__/server-articles.test.ts +413 -1
- package/src/__tests__/validateArticles.test.ts +167 -6
- package/src/articleTypes.ts +57 -0
- package/src/articlesConfig.ts +176 -1
- package/src/authorUtils.ts +19 -1
- package/src/errorReporting.ts +1 -0
- package/src/index.ts +17 -1
- package/src/linkClassification.ts +30 -0
- package/src/markdown.ts +103 -25
- package/src/nextjs.ts +7 -4
- package/src/renderMdx.tsx +43 -6
- package/src/seoUtils.ts +247 -26
- package/src/server-articles.ts +375 -24
- package/src/server.ts +35 -4
- package/src/validateArticles.ts +157 -12
package/dist/server.cjs
CHANGED
|
@@ -79,16 +79,21 @@ var __async = (__this, __arguments, generator) => {
|
|
|
79
79
|
// src/server.ts
|
|
80
80
|
var server_exports = {};
|
|
81
81
|
__export(server_exports, {
|
|
82
|
+
AI_CRAWLERS: () => AI_CRAWLERS,
|
|
83
|
+
ArticleAnswer: () => ArticleAnswer,
|
|
82
84
|
ArticleContent: () => ArticleContent,
|
|
83
85
|
ArticleTOC: () => ArticleTOC,
|
|
84
86
|
buildArticleBreadcrumbs: () => buildArticleBreadcrumbs,
|
|
85
87
|
buildAuthorBreadcrumbs: () => buildAuthorBreadcrumbs,
|
|
86
88
|
buildCategoryBreadcrumbs: () => buildCategoryBreadcrumbs,
|
|
89
|
+
buildMarkdownTwinHeader: () => buildMarkdownTwinHeader,
|
|
87
90
|
buildPageUrl: () => buildPageUrl,
|
|
88
91
|
buildPaginationLinks: () => buildPaginationLinks,
|
|
89
92
|
categoryToSlug: () => categoryToSlug,
|
|
93
|
+
deriveFaqFromHeadings: () => deriveFaqFromHeadings,
|
|
90
94
|
emitArticleEvent: () => emitArticleEvent,
|
|
91
95
|
extractToc: () => extractToc,
|
|
96
|
+
formatPageTitle: () => formatPageTitle,
|
|
92
97
|
generateArticleMetadata: () => generateArticleMetadata,
|
|
93
98
|
generateArticleStaticParams: () => generateArticleStaticParams,
|
|
94
99
|
generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
|
|
@@ -100,6 +105,8 @@ __export(server_exports, {
|
|
|
100
105
|
generateCategoryPageMetadata: () => generateCategoryPageMetadata,
|
|
101
106
|
generateCategoryStaticParams: () => generateCategoryStaticParams,
|
|
102
107
|
generateListingPageStaticParams: () => generateListingPageStaticParams,
|
|
108
|
+
generateLlmsFullTxt: () => generateLlmsFullTxt,
|
|
109
|
+
generateLlmsTxt: () => generateLlmsTxt,
|
|
103
110
|
generateRssFeed: () => generateRssFeed,
|
|
104
111
|
generateSeriesMetadata: () => generateSeriesMetadata,
|
|
105
112
|
generateSeriesStaticParams: () => generateSeriesStaticParams,
|
|
@@ -120,16 +127,24 @@ __export(server_exports, {
|
|
|
120
127
|
getArticlesByCategory: () => getArticlesByCategory,
|
|
121
128
|
getArticlesBySeries: () => getArticlesBySeries,
|
|
122
129
|
getAuthorBySlug: () => getAuthorBySlug,
|
|
130
|
+
getAuthorMarkdown: () => getAuthorMarkdown,
|
|
123
131
|
getAvailableArticleSlugs: () => getAvailableArticleSlugs,
|
|
124
132
|
getBreadcrumbsConfig: () => getBreadcrumbsConfig,
|
|
133
|
+
getCategoryMarkdown: () => getCategoryMarkdown,
|
|
125
134
|
getContentSlotBoundaries: () => getContentSlotBoundaries,
|
|
135
|
+
getMarkdownTwinResponse: () => getMarkdownTwinResponse,
|
|
136
|
+
getOrganizationId: () => getOrganizationId,
|
|
126
137
|
getPath: () => getPath,
|
|
127
138
|
getPathArticles: () => getPathArticles,
|
|
139
|
+
getPersonId: () => getPersonId,
|
|
128
140
|
getRelatedArticlesByCategory: () => getRelatedArticlesByCategory,
|
|
129
141
|
getRelatedContent: () => getRelatedContent,
|
|
142
|
+
getSeriesMarkdown: () => getSeriesMarkdown,
|
|
130
143
|
getTotalPages: () => getTotalPages,
|
|
144
|
+
getWebSiteId: () => getWebSiteId,
|
|
131
145
|
isPageOutOfRange: () => isPageOutOfRange,
|
|
132
146
|
markdownToHtml: () => markdownToHtml,
|
|
147
|
+
matchAiCrawler: () => matchAiCrawler,
|
|
133
148
|
paginateArticles: () => paginateArticles,
|
|
134
149
|
parsePageParam: () => parsePageParam,
|
|
135
150
|
resolveAuthorAvatar: () => resolveAuthorAvatar,
|
|
@@ -188,8 +203,7 @@ function reportArticlesError(report) {
|
|
|
188
203
|
articlesErrorHandler(report);
|
|
189
204
|
}
|
|
190
205
|
|
|
191
|
-
// src/
|
|
192
|
-
var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
|
|
206
|
+
// src/linkClassification.ts
|
|
193
207
|
function isNonBrowserNavigationLink(href) {
|
|
194
208
|
return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href) && !href.startsWith("http://") && !href.startsWith("https://");
|
|
195
209
|
}
|
|
@@ -207,6 +221,9 @@ function isExternalHttpLink(href, siteUrl) {
|
|
|
207
221
|
if (!siteOrigin) return true;
|
|
208
222
|
return getOrigin(href) !== siteOrigin;
|
|
209
223
|
}
|
|
224
|
+
|
|
225
|
+
// src/markdown.ts
|
|
226
|
+
var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
|
|
210
227
|
function shouldOpenInNewTab(href, options = {}) {
|
|
211
228
|
var _a;
|
|
212
229
|
if (!href || href.startsWith("#") || isNonBrowserNavigationLink(href)) return false;
|
|
@@ -522,6 +539,64 @@ function extractToc(markdown) {
|
|
|
522
539
|
return headings;
|
|
523
540
|
});
|
|
524
541
|
}
|
|
542
|
+
var QUESTION_OPENERS = /^(what|how|why|when|where|who|which|can|should|does|do|is|are|will|would|must)\b/i;
|
|
543
|
+
function deriveFaqFromHeadings(markdown) {
|
|
544
|
+
const items = [];
|
|
545
|
+
const collector = createFaqCollector(items);
|
|
546
|
+
let inFence = false;
|
|
547
|
+
for (const line of markdown.split("\n")) {
|
|
548
|
+
if (FENCE.test(line)) {
|
|
549
|
+
inFence = !inFence;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (inFence) continue;
|
|
553
|
+
collector.consume(line);
|
|
554
|
+
}
|
|
555
|
+
collector.flush();
|
|
556
|
+
return items;
|
|
557
|
+
}
|
|
558
|
+
var FENCE = /^\s*(```|~~~)/;
|
|
559
|
+
var ANY_HEADING = /^#{1,6}\s/;
|
|
560
|
+
function isSpace(char) {
|
|
561
|
+
return char === " " || char === " ";
|
|
562
|
+
}
|
|
563
|
+
function trimTrailingHashes(value) {
|
|
564
|
+
let end = value.length;
|
|
565
|
+
while (end > 0 && value[end - 1] === "#") end--;
|
|
566
|
+
return value.slice(0, end).trimEnd();
|
|
567
|
+
}
|
|
568
|
+
function readQuestionHeading(line) {
|
|
569
|
+
if (!line.startsWith("##") || line.startsWith("###")) return null;
|
|
570
|
+
if (!isSpace(line[2])) return null;
|
|
571
|
+
const text = trimTrailingHashes(line.slice(3).trim());
|
|
572
|
+
if (!text.endsWith("?") || !QUESTION_OPENERS.test(text)) return null;
|
|
573
|
+
return text;
|
|
574
|
+
}
|
|
575
|
+
function createFaqCollector(items) {
|
|
576
|
+
let pending = null;
|
|
577
|
+
let buffer = [];
|
|
578
|
+
const flush = () => {
|
|
579
|
+
if (pending && buffer.length > 0) {
|
|
580
|
+
items.push({ question: pending, answer: buffer.join(" ").trim() });
|
|
581
|
+
}
|
|
582
|
+
pending = null;
|
|
583
|
+
buffer = [];
|
|
584
|
+
};
|
|
585
|
+
const consume = (line) => {
|
|
586
|
+
if (ANY_HEADING.test(line)) {
|
|
587
|
+
flush();
|
|
588
|
+
pending = readQuestionHeading(line);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (!pending) return;
|
|
592
|
+
if (line.trim() === "") {
|
|
593
|
+
if (buffer.length > 0) flush();
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
buffer.push(line.trim());
|
|
597
|
+
};
|
|
598
|
+
return { consume, flush };
|
|
599
|
+
}
|
|
525
600
|
|
|
526
601
|
// src/server-articles.ts
|
|
527
602
|
var articlesDirectory = import_node_path.default.join(
|
|
@@ -654,6 +729,58 @@ function parseHowToSteps(raw) {
|
|
|
654
729
|
function parseOptionalString(raw) {
|
|
655
730
|
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
656
731
|
}
|
|
732
|
+
function deriveFaq(markdownContent, config) {
|
|
733
|
+
if ((config == null ? void 0 : config.deriveFaqFromHeadings) !== true) return void 0;
|
|
734
|
+
const derived = deriveFaqFromHeadings(markdownContent);
|
|
735
|
+
return derived.length ? derived : void 0;
|
|
736
|
+
}
|
|
737
|
+
function resolveLastmod(rawLastmod, rawDate, filePath, config) {
|
|
738
|
+
const declared = parseDateField(rawLastmod);
|
|
739
|
+
if (declared) return declared;
|
|
740
|
+
if ((config == null ? void 0 : config.lastmodFallback) !== "fileMtime") return void 0;
|
|
741
|
+
try {
|
|
742
|
+
return parseDateField(import_node_fs.default.statSync(filePath).mtime);
|
|
743
|
+
} catch (e) {
|
|
744
|
+
return parseDateField(rawDate);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function resolveAiCrawl(raw, config) {
|
|
748
|
+
if (typeof raw === "boolean") return raw;
|
|
749
|
+
return (config == null ? void 0 : config.aiCrawlDefault) === true;
|
|
750
|
+
}
|
|
751
|
+
function parseEntityReferences(raw, config) {
|
|
752
|
+
if (!Array.isArray(raw)) return void 0;
|
|
753
|
+
const items = raw.map((item) => {
|
|
754
|
+
var _a, _b;
|
|
755
|
+
if (typeof item === "string") {
|
|
756
|
+
const key = item.trim();
|
|
757
|
+
if (!key) return null;
|
|
758
|
+
return (_b = (_a = config == null ? void 0 : config.entities) == null ? void 0 : _a[key]) != null ? _b : { name: key };
|
|
759
|
+
}
|
|
760
|
+
if (typeof item === "object" && item !== null && typeof item.name === "string") {
|
|
761
|
+
const entity = item;
|
|
762
|
+
const name = entity.name.trim();
|
|
763
|
+
if (!name) return null;
|
|
764
|
+
return entity.sameAs ? { name, sameAs: entity.sameAs } : { name };
|
|
765
|
+
}
|
|
766
|
+
return null;
|
|
767
|
+
}).filter((item) => item !== null);
|
|
768
|
+
return items.length ? items : void 0;
|
|
769
|
+
}
|
|
770
|
+
function parseCitations(raw) {
|
|
771
|
+
if (!Array.isArray(raw)) return void 0;
|
|
772
|
+
const items = raw.map((item) => {
|
|
773
|
+
if (typeof item === "string") return item.trim() ? { name: item.trim() } : null;
|
|
774
|
+
if (typeof item === "object" && item !== null && typeof item.name === "string") {
|
|
775
|
+
const citation = item;
|
|
776
|
+
const name = citation.name.trim();
|
|
777
|
+
if (!name) return null;
|
|
778
|
+
return citation.url ? { name, url: citation.url } : { name };
|
|
779
|
+
}
|
|
780
|
+
return null;
|
|
781
|
+
}).filter((item) => item !== null);
|
|
782
|
+
return items.length ? items : void 0;
|
|
783
|
+
}
|
|
657
784
|
function parseSeriesOrder(raw) {
|
|
658
785
|
return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
659
786
|
}
|
|
@@ -730,6 +857,7 @@ function getAllAuthors(config) {
|
|
|
730
857
|
}
|
|
731
858
|
function getArticleSummary(slug, config) {
|
|
732
859
|
return __async(this, null, function* () {
|
|
860
|
+
var _a;
|
|
733
861
|
try {
|
|
734
862
|
const found = findArticleFile(slug);
|
|
735
863
|
if (!found) return null;
|
|
@@ -746,7 +874,7 @@ function getArticleSummary(slug, config) {
|
|
|
746
874
|
title: data.title || slug.replaceAll("-", " "),
|
|
747
875
|
excerpt: data.excerpt || "",
|
|
748
876
|
date: parseDateField(data.date),
|
|
749
|
-
lastmod:
|
|
877
|
+
lastmod: resolveLastmod(data.lastmod, data.date, found.filePath, config),
|
|
750
878
|
author,
|
|
751
879
|
authors,
|
|
752
880
|
authorSlug: primaryAuthorProfile == null ? void 0 : primaryAuthorProfile.slug,
|
|
@@ -759,14 +887,17 @@ function getArticleSummary(slug, config) {
|
|
|
759
887
|
tags: data.tags || [],
|
|
760
888
|
contentType: found.contentType,
|
|
761
889
|
draft: data.draft === true,
|
|
762
|
-
faq: parseFaqItems(data.faq),
|
|
890
|
+
faq: (_a = parseFaqItems(data.faq)) != null ? _a : deriveFaq(markdownContent, config),
|
|
763
891
|
howTo: parseHowToSteps(data.howTo),
|
|
892
|
+
answer: parseOptionalString(data.answer),
|
|
893
|
+
about: parseEntityReferences(data.about, config),
|
|
894
|
+
citation: parseCitations(data.citation),
|
|
764
895
|
canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
|
|
765
896
|
articleType: typeof data.articleType === "string" ? data.articleType : void 0,
|
|
766
897
|
series: typeof data.series === "string" ? data.series : void 0,
|
|
767
898
|
seriesSlug: parseOptionalString(data.seriesSlug),
|
|
768
899
|
seriesOrder: parseSeriesOrder(data.seriesOrder),
|
|
769
|
-
aiCrawl: data.aiCrawl
|
|
900
|
+
aiCrawl: resolveAiCrawl(data.aiCrawl, config),
|
|
770
901
|
searchTitle: parseOptionalString(data.searchTitle),
|
|
771
902
|
searchDescription: parseOptionalString(data.searchDescription),
|
|
772
903
|
socialTitle: parseOptionalString(data.socialTitle),
|
|
@@ -835,10 +966,10 @@ function getAdjacentArticles(currentSlug) {
|
|
|
835
966
|
return { previous, next };
|
|
836
967
|
});
|
|
837
968
|
}
|
|
838
|
-
function getArticleMarkdown(slug) {
|
|
969
|
+
function getArticleMarkdown(slug, config) {
|
|
839
970
|
return __async(this, null, function* () {
|
|
840
971
|
try {
|
|
841
|
-
const summary = yield getArticleSummary(slug);
|
|
972
|
+
const summary = yield getArticleSummary(slug, config);
|
|
842
973
|
if (!(summary == null ? void 0 : summary.aiCrawl)) return null;
|
|
843
974
|
const found = findArticleFile(slug);
|
|
844
975
|
if (!found) return null;
|
|
@@ -856,12 +987,180 @@ function getArticleMarkdown(slug) {
|
|
|
856
987
|
}
|
|
857
988
|
});
|
|
858
989
|
}
|
|
859
|
-
|
|
990
|
+
var AI_CRAWLERS = [
|
|
991
|
+
"GPTBot",
|
|
992
|
+
"ChatGPT-User",
|
|
993
|
+
"OAI-SearchBot",
|
|
994
|
+
"CCBot",
|
|
995
|
+
"ClaudeBot",
|
|
996
|
+
"Claude-User",
|
|
997
|
+
"Claude-SearchBot",
|
|
998
|
+
"anthropic-ai",
|
|
999
|
+
"PerplexityBot",
|
|
1000
|
+
"Perplexity-User",
|
|
1001
|
+
"Google-Extended",
|
|
1002
|
+
"Applebot-Extended",
|
|
1003
|
+
"Bytespider",
|
|
1004
|
+
"Amazonbot",
|
|
1005
|
+
"meta-externalagent",
|
|
1006
|
+
"cohere-ai",
|
|
1007
|
+
"DuckAssistBot",
|
|
1008
|
+
"MistralAI-User"
|
|
1009
|
+
];
|
|
1010
|
+
function matchAiCrawler(userAgent) {
|
|
1011
|
+
var _a;
|
|
1012
|
+
if (!userAgent) return null;
|
|
1013
|
+
const normalized = userAgent.toLowerCase();
|
|
1014
|
+
return (_a = AI_CRAWLERS.find((crawler) => normalized.includes(crawler.toLowerCase()))) != null ? _a : null;
|
|
1015
|
+
}
|
|
1016
|
+
function buildMarkdownTwinHeader(article, config, body) {
|
|
1017
|
+
var _a, _b;
|
|
1018
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1019
|
+
const firstLine = (_b = (_a = body.trimStart().split("\n", 1)[0]) == null ? void 0 : _a.trim()) != null ? _b : "";
|
|
1020
|
+
const bodyRepeatsTitle = firstLine.toLowerCase() === `# ${article.title}`.toLowerCase();
|
|
1021
|
+
const facts = [
|
|
1022
|
+
`Source: ${siteUrl}/articles/${article.slug}`,
|
|
1023
|
+
article.date ? `Published: ${article.date}` : "",
|
|
1024
|
+
article.lastmod ? `Updated: ${article.lastmod}` : "",
|
|
1025
|
+
config.showAuthor !== false && article.author ? `Author: ${article.author}` : "",
|
|
1026
|
+
`Site: ${config.siteName}`
|
|
1027
|
+
].filter(Boolean);
|
|
1028
|
+
const blocks = [
|
|
1029
|
+
bodyRepeatsTitle ? "" : `# ${article.title}`,
|
|
1030
|
+
article.excerpt ? `> ${article.excerpt}` : "",
|
|
1031
|
+
facts.join("\n"),
|
|
1032
|
+
article.answer ? `**Short answer:** ${article.answer}` : "",
|
|
1033
|
+
"---"
|
|
1034
|
+
].filter((block) => block !== "");
|
|
1035
|
+
return `${blocks.join("\n\n")}
|
|
1036
|
+
|
|
1037
|
+
`;
|
|
1038
|
+
}
|
|
1039
|
+
function reportAiCrawl(slug, config, headers) {
|
|
1040
|
+
var _a, _b;
|
|
1041
|
+
if (!config.onAiCrawl) return;
|
|
1042
|
+
const userAgent = (_a = headers == null ? void 0 : headers.get("user-agent")) != null ? _a : "";
|
|
1043
|
+
try {
|
|
1044
|
+
config.onAiCrawl({ slug, crawler: (_b = matchAiCrawler(userAgent)) != null ? _b : "unknown", userAgent });
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
reportArticlesError({
|
|
1047
|
+
code: "ai-crawl-handler-failed",
|
|
1048
|
+
message: "onAiCrawl handler threw.",
|
|
1049
|
+
error,
|
|
1050
|
+
context: { slug }
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
function buildListingMarkdown(heading, intro, articles, config) {
|
|
1055
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1056
|
+
const crawlable = articles.filter((article) => article.aiCrawl === true);
|
|
1057
|
+
const entries = crawlable.map((article) => {
|
|
1058
|
+
var _a;
|
|
1059
|
+
const summary = (_a = article.answer) != null ? _a : article.excerpt;
|
|
1060
|
+
const line = `- [${article.title}](${siteUrl}/articles/${article.slug}.md)`;
|
|
1061
|
+
return summary ? `${line}: ${summary}` : line;
|
|
1062
|
+
});
|
|
1063
|
+
return [
|
|
1064
|
+
`# ${heading}`,
|
|
1065
|
+
"",
|
|
1066
|
+
...intro.flatMap((line) => [line, ""]),
|
|
1067
|
+
`Source: ${siteUrl}`,
|
|
1068
|
+
`Site: ${config.siteName}`,
|
|
1069
|
+
"",
|
|
1070
|
+
"---",
|
|
1071
|
+
"",
|
|
1072
|
+
...entries.length > 0 ? entries : ["_No articles available._"],
|
|
1073
|
+
""
|
|
1074
|
+
].join("\n");
|
|
1075
|
+
}
|
|
1076
|
+
function getCategoryMarkdown(categorySlug, config) {
|
|
1077
|
+
return __async(this, null, function* () {
|
|
1078
|
+
var _a;
|
|
1079
|
+
const articles = yield getArticlesByCategory(categorySlug, config);
|
|
1080
|
+
if (articles.length === 0) return null;
|
|
1081
|
+
const name = (_a = articles[0].categories.find((c) => categoryToSlug(c) === categorySlug)) != null ? _a : categorySlug;
|
|
1082
|
+
const description = resolveCategoryDescription(categorySlug, config);
|
|
1083
|
+
return buildListingMarkdown(name, description ? [description] : [], articles, config);
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
function resolveCategoryDescription(categorySlug, config) {
|
|
1087
|
+
var _a, _b;
|
|
1088
|
+
const entry = (_a = config.categoryDescriptions) == null ? void 0 : _a[categorySlug];
|
|
1089
|
+
if (!entry) return void 0;
|
|
1090
|
+
return typeof entry === "string" ? entry : (_b = entry.long) != null ? _b : entry.short;
|
|
1091
|
+
}
|
|
1092
|
+
function getAuthorMarkdown(authorSlug, config) {
|
|
1093
|
+
return __async(this, null, function* () {
|
|
1094
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1095
|
+
const author = getAuthorBySlug(authorSlug, config);
|
|
1096
|
+
if (!author) return null;
|
|
1097
|
+
const articles = yield getArticlesByAuthor(authorSlug, config);
|
|
1098
|
+
const intro = [
|
|
1099
|
+
(_a = author.promise) != null ? _a : "",
|
|
1100
|
+
(_b = author.bio) != null ? _b : "",
|
|
1101
|
+
...((_c = author.servesWho) == null ? void 0 : _c.length) ? [`Writes for: ${author.servesWho.join(", ")}`] : [],
|
|
1102
|
+
...((_d = author.knowsAbout) == null ? void 0 : _d.length) ? [`Writes about: ${author.knowsAbout.join(", ")}`] : [],
|
|
1103
|
+
...((_e = author.credentials) == null ? void 0 : _e.length) ? ["## Stated experience", ...author.credentials.map((item) => `- ${item}`)] : [],
|
|
1104
|
+
...((_f = author.proof) == null ? void 0 : _f.length) ? [
|
|
1105
|
+
"## Proof points",
|
|
1106
|
+
...author.proof.map(
|
|
1107
|
+
(item) => item.url ? `- [${item.claim}](${item.url})` : `- ${item.claim}`
|
|
1108
|
+
)
|
|
1109
|
+
] : [],
|
|
1110
|
+
...((_g = author.originStory) == null ? void 0 : _g.length) ? [
|
|
1111
|
+
"## Background",
|
|
1112
|
+
...author.originStory.flatMap((section) => [
|
|
1113
|
+
...section.heading ? [`### ${section.heading}`] : [],
|
|
1114
|
+
...section.paragraphs
|
|
1115
|
+
])
|
|
1116
|
+
] : []
|
|
1117
|
+
].filter((line) => line.trim() !== "");
|
|
1118
|
+
return buildListingMarkdown(author.name, intro, articles, config);
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
function getSeriesMarkdown(seriesSlug, config) {
|
|
1122
|
+
return __async(this, null, function* () {
|
|
1123
|
+
var _a;
|
|
1124
|
+
const articles = yield getArticlesBySeries(seriesSlug, config);
|
|
1125
|
+
if (articles.length === 0) return null;
|
|
1126
|
+
const name = (_a = articles[0].series) != null ? _a : seriesSlug;
|
|
1127
|
+
return buildListingMarkdown(name, [], articles, config);
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
function getMarkdownTwinResponse(slug, config, options) {
|
|
1131
|
+
return __async(this, null, function* () {
|
|
1132
|
+
const listing = yield resolveListingMarkdown(slug, config);
|
|
1133
|
+
if (listing !== void 0) {
|
|
1134
|
+
if (listing === null) return new Response("Not Found", { status: 404 });
|
|
1135
|
+
reportAiCrawl(slug, config, options == null ? void 0 : options.headers);
|
|
1136
|
+
return new Response(listing, { headers: LISTING_MARKDOWN_HEADERS });
|
|
1137
|
+
}
|
|
1138
|
+
return getArticleMarkdownResponse(slug, config, options);
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
var LISTING_MARKDOWN_HEADERS = {
|
|
1142
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
1143
|
+
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
1144
|
+
};
|
|
1145
|
+
function resolveListingMarkdown(slug, config) {
|
|
860
1146
|
return __async(this, null, function* () {
|
|
861
|
-
const
|
|
1147
|
+
const [prefix, ...rest] = slug.split("/");
|
|
1148
|
+
const key = rest.join("/");
|
|
1149
|
+
if (!key) return void 0;
|
|
1150
|
+
if (prefix === "category") return getCategoryMarkdown(key, config);
|
|
1151
|
+
if (prefix === "authors") return getAuthorMarkdown(key, config);
|
|
1152
|
+
if (prefix === "series") return getSeriesMarkdown(key, config);
|
|
1153
|
+
return void 0;
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
function getArticleMarkdownResponse(slug, config, options) {
|
|
1157
|
+
return __async(this, null, function* () {
|
|
1158
|
+
const markdown = yield getArticleMarkdown(slug, config);
|
|
862
1159
|
if (markdown === null) return new Response("Not Found", { status: 404 });
|
|
863
|
-
const article = yield getArticleMetadata(slug);
|
|
864
|
-
|
|
1160
|
+
const article = yield getArticleMetadata(slug, config);
|
|
1161
|
+
reportAiCrawl(slug, config, options == null ? void 0 : options.headers);
|
|
1162
|
+
const body = article && config.markdownTwinHeader !== false ? `${buildMarkdownTwinHeader(article, config, markdown)}${markdown.trimStart()}` : markdown;
|
|
1163
|
+
return new Response(body, {
|
|
865
1164
|
headers: __spreadValues({
|
|
866
1165
|
"Content-Type": "text/markdown; charset=utf-8",
|
|
867
1166
|
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
@@ -886,22 +1185,15 @@ function getArticleAiHeaders(article, config) {
|
|
|
886
1185
|
"X-Robots-Tag": "noai, noimageai"
|
|
887
1186
|
};
|
|
888
1187
|
}
|
|
889
|
-
function getAiRobotsTxtRules() {
|
|
1188
|
+
function getAiRobotsTxtRules(config) {
|
|
890
1189
|
return __async(this, null, function* () {
|
|
891
|
-
const articles = yield getAllArticles();
|
|
1190
|
+
const articles = yield getAllArticles(config);
|
|
892
1191
|
const blockedArticles = articles.filter((article) => article.aiCrawl !== true);
|
|
893
1192
|
if (blockedArticles.length === 0) return "";
|
|
894
|
-
const aiCrawlers = [
|
|
895
|
-
"GPTBot",
|
|
896
|
-
"ChatGPT-User",
|
|
897
|
-
"CCBot",
|
|
898
|
-
"ClaudeBot",
|
|
899
|
-
"Claude-User",
|
|
900
|
-
"PerplexityBot",
|
|
901
|
-
"Google-Extended"
|
|
902
|
-
];
|
|
903
1193
|
const disallowRules = blockedArticles.map((article) => `Disallow: /articles/${article.slug}`).join("\n");
|
|
904
|
-
return
|
|
1194
|
+
return AI_CRAWLERS.map((crawler) => [`User-agent: ${crawler}`, disallowRules].join("\n")).join(
|
|
1195
|
+
"\n\n"
|
|
1196
|
+
);
|
|
905
1197
|
});
|
|
906
1198
|
}
|
|
907
1199
|
function searchArticles(query, config) {
|
|
@@ -1041,6 +1333,7 @@ function getRelatedContent(article, config, limit = 3) {
|
|
|
1041
1333
|
}
|
|
1042
1334
|
|
|
1043
1335
|
// src/articlesConfig.ts
|
|
1336
|
+
var DEFAULT_PAGE_SIZE = 6;
|
|
1044
1337
|
function breadcrumbsAreEnabled(config) {
|
|
1045
1338
|
var _a;
|
|
1046
1339
|
return config.breadcrumbs !== false && ((_a = config.breadcrumbs) == null ? void 0 : _a.show) !== false;
|
|
@@ -1050,6 +1343,20 @@ function getBreadcrumbsConfig(config) {
|
|
|
1050
1343
|
if (config.breadcrumbs === false) return {};
|
|
1051
1344
|
return (_a = config.breadcrumbs) != null ? _a : {};
|
|
1052
1345
|
}
|
|
1346
|
+
function getOrganizationId(config) {
|
|
1347
|
+
return `${config.siteUrl.replace(/\/$/, "")}/#organization`;
|
|
1348
|
+
}
|
|
1349
|
+
function getWebSiteId(config) {
|
|
1350
|
+
return `${config.siteUrl.replace(/\/$/, "")}/#website`;
|
|
1351
|
+
}
|
|
1352
|
+
function getPersonId(authorUrl) {
|
|
1353
|
+
return `${authorUrl.replace(/\/$/, "")}#person`;
|
|
1354
|
+
}
|
|
1355
|
+
var DEFAULT_TITLE_TEMPLATE = "{title} | {siteName}";
|
|
1356
|
+
function formatPageTitle(title, config) {
|
|
1357
|
+
var _a;
|
|
1358
|
+
return ((_a = config.titleTemplate) != null ? _a : DEFAULT_TITLE_TEMPLATE).replaceAll("{title}", title).replaceAll("{siteName}", config.siteName);
|
|
1359
|
+
}
|
|
1053
1360
|
|
|
1054
1361
|
// src/pagination.ts
|
|
1055
1362
|
function getTotalPages(totalCount, pageSize) {
|
|
@@ -1097,10 +1404,11 @@ function isPageOutOfRange(page, totalPages) {
|
|
|
1097
1404
|
function escapeXml(str) {
|
|
1098
1405
|
return str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1099
1406
|
}
|
|
1100
|
-
function generateRssFeed(articles, config) {
|
|
1101
|
-
var _a;
|
|
1407
|
+
function generateRssFeed(articles, config, options) {
|
|
1408
|
+
var _a, _b;
|
|
1102
1409
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1103
1410
|
const showAuthor = config.showAuthor !== false;
|
|
1411
|
+
const fullContent = (options == null ? void 0 : options.fullContent) === true;
|
|
1104
1412
|
const items = articles.map((article) => {
|
|
1105
1413
|
const url = `${siteUrl}/articles/${article.slug}`;
|
|
1106
1414
|
const pubDate = article.date ? new Date(article.date).toUTCString() : "";
|
|
@@ -1112,6 +1420,7 @@ function generateRssFeed(articles, config) {
|
|
|
1112
1420
|
` <guid isPermaLink="true">${url}</guid>`,
|
|
1113
1421
|
pubDate ? ` <pubDate>${pubDate}</pubDate>` : "",
|
|
1114
1422
|
article.excerpt ? ` <description><![CDATA[${article.excerpt}]]></description>` : "",
|
|
1423
|
+
fullContent && article.htmlContent ? ` <content:encoded><![CDATA[${article.htmlContent}]]></content:encoded>` : "",
|
|
1115
1424
|
showAuthor && article.author ? ` <author>${escapeXml(article.author)}</author>` : "",
|
|
1116
1425
|
article.category ? ` <category><![CDATA[${article.category}]]></category>` : "",
|
|
1117
1426
|
imageUrl ? ` <media:content url="${imageUrl}" medium="image" width="1200" height="630"/>` : "",
|
|
@@ -1120,17 +1429,82 @@ function generateRssFeed(articles, config) {
|
|
|
1120
1429
|
}).join("\n");
|
|
1121
1430
|
const description = (_a = config.description) != null ? _a : `${config.siteName} articles`;
|
|
1122
1431
|
return `<?xml version="1.0" encoding="UTF-8" ?>
|
|
1123
|
-
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
|
|
1432
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
|
1124
1433
|
<channel>
|
|
1125
1434
|
<title><![CDATA[${config.siteName}]]></title>
|
|
1126
1435
|
<link>${siteUrl}/articles</link>
|
|
1127
1436
|
<description><![CDATA[${description}]]></description>
|
|
1128
|
-
<language
|
|
1437
|
+
<language>${(_b = config.language) != null ? _b : "en"}</language>
|
|
1129
1438
|
<atom:link href="${siteUrl}/articles/feed.xml" rel="self" type="application/rss+xml" />
|
|
1130
1439
|
${items}
|
|
1131
1440
|
</channel>
|
|
1132
1441
|
</rss>`;
|
|
1133
1442
|
}
|
|
1443
|
+
function buildLlmsHeader(config) {
|
|
1444
|
+
var _a;
|
|
1445
|
+
const summary = (_a = config.description) != null ? _a : `${config.siteName} articles`;
|
|
1446
|
+
return [`# ${config.siteName}`, "", `> ${summary}`, ""];
|
|
1447
|
+
}
|
|
1448
|
+
function generateLlmsTxt(articles, config) {
|
|
1449
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1450
|
+
const crawlable = articles.filter((article) => article.aiCrawl === true);
|
|
1451
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
1452
|
+
for (const article of crawlable) {
|
|
1453
|
+
const category = article.category || "Articles";
|
|
1454
|
+
const existing = byCategory.get(category);
|
|
1455
|
+
if (existing) existing.push(article);
|
|
1456
|
+
else byCategory.set(category, [article]);
|
|
1457
|
+
}
|
|
1458
|
+
const sections = [...byCategory.entries()].map(([category, categoryArticles]) => {
|
|
1459
|
+
const lines = categoryArticles.map((article) => {
|
|
1460
|
+
const url = `${siteUrl}/articles/${article.slug}.md`;
|
|
1461
|
+
const summary = article.excerpt ? `: ${article.excerpt}` : "";
|
|
1462
|
+
return `- [${article.title}](${url})${summary}`;
|
|
1463
|
+
});
|
|
1464
|
+
return [`## ${category}`, "", ...lines].join("\n");
|
|
1465
|
+
});
|
|
1466
|
+
return [
|
|
1467
|
+
...buildLlmsHeader(config),
|
|
1468
|
+
...sections.length > 0 ? sections : ["## Articles", "", "_No articles available._"],
|
|
1469
|
+
...buildLlmsListingSection(crawlable, config),
|
|
1470
|
+
""
|
|
1471
|
+
].join("\n");
|
|
1472
|
+
}
|
|
1473
|
+
function buildLlmsListingSection(articles, config) {
|
|
1474
|
+
var _a;
|
|
1475
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1476
|
+
const categories = [...new Set(articles.flatMap((a) => {
|
|
1477
|
+
var _a2;
|
|
1478
|
+
return (_a2 = a.categories) != null ? _a2 : [];
|
|
1479
|
+
}))].filter(Boolean);
|
|
1480
|
+
const series = [...new Set(articles.map((a) => a.seriesSlug).filter(Boolean))];
|
|
1481
|
+
const authors = Object.values((_a = config.authors) != null ? _a : {});
|
|
1482
|
+
const lines = [
|
|
1483
|
+
...categories.map(
|
|
1484
|
+
(name) => `- [${name}](${siteUrl}/articles/category/${categoryToSlug(name)}.md)`
|
|
1485
|
+
),
|
|
1486
|
+
...config.showAuthorPage === false ? [] : authors.map((a) => `- [${a.name}](${siteUrl}/articles/authors/${a.slug}.md)`),
|
|
1487
|
+
...series.map((slug) => `- [${slug}](${siteUrl}/articles/series/${slug}.md)`)
|
|
1488
|
+
];
|
|
1489
|
+
if (lines.length === 0) return [];
|
|
1490
|
+
return ["## Collections", "", ...lines];
|
|
1491
|
+
}
|
|
1492
|
+
function generateLlmsFullTxt(articles, config) {
|
|
1493
|
+
return __async(this, null, function* () {
|
|
1494
|
+
const crawlable = articles.filter((article) => article.aiCrawl === true);
|
|
1495
|
+
const documents = yield Promise.all(
|
|
1496
|
+
crawlable.map((article) => __async(this, null, function* () {
|
|
1497
|
+
const body = yield getArticleMarkdown(article.slug, config);
|
|
1498
|
+
if (body === null) return null;
|
|
1499
|
+
return `${buildMarkdownTwinHeader(article, config, body)}${body.trimStart()}`;
|
|
1500
|
+
}))
|
|
1501
|
+
);
|
|
1502
|
+
return [
|
|
1503
|
+
...buildLlmsHeader(config),
|
|
1504
|
+
...documents.filter((doc) => doc !== null)
|
|
1505
|
+
].join("\n");
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1134
1508
|
function generateArticleStaticParams() {
|
|
1135
1509
|
return getAvailableArticleSlugs().map((slug) => ({ slug }));
|
|
1136
1510
|
}
|
|
@@ -1196,7 +1570,7 @@ function generateArticleMetadata(slug, config) {
|
|
|
1196
1570
|
const markdownUrl = getArticleMarkdownUrl(article, config);
|
|
1197
1571
|
const authorNames = getArticleAuthors(article, config).map((author) => author.name);
|
|
1198
1572
|
return {
|
|
1199
|
-
title:
|
|
1573
|
+
title: formatPageTitle(search.title, config),
|
|
1200
1574
|
description,
|
|
1201
1575
|
keywords: [...((_b = article.tags) != null ? _b : []).map((tag) => tag.toLowerCase())].join(", "),
|
|
1202
1576
|
openGraph: __spreadProps(__spreadValues(__spreadValues(__spreadValues({
|
|
@@ -1250,7 +1624,7 @@ function generateArticlesIndexMetadata(config) {
|
|
|
1250
1624
|
var _a, _b;
|
|
1251
1625
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1252
1626
|
const indexUrl = `${siteUrl}/articles`;
|
|
1253
|
-
const title =
|
|
1627
|
+
const title = formatPageTitle("Articles", config);
|
|
1254
1628
|
const description = (_b = (_a = config.hero) == null ? void 0 : _a.description) != null ? _b : `Expert analysis and insights from ${config.siteName}.`;
|
|
1255
1629
|
return {
|
|
1256
1630
|
title,
|
|
@@ -1298,7 +1672,7 @@ function generateCategoryMetadata(categorySlug, config) {
|
|
|
1298
1672
|
const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[categorySlug];
|
|
1299
1673
|
const fallback = `Browse ${articles.length} article${articles.length === 1 ? "" : "s"} in the ${categoryName} category.`;
|
|
1300
1674
|
const description = typeof raw === "string" ? raw : (_b = raw == null ? void 0 : raw.short) != null ? _b : fallback;
|
|
1301
|
-
const title = `${categoryName} Articles
|
|
1675
|
+
const title = formatPageTitle(`${categoryName} Articles`, config);
|
|
1302
1676
|
return {
|
|
1303
1677
|
title,
|
|
1304
1678
|
description,
|
|
@@ -1342,7 +1716,7 @@ function generateSeriesMetadata(seriesSlug, config) {
|
|
|
1342
1716
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1343
1717
|
const seriesUrl = `${siteUrl}/articles/series/${seriesSlug}`;
|
|
1344
1718
|
const description = `Follow the ${seriesName} series - ${articles.length} article${articles.length === 1 ? "" : "s"} on ${config.siteName}.`;
|
|
1345
|
-
const title = `${seriesName} Series
|
|
1719
|
+
const title = formatPageTitle(`${seriesName} Series`, config);
|
|
1346
1720
|
return {
|
|
1347
1721
|
title,
|
|
1348
1722
|
description,
|
|
@@ -1384,7 +1758,7 @@ function generateAuthorMetadata(authorSlug, config) {
|
|
|
1384
1758
|
if (!author || config.showAuthorPage === false) return { title: "Author Not Found" };
|
|
1385
1759
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1386
1760
|
const authorUrl = (_a = author.url) != null ? _a : `${siteUrl}/articles/authors/${author.slug}`;
|
|
1387
|
-
const title = `${author.name} Articles
|
|
1761
|
+
const title = formatPageTitle(`${author.name} Articles`, config);
|
|
1388
1762
|
return {
|
|
1389
1763
|
title,
|
|
1390
1764
|
description: author.bio,
|
|
@@ -1566,17 +1940,41 @@ function resolveAuthorAvatar(author, config) {
|
|
|
1566
1940
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1567
1941
|
return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
|
|
1568
1942
|
}
|
|
1943
|
+
function newestArticleDate(articles) {
|
|
1944
|
+
var _a;
|
|
1945
|
+
let newest;
|
|
1946
|
+
for (const article of articles) {
|
|
1947
|
+
const stamp = (_a = article.lastmod) != null ? _a : article.date;
|
|
1948
|
+
if (!stamp) continue;
|
|
1949
|
+
const parsed = new Date(stamp);
|
|
1950
|
+
if (Number.isNaN(parsed.getTime())) continue;
|
|
1951
|
+
if (!newest || parsed > newest) newest = parsed;
|
|
1952
|
+
}
|
|
1953
|
+
return newest;
|
|
1954
|
+
}
|
|
1955
|
+
function paginationEntries(basePath, itemCount, pageSize, lastModified, priority) {
|
|
1956
|
+
const totalPages = getTotalPages(itemCount, pageSize);
|
|
1957
|
+
const entries = [];
|
|
1958
|
+
for (let page = 2; page <= totalPages; page++) {
|
|
1959
|
+
entries.push({
|
|
1960
|
+
url: buildPageUrl(basePath, page),
|
|
1961
|
+
lastModified,
|
|
1962
|
+
changeFrequency: "weekly",
|
|
1963
|
+
priority
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
return entries;
|
|
1967
|
+
}
|
|
1569
1968
|
function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
1570
1969
|
return __async(this, null, function* () {
|
|
1571
|
-
|
|
1970
|
+
var _a, _b;
|
|
1971
|
+
const config = typeof baseUrlOrConfig === "string" ? void 0 : baseUrlOrConfig;
|
|
1972
|
+
const baseUrl = ((_a = config == null ? void 0 : config.siteUrl) != null ? _a : baseUrlOrConfig).replace(/\/$/, "");
|
|
1572
1973
|
try {
|
|
1573
|
-
const [articles, categories] = yield Promise.all([
|
|
1574
|
-
getAllArticles(typeof baseUrlOrConfig === "string" ? void 0 : baseUrlOrConfig),
|
|
1575
|
-
getAllCategories()
|
|
1576
|
-
]);
|
|
1974
|
+
const [articles, categories] = yield Promise.all([getAllArticles(config), getAllCategories()]);
|
|
1577
1975
|
const articleEntries = articles.map((article) => {
|
|
1578
|
-
var
|
|
1579
|
-
const dateStr = (
|
|
1976
|
+
var _a2;
|
|
1977
|
+
const dateStr = (_a2 = article.lastmod) != null ? _a2 : article.date;
|
|
1580
1978
|
const lastModified = dateStr ? new Date(dateStr) : void 0;
|
|
1581
1979
|
return {
|
|
1582
1980
|
url: `${baseUrl}/articles/${article.slug}`,
|
|
@@ -1587,16 +1985,86 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
1587
1985
|
});
|
|
1588
1986
|
const categoryEntries = categories.map((cat) => ({
|
|
1589
1987
|
url: `${baseUrl}/articles/category/${cat.slug}`,
|
|
1590
|
-
lastModified:
|
|
1988
|
+
lastModified: newestArticleDate(
|
|
1989
|
+
articles.filter(
|
|
1990
|
+
(article) => {
|
|
1991
|
+
var _a2;
|
|
1992
|
+
return ((_a2 = article.categories) != null ? _a2 : []).some((name) => categoryToSlug(name) === cat.slug);
|
|
1993
|
+
}
|
|
1994
|
+
)
|
|
1995
|
+
),
|
|
1591
1996
|
changeFrequency: "weekly",
|
|
1592
1997
|
priority: 0.7
|
|
1593
1998
|
}));
|
|
1594
|
-
const
|
|
1999
|
+
const authors = config && config.showAuthorPage !== false ? getAllAuthors(config) : [];
|
|
2000
|
+
const authorEntries = authors.map((author) => ({
|
|
1595
2001
|
url: `${baseUrl}/articles/authors/${author.slug}`,
|
|
2002
|
+
lastModified: newestArticleDate(
|
|
2003
|
+
articles.filter(
|
|
2004
|
+
(article) => getArticleAuthors(article, config).some((profile) => profile.slug === author.slug)
|
|
2005
|
+
)
|
|
2006
|
+
),
|
|
1596
2007
|
changeFrequency: "monthly",
|
|
1597
2008
|
priority: 0.6
|
|
1598
2009
|
}));
|
|
1599
|
-
|
|
2010
|
+
const seriesSlugs = [...new Set(articles.map((a) => a.seriesSlug).filter(Boolean))];
|
|
2011
|
+
const seriesEntries = seriesSlugs.map((seriesSlug) => ({
|
|
2012
|
+
url: `${baseUrl}/articles/series/${seriesSlug}`,
|
|
2013
|
+
lastModified: newestArticleDate(articles.filter((a) => a.seriesSlug === seriesSlug)),
|
|
2014
|
+
changeFrequency: "weekly",
|
|
2015
|
+
priority: 0.6
|
|
2016
|
+
}));
|
|
2017
|
+
const pageEntries = [];
|
|
2018
|
+
if ((config == null ? void 0 : config.listingPagination) === "pages") {
|
|
2019
|
+
const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
|
|
2020
|
+
pageEntries.push(
|
|
2021
|
+
...paginationEntries(
|
|
2022
|
+
`${baseUrl}/articles`,
|
|
2023
|
+
articles.length,
|
|
2024
|
+
pageSize,
|
|
2025
|
+
newestArticleDate(articles),
|
|
2026
|
+
0.5
|
|
2027
|
+
)
|
|
2028
|
+
);
|
|
2029
|
+
for (const cat of categories) {
|
|
2030
|
+
const inCategory = articles.filter(
|
|
2031
|
+
(article) => {
|
|
2032
|
+
var _a2;
|
|
2033
|
+
return ((_a2 = article.categories) != null ? _a2 : []).some((name) => categoryToSlug(name) === cat.slug);
|
|
2034
|
+
}
|
|
2035
|
+
);
|
|
2036
|
+
pageEntries.push(
|
|
2037
|
+
...paginationEntries(
|
|
2038
|
+
`${baseUrl}/articles/category/${cat.slug}`,
|
|
2039
|
+
inCategory.length,
|
|
2040
|
+
pageSize,
|
|
2041
|
+
newestArticleDate(inCategory),
|
|
2042
|
+
0.4
|
|
2043
|
+
)
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
for (const author of authors) {
|
|
2047
|
+
const byAuthor = articles.filter(
|
|
2048
|
+
(article) => getArticleAuthors(article, config).some((profile) => profile.slug === author.slug)
|
|
2049
|
+
);
|
|
2050
|
+
pageEntries.push(
|
|
2051
|
+
...paginationEntries(
|
|
2052
|
+
`${baseUrl}/articles/authors/${author.slug}`,
|
|
2053
|
+
byAuthor.length,
|
|
2054
|
+
pageSize,
|
|
2055
|
+
newestArticleDate(byAuthor),
|
|
2056
|
+
0.4
|
|
2057
|
+
)
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return [
|
|
2062
|
+
...articleEntries,
|
|
2063
|
+
...categoryEntries,
|
|
2064
|
+
...authorEntries,
|
|
2065
|
+
...seriesEntries,
|
|
2066
|
+
...pageEntries
|
|
2067
|
+
];
|
|
1600
2068
|
} catch (e) {
|
|
1601
2069
|
return [];
|
|
1602
2070
|
}
|
|
@@ -1605,6 +2073,7 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
1605
2073
|
|
|
1606
2074
|
// src/renderMdx.tsx
|
|
1607
2075
|
var import_react2 = __toESM(require("react"), 1);
|
|
2076
|
+
var import_link = __toESM(require("next/link"), 1);
|
|
1608
2077
|
var devRuntime = __toESM(require("react/jsx-dev-runtime"), 1);
|
|
1609
2078
|
var runtime = __toESM(require("react/jsx-runtime"), 1);
|
|
1610
2079
|
var import_mdx = require("@mdx-js/mdx");
|
|
@@ -1620,6 +2089,19 @@ function makeImgComponent(basePath) {
|
|
|
1620
2089
|
return import_react2.default.createElement("img", __spreadValues({ src: resolvedSrc, alt }, props));
|
|
1621
2090
|
};
|
|
1622
2091
|
}
|
|
2092
|
+
function isInternalNavigableHref(href, siteUrl) {
|
|
2093
|
+
if (!href || href.startsWith("#") || isNonBrowserNavigationLink(href)) return false;
|
|
2094
|
+
return !isExternalHttpLink(href, siteUrl);
|
|
2095
|
+
}
|
|
2096
|
+
function makeLinkComponent(siteUrl) {
|
|
2097
|
+
return function MdxLink(_a) {
|
|
2098
|
+
var _b = _a, { href, children } = _b, props = __objRest(_b, ["href", "children"]);
|
|
2099
|
+
if (typeof href === "string" && isInternalNavigableHref(href, siteUrl)) {
|
|
2100
|
+
return import_react2.default.createElement(import_link.default, __spreadValues({ href }, props), children);
|
|
2101
|
+
}
|
|
2102
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", __spreadProps(__spreadValues({ href }, props), { children }));
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
1623
2105
|
function renderMdxSource(source, basePath, config) {
|
|
1624
2106
|
return __async(this, null, function* () {
|
|
1625
2107
|
const isDevelopment = process.env.NODE_ENV === "development";
|
|
@@ -1634,8 +2116,10 @@ function renderMdxSource(source, basePath, config) {
|
|
|
1634
2116
|
]
|
|
1635
2117
|
}));
|
|
1636
2118
|
const Content = mdxModule.default;
|
|
1637
|
-
const internalComponents =
|
|
1638
|
-
|
|
2119
|
+
const internalComponents = __spreadValues({
|
|
2120
|
+
a: makeLinkComponent(config == null ? void 0 : config.siteUrl)
|
|
2121
|
+
}, basePath ? { img: makeImgComponent(basePath) } : {});
|
|
2122
|
+
const components = __spreadValues(__spreadValues({}, internalComponents), config == null ? void 0 : config.mdxComponents);
|
|
1639
2123
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Content, { components });
|
|
1640
2124
|
});
|
|
1641
2125
|
}
|
|
@@ -1754,8 +2238,32 @@ function ArticleTOC({ toc, className }) {
|
|
|
1754
2238
|
);
|
|
1755
2239
|
}
|
|
1756
2240
|
|
|
2241
|
+
// src/ArticleAnswer.tsx
|
|
2242
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
2243
|
+
function ArticleAnswer({
|
|
2244
|
+
article,
|
|
2245
|
+
label = "The short answer",
|
|
2246
|
+
className
|
|
2247
|
+
}) {
|
|
2248
|
+
var _a;
|
|
2249
|
+
if (!((_a = article.answer) == null ? void 0 : _a.trim())) return null;
|
|
2250
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2251
|
+
"aside",
|
|
2252
|
+
{
|
|
2253
|
+
className: `mb-8 rounded-lg border-l-4 border-primary bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
|
|
2254
|
+
style: { borderLeftWidth: "4px" },
|
|
2255
|
+
children: [
|
|
2256
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mb-2 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
|
|
2257
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-base leading-relaxed", children: article.answer })
|
|
2258
|
+
]
|
|
2259
|
+
}
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
1757
2263
|
// src/validateArticles.ts
|
|
1758
2264
|
var UNSAFE_URL_SCHEME = /^\s*(javascript|data|vbscript):/i;
|
|
2265
|
+
var THIN_CONTENT_WORDS = 300;
|
|
2266
|
+
var STALE_CONTENT_MONTHS = 18;
|
|
1759
2267
|
var SEARCH_TITLE_MAX = 60;
|
|
1760
2268
|
var SEARCH_DESCRIPTION_MAX = 160;
|
|
1761
2269
|
var SOCIAL_TITLE_MAX = 95;
|
|
@@ -1899,12 +2407,15 @@ function checkRequiredFrontmatter(articles) {
|
|
|
1899
2407
|
}
|
|
1900
2408
|
return issues;
|
|
1901
2409
|
}
|
|
1902
|
-
function checkDiscoveryFieldLengths(articles) {
|
|
2410
|
+
function checkDiscoveryFieldLengths(articles, config) {
|
|
2411
|
+
var _a, _b;
|
|
1903
2412
|
const issues = [];
|
|
1904
2413
|
for (const article of articles) {
|
|
2414
|
+
const effectiveTitle = formatPageTitle((_a = article.searchTitle) != null ? _a : article.title, config);
|
|
2415
|
+
const effectiveDescription = (_b = article.searchDescription) != null ? _b : article.excerpt;
|
|
1905
2416
|
const checks = [
|
|
1906
|
-
[
|
|
1907
|
-
[
|
|
2417
|
+
[effectiveTitle, "effective-title-too-long", SEARCH_TITLE_MAX],
|
|
2418
|
+
[effectiveDescription, "effective-description-too-long", SEARCH_DESCRIPTION_MAX],
|
|
1908
2419
|
[article.socialTitle, "social-title-too-long", SOCIAL_TITLE_MAX],
|
|
1909
2420
|
[article.socialDescription, "social-description-too-long", SOCIAL_DESCRIPTION_MAX]
|
|
1910
2421
|
];
|
|
@@ -1921,6 +2432,101 @@ function checkDiscoveryFieldLengths(articles) {
|
|
|
1921
2432
|
}
|
|
1922
2433
|
return issues;
|
|
1923
2434
|
}
|
|
2435
|
+
function checkAnswerability(articles) {
|
|
2436
|
+
var _a, _b, _c;
|
|
2437
|
+
const issues = [];
|
|
2438
|
+
for (const article of articles) {
|
|
2439
|
+
const hasQuestionHeading = ((_a = article.toc) != null ? _a : []).some(
|
|
2440
|
+
(item) => item.depth === 2 && item.text.trim().endsWith("?")
|
|
2441
|
+
);
|
|
2442
|
+
if (!article.answer && !((_b = article.faq) == null ? void 0 : _b.length) && !hasQuestionHeading) {
|
|
2443
|
+
issues.push({
|
|
2444
|
+
severity: "warning",
|
|
2445
|
+
code: "no-answer",
|
|
2446
|
+
message: "Article has no `answer`, no `faq`, and no question-shaped heading - nothing for an answer engine to lift.",
|
|
2447
|
+
articleSlug: article.slug
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
if (article.wordCount !== void 0 && article.wordCount < THIN_CONTENT_WORDS) {
|
|
2451
|
+
issues.push({
|
|
2452
|
+
severity: "warning",
|
|
2453
|
+
code: "thin-content",
|
|
2454
|
+
message: `Article is ${article.wordCount} words (under ${THIN_CONTENT_WORDS}).`,
|
|
2455
|
+
articleSlug: article.slug
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
if (!((_c = article.about) == null ? void 0 : _c.length)) {
|
|
2459
|
+
issues.push({
|
|
2460
|
+
severity: "warning",
|
|
2461
|
+
code: "missing-about",
|
|
2462
|
+
message: "Article has no `about` entity references.",
|
|
2463
|
+
articleSlug: article.slug
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
return issues;
|
|
2468
|
+
}
|
|
2469
|
+
function checkEntityReferences(articles, config) {
|
|
2470
|
+
var _a;
|
|
2471
|
+
const registry = config.entities;
|
|
2472
|
+
if (!registry) return [];
|
|
2473
|
+
const known = new Set(Object.values(registry).map((entity) => entity.name));
|
|
2474
|
+
const issues = [];
|
|
2475
|
+
for (const article of articles) {
|
|
2476
|
+
for (const entity of (_a = article.about) != null ? _a : []) {
|
|
2477
|
+
if (!known.has(entity.name) && !entity.sameAs) {
|
|
2478
|
+
issues.push({
|
|
2479
|
+
severity: "warning",
|
|
2480
|
+
code: "unknown-entity",
|
|
2481
|
+
message: `about entry "${entity.name}" is not in config.entities and has no sameAs.`,
|
|
2482
|
+
articleSlug: article.slug
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return issues;
|
|
2488
|
+
}
|
|
2489
|
+
function checkStaleContent(articles, now) {
|
|
2490
|
+
var _a;
|
|
2491
|
+
const cutoff = new Date(now);
|
|
2492
|
+
cutoff.setMonth(cutoff.getMonth() - STALE_CONTENT_MONTHS);
|
|
2493
|
+
const issues = [];
|
|
2494
|
+
for (const article of articles) {
|
|
2495
|
+
const stamp = (_a = article.lastmod) != null ? _a : article.date;
|
|
2496
|
+
if (!stamp) continue;
|
|
2497
|
+
const parsed = new Date(stamp);
|
|
2498
|
+
if (Number.isNaN(parsed.getTime())) continue;
|
|
2499
|
+
if (parsed < cutoff) {
|
|
2500
|
+
issues.push({
|
|
2501
|
+
severity: "warning",
|
|
2502
|
+
code: "stale-content",
|
|
2503
|
+
message: `Last updated ${stamp}, over ${STALE_CONTENT_MONTHS} months ago.`,
|
|
2504
|
+
articleSlug: article.slug
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
return issues;
|
|
2509
|
+
}
|
|
2510
|
+
function checkOrphanArticles(articles) {
|
|
2511
|
+
const bodies = articles.filter((article) => typeof article.content === "string");
|
|
2512
|
+
if (bodies.length === 0) return [];
|
|
2513
|
+
const issues = [];
|
|
2514
|
+
for (const article of articles) {
|
|
2515
|
+
const needle = `/articles/${article.slug}`;
|
|
2516
|
+
const linked = bodies.some(
|
|
2517
|
+
(other) => other.slug !== article.slug && other.content.includes(needle)
|
|
2518
|
+
);
|
|
2519
|
+
if (!linked) {
|
|
2520
|
+
issues.push({
|
|
2521
|
+
severity: "warning",
|
|
2522
|
+
code: "orphan-article",
|
|
2523
|
+
message: "No other article links to this one.",
|
|
2524
|
+
articleSlug: article.slug
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
return issues;
|
|
2529
|
+
}
|
|
1924
2530
|
function checkCategorySlugs(articles) {
|
|
1925
2531
|
var _a;
|
|
1926
2532
|
const issues = [];
|
|
@@ -1944,7 +2550,8 @@ function checkCategorySlugs(articles) {
|
|
|
1944
2550
|
}
|
|
1945
2551
|
return issues;
|
|
1946
2552
|
}
|
|
1947
|
-
function validateArticles(articles, config) {
|
|
2553
|
+
function validateArticles(articles, config, options) {
|
|
2554
|
+
var _a;
|
|
1948
2555
|
const errors = [
|
|
1949
2556
|
...checkDuplicateCanonicalUrls(articles),
|
|
1950
2557
|
...checkAuthorReferences(articles, config),
|
|
@@ -1954,15 +2561,19 @@ function validateArticles(articles, config) {
|
|
|
1954
2561
|
];
|
|
1955
2562
|
const warnings = [
|
|
1956
2563
|
...checkRequiredFrontmatter(articles),
|
|
1957
|
-
...checkDiscoveryFieldLengths(articles),
|
|
2564
|
+
...checkDiscoveryFieldLengths(articles, config),
|
|
2565
|
+
...checkAnswerability(articles),
|
|
2566
|
+
...checkEntityReferences(articles, config),
|
|
2567
|
+
...checkStaleContent(articles, (_a = options == null ? void 0 : options.now) != null ? _a : /* @__PURE__ */ new Date()),
|
|
2568
|
+
...checkOrphanArticles(articles),
|
|
1958
2569
|
...checkCategorySlugs(articles)
|
|
1959
2570
|
];
|
|
1960
2571
|
return { ok: errors.length === 0, errors, warnings };
|
|
1961
2572
|
}
|
|
1962
|
-
function validateAllArticles(config) {
|
|
2573
|
+
function validateAllArticles(config, options) {
|
|
1963
2574
|
return __async(this, null, function* () {
|
|
1964
2575
|
const articles = yield getAllArticles(config);
|
|
1965
|
-
return validateArticles(articles, config);
|
|
2576
|
+
return validateArticles(articles, config, options);
|
|
1966
2577
|
});
|
|
1967
2578
|
}
|
|
1968
2579
|
|
|
@@ -1976,16 +2587,21 @@ function emitArticleEvent(handler, event) {
|
|
|
1976
2587
|
}
|
|
1977
2588
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1978
2589
|
0 && (module.exports = {
|
|
2590
|
+
AI_CRAWLERS,
|
|
2591
|
+
ArticleAnswer,
|
|
1979
2592
|
ArticleContent,
|
|
1980
2593
|
ArticleTOC,
|
|
1981
2594
|
buildArticleBreadcrumbs,
|
|
1982
2595
|
buildAuthorBreadcrumbs,
|
|
1983
2596
|
buildCategoryBreadcrumbs,
|
|
2597
|
+
buildMarkdownTwinHeader,
|
|
1984
2598
|
buildPageUrl,
|
|
1985
2599
|
buildPaginationLinks,
|
|
1986
2600
|
categoryToSlug,
|
|
2601
|
+
deriveFaqFromHeadings,
|
|
1987
2602
|
emitArticleEvent,
|
|
1988
2603
|
extractToc,
|
|
2604
|
+
formatPageTitle,
|
|
1989
2605
|
generateArticleMetadata,
|
|
1990
2606
|
generateArticleStaticParams,
|
|
1991
2607
|
generateArticlesIndexMetadata,
|
|
@@ -1997,6 +2613,8 @@ function emitArticleEvent(handler, event) {
|
|
|
1997
2613
|
generateCategoryPageMetadata,
|
|
1998
2614
|
generateCategoryStaticParams,
|
|
1999
2615
|
generateListingPageStaticParams,
|
|
2616
|
+
generateLlmsFullTxt,
|
|
2617
|
+
generateLlmsTxt,
|
|
2000
2618
|
generateRssFeed,
|
|
2001
2619
|
generateSeriesMetadata,
|
|
2002
2620
|
generateSeriesStaticParams,
|
|
@@ -2017,16 +2635,24 @@ function emitArticleEvent(handler, event) {
|
|
|
2017
2635
|
getArticlesByCategory,
|
|
2018
2636
|
getArticlesBySeries,
|
|
2019
2637
|
getAuthorBySlug,
|
|
2638
|
+
getAuthorMarkdown,
|
|
2020
2639
|
getAvailableArticleSlugs,
|
|
2021
2640
|
getBreadcrumbsConfig,
|
|
2641
|
+
getCategoryMarkdown,
|
|
2022
2642
|
getContentSlotBoundaries,
|
|
2643
|
+
getMarkdownTwinResponse,
|
|
2644
|
+
getOrganizationId,
|
|
2023
2645
|
getPath,
|
|
2024
2646
|
getPathArticles,
|
|
2647
|
+
getPersonId,
|
|
2025
2648
|
getRelatedArticlesByCategory,
|
|
2026
2649
|
getRelatedContent,
|
|
2650
|
+
getSeriesMarkdown,
|
|
2027
2651
|
getTotalPages,
|
|
2652
|
+
getWebSiteId,
|
|
2028
2653
|
isPageOutOfRange,
|
|
2029
2654
|
markdownToHtml,
|
|
2655
|
+
matchAiCrawler,
|
|
2030
2656
|
paginateArticles,
|
|
2031
2657
|
parsePageParam,
|
|
2032
2658
|
resolveAuthorAvatar,
|