@fullstackdatasolutions/articles 0.8.2 → 0.9.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/dist/server.js CHANGED
@@ -85,6 +85,48 @@ function reportArticlesError(report) {
85
85
  }
86
86
 
87
87
  // src/markdown.ts
88
+ var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
89
+ function isNonBrowserNavigationLink(href) {
90
+ return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href) && !href.startsWith("http://") && !href.startsWith("https://");
91
+ }
92
+ function getOrigin(url) {
93
+ if (!url) return null;
94
+ try {
95
+ return new URL(url).origin;
96
+ } catch (e) {
97
+ return null;
98
+ }
99
+ }
100
+ function isExternalHttpLink(href, siteUrl) {
101
+ if (!href.startsWith("http://") && !href.startsWith("https://")) return false;
102
+ const siteOrigin = getOrigin(siteUrl);
103
+ if (!siteOrigin) return true;
104
+ return getOrigin(href) !== siteOrigin;
105
+ }
106
+ function shouldOpenInNewTab(href, options = {}) {
107
+ var _a;
108
+ if (!href || href.startsWith("#") || isNonBrowserNavigationLink(href)) return false;
109
+ const strategy = (_a = options.strategy) != null ? _a : DEFAULT_LINK_TARGET_STRATEGY;
110
+ if (strategy === "same-tab") return false;
111
+ if (strategy === "all-new-tab") return true;
112
+ return isExternalHttpLink(href, options.siteUrl);
113
+ }
114
+ function applyLinkTarget(props, options) {
115
+ const href = typeof props.href === "string" ? props.href : "";
116
+ if (shouldOpenInNewTab(href, options)) {
117
+ props.target = "_blank";
118
+ props.rel = "noopener noreferrer";
119
+ return;
120
+ }
121
+ delete props.target;
122
+ delete props.rel;
123
+ }
124
+ function getLinkTargetOptions(config) {
125
+ return {
126
+ strategy: config == null ? void 0 : config.linkTargetStrategy,
127
+ siteUrl: config == null ? void 0 : config.siteUrl
128
+ };
129
+ }
88
130
  function sanitizeImagePath(rawPath, articleSlug) {
89
131
  if (!rawPath || typeof rawPath !== "string") {
90
132
  return null;
@@ -206,7 +248,7 @@ function processFootnotesSection(node) {
206
248
  node.children = [hr, h3, ol];
207
249
  if (node.properties) node.properties.className = void 0;
208
250
  }
209
- var customRenderer = () => {
251
+ var customRenderer = (linkTargetOptions = {}) => {
210
252
  return (tree) => {
211
253
  visit(tree, "element", (node) => {
212
254
  if (node.tagName) {
@@ -229,11 +271,7 @@ var customRenderer = () => {
229
271
  break;
230
272
  case "a": {
231
273
  props.className = "text-primary hover:underline transition-colors duration-200";
232
- const href = typeof props.href === "string" ? props.href : "";
233
- if (!href.startsWith("#")) {
234
- props.target = "_blank";
235
- props.rel = "noopener noreferrer";
236
- }
274
+ applyLinkTarget(props, linkTargetOptions);
237
275
  break;
238
276
  }
239
277
  case "ul":
@@ -303,10 +341,10 @@ var rehypeProcessImages = (options = {}) => {
303
341
  });
304
342
  };
305
343
  };
306
- function markdownToHtml(markdown, articleSlug) {
344
+ function markdownToHtml(markdown, articleSlug, config) {
307
345
  return __async(this, null, function* () {
308
346
  try {
309
- let processor = remark().use(remarkParse).use(remarkGfm).use(remarkGithubBlockquoteAlert).use(remarkRehype).use(customRenderer).use(rehypeSlug).use(rehypePrism).use(rehypeSanitize, {
347
+ let processor = remark().use(remarkParse).use(remarkGfm).use(remarkGithubBlockquoteAlert).use(remarkRehype).use(customRenderer, getLinkTargetOptions(config)).use(rehypeSlug).use(rehypePrism).use(rehypeSanitize, {
310
348
  attributes: {
311
349
  "*": ["className", "class", "id"],
312
350
  a: ["href", "target", "rel", "id"],
@@ -524,33 +562,35 @@ function getArticleSummary(slug) {
524
562
  }
525
563
  });
526
564
  }
527
- var getArticleMetadata = cache((slug) => __async(void 0, null, function* () {
528
- try {
529
- const summary = yield getArticleSummary(slug);
530
- if (!summary) return null;
531
- const found = findArticleFile(slug);
532
- if (!found) return null;
533
- const fileContent = fs.readFileSync(found.filePath, "utf8");
534
- const { content: markdownContent } = matter(fileContent);
535
- const toc = yield extractToc(markdownContent);
536
- let htmlContent;
537
- let mdxSource;
538
- if (found.contentType === "mdx") {
539
- mdxSource = markdownContent;
540
- } else {
541
- htmlContent = yield markdownToHtml(markdownContent, slug);
565
+ var getArticleMetadata = cache(
566
+ (slug, config) => __async(void 0, null, function* () {
567
+ try {
568
+ const summary = yield getArticleSummary(slug);
569
+ if (!summary) return null;
570
+ const found = findArticleFile(slug);
571
+ if (!found) return null;
572
+ const fileContent = fs.readFileSync(found.filePath, "utf8");
573
+ const { content: markdownContent } = matter(fileContent);
574
+ const toc = yield extractToc(markdownContent);
575
+ let htmlContent;
576
+ let mdxSource;
577
+ if (found.contentType === "mdx") {
578
+ mdxSource = markdownContent;
579
+ } else {
580
+ htmlContent = yield markdownToHtml(markdownContent, slug, config);
581
+ }
582
+ return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
583
+ } catch (error) {
584
+ reportArticlesError({
585
+ code: "article-load-failed",
586
+ message: "Unable to load article metadata.",
587
+ error,
588
+ context: { slug }
589
+ });
590
+ return null;
542
591
  }
543
- return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
544
- } catch (error) {
545
- reportArticlesError({
546
- code: "article-load-failed",
547
- message: "Unable to load article metadata.",
548
- error,
549
- context: { slug }
550
- });
551
- return null;
552
- }
553
- }));
592
+ })
593
+ );
554
594
  var getAllArticles = cache(() => __async(void 0, null, function* () {
555
595
  const slugs = getAvailableArticleSlugs();
556
596
  const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
@@ -691,6 +731,43 @@ function getArticlesByCategory(categorySlug) {
691
731
  }
692
732
 
693
733
  // src/seoUtils.ts
734
+ function escapeXml(str) {
735
+ return str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
736
+ }
737
+ function generateRssFeed(articles, config) {
738
+ var _a;
739
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
740
+ const showAuthor = config.showAuthor !== false;
741
+ const items = articles.map((article) => {
742
+ const url = `${siteUrl}/articles/${article.slug}`;
743
+ const pubDate = article.date ? new Date(article.date).toUTCString() : "";
744
+ const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : "";
745
+ return [
746
+ " <item>",
747
+ ` <title><![CDATA[${article.title}]]></title>`,
748
+ ` <link>${url}</link>`,
749
+ ` <guid isPermaLink="true">${url}</guid>`,
750
+ pubDate ? ` <pubDate>${pubDate}</pubDate>` : "",
751
+ article.excerpt ? ` <description><![CDATA[${article.excerpt}]]></description>` : "",
752
+ showAuthor && article.author ? ` <author>${escapeXml(article.author)}</author>` : "",
753
+ article.category ? ` <category><![CDATA[${article.category}]]></category>` : "",
754
+ imageUrl ? ` <media:content url="${imageUrl}" medium="image" width="1200" height="630"/>` : "",
755
+ " </item>"
756
+ ].filter(Boolean).join("\n");
757
+ }).join("\n");
758
+ const description = (_a = config.description) != null ? _a : `${config.siteName} articles`;
759
+ return `<?xml version="1.0" encoding="UTF-8" ?>
760
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
761
+ <channel>
762
+ <title><![CDATA[${config.siteName}]]></title>
763
+ <link>${siteUrl}/articles</link>
764
+ <description><![CDATA[${description}]]></description>
765
+ <language>en</language>
766
+ <atom:link href="${siteUrl}/articles/feed.xml" rel="self" type="application/rss+xml" />
767
+ ${items}
768
+ </channel>
769
+ </rss>`;
770
+ }
694
771
  function generateArticleStaticParams() {
695
772
  return getAvailableArticleSlugs().map((slug) => ({ slug }));
696
773
  }
@@ -908,14 +985,14 @@ function makeImgComponent(basePath) {
908
985
  return React.createElement("img", __spreadValues({ src: resolvedSrc, alt }, props));
909
986
  };
910
987
  }
911
- function renderMdxSource(source, basePath) {
988
+ function renderMdxSource(source, basePath, config) {
912
989
  return __async(this, null, function* () {
913
990
  const isDevelopment = process.env.NODE_ENV === "development";
914
991
  const mdxModule = yield evaluate(source, __spreadProps(__spreadValues({}, isDevelopment ? devRuntime : runtime), {
915
992
  development: isDevelopment,
916
993
  remarkPlugins: [remarkGfm2, remarkGithubBlockquoteAlert2],
917
994
  rehypePlugins: [
918
- customRenderer,
995
+ [customRenderer, { strategy: config == null ? void 0 : config.linkTargetStrategy, siteUrl: config == null ? void 0 : config.siteUrl }],
919
996
  rehypeSlug2,
920
997
  // @ts-ignore
921
998
  rehypePrism2
@@ -930,9 +1007,9 @@ function renderMdxSource(source, basePath) {
930
1007
  // src/ArticleContent.tsx
931
1008
  import { jsx as jsx2 } from "react/jsx-runtime";
932
1009
  function ArticleContent(_0) {
933
- return __async(this, arguments, function* ({ article, className }) {
1010
+ return __async(this, arguments, function* ({ article, className, config }) {
934
1011
  if (article.contentType === "mdx" && article.mdxSource) {
935
- const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`);
1012
+ const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config);
936
1013
  return /* @__PURE__ */ jsx2("div", { className, children: content });
937
1014
  }
938
1015
  return /* @__PURE__ */ jsx2("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
@@ -972,6 +1049,7 @@ export {
972
1049
  generateArticlesIndexMetadata,
973
1050
  generateCategoryMetadata,
974
1051
  generateCategoryStaticParams,
1052
+ generateRssFeed,
975
1053
  getAdjacentArticles,
976
1054
  getAiRobotsTxtRules,
977
1055
  getAllArticles,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server-articles.ts","../src/markdown.ts","../src/errorReporting.ts","../src/seoUtils.ts","../src/renderMdx.tsx","../src/ArticleContent.tsx","../src/ArticleTOC.tsx"],"sourcesContent":["import { cache } from 'react'\nimport matter from 'gray-matter'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport readingTime from 'reading-time'\nimport { markdownToHtml, extractToc } from './markdown'\nimport type { Article, CategoryInfo, FaqItem, HowToStep } from './articleTypes'\nimport type { ArticlesConfig } from './articlesConfig'\nimport { reportArticlesError } from './errorReporting'\n\nconst articlesDirectory = path.join(/* turbopackIgnore: true */ process.cwd(), 'public/articles')\n\nfunction getReadingTime(content: string): string {\n return readingTime(content).text\n}\n\nfunction findArticleImage(slug: string): string | null {\n try {\n const articleDir = path.join(articlesDirectory, slug)\n const entries: (fs.Dirent | string)[] = fs.readdirSync(articleDir, { withFileTypes: true })\n const names = entries\n .map((entry) => {\n if (typeof entry === 'string') return entry\n if (entry && typeof entry.name === 'string') return entry.name\n return null\n })\n .filter((name): name is string => Boolean(name))\n const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp']\n const imageFileName = names.find((name) =>\n imageExtensions.some((ext) => name.toLowerCase().endsWith(ext))\n )\n return imageFileName ? sanitizeImagePath(imageFileName, slug) : null\n } catch {\n return null\n }\n}\n\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') return null\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) return cleanPath\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected unsafe article image path.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected article image path with invalid characters.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n if (cleanPath.includes('/')) {\n const normalizedPath = path.normalize(cleanPath)\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected article image path traversal attempt.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n return `/articles/${articleSlug}/${cleanPath}`\n }\n return `/articles/${articleSlug}/${cleanPath}`\n}\n\nfunction findArticleFile(slug: string): { filePath: string; contentType: 'md' | 'mdx' } | null {\n const mdPath = path.join(articlesDirectory, slug, 'article.md')\n const mdxPath = path.join(articlesDirectory, slug, 'article.mdx')\n if (fs.existsSync(mdPath)) return { filePath: mdPath, contentType: 'md' }\n if (fs.existsSync(mdxPath)) return { filePath: mdxPath, contentType: 'mdx' }\n return null\n}\n\nexport function getAvailableArticleSlugs(): string[] {\n try {\n if (!fs.existsSync(articlesDirectory)) return []\n return walkArticleDir(articlesDirectory, '')\n } catch (error) {\n reportArticlesError({\n code: 'article-directory-read-failed',\n message: 'Unable to read articles directory.',\n error,\n context: { directory: articlesDirectory },\n })\n return []\n }\n}\n\nfunction walkArticleDir(dir: string, baseSlug: string): string[] {\n const slugs: string[] = []\n try {\n const items = fs.readdirSync(dir, { withFileTypes: true })\n for (const item of items) {\n if (!item.isDirectory()) continue\n const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name\n if (findArticleFile(slug) !== null) {\n slugs.push(slug)\n }\n slugs.push(...walkArticleDir(path.join(dir, item.name), slug))\n }\n } catch {\n // ignore unreadable directories\n }\n return slugs\n}\n\nfunction parseDateField(rawDate: unknown): string | undefined {\n if (!rawDate) return undefined\n try {\n const parsed = new Date(rawDate as string)\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split('T')[0]\n } catch {\n // ignore invalid dates\n }\n return undefined\n}\n\nfunction resolveFeaturedImage(rawImage: unknown, slug: string): string {\n const img = typeof rawImage === 'string' ? rawImage : ''\n if (img && !img.startsWith('http')) return sanitizeImagePath(img, slug) || '/placeholder-logo.png'\n if (!img) return findArticleImage(slug) || '/placeholder-logo.png'\n return img\n}\n\nfunction parseFaqItems(raw: unknown): FaqItem[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const items = raw.filter(\n (item): item is FaqItem =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as FaqItem).question === 'string' &&\n typeof (item as FaqItem).answer === 'string'\n )\n return items.length ? items : undefined\n}\n\nfunction parseHowToSteps(raw: unknown): HowToStep[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const steps = raw.filter(\n (item): item is HowToStep =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as HowToStep).name === 'string' &&\n typeof (item as HowToStep).text === 'string'\n )\n return steps.length ? steps : undefined\n}\n\nasync function getArticleSummary(slug: string): Promise<Article | null> {\n try {\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { data, content: markdownContent } = matter(fileContent)\n const readTime = getReadingTime(markdownContent)\n const allTags: string[] = Array.isArray(data.tags)\n ? data.tags.filter((t: unknown) => typeof t === 'string' && String(t).trim())\n : []\n const categories: string[] =\n allTags.length > 0 ? allTags.map((t: string) => t.replaceAll('-', ' ').trim()) : ['Campaigns']\n return {\n slug,\n title: data.title || slug.replaceAll('-', ' '),\n excerpt: data.excerpt || '',\n date: parseDateField(data.date),\n lastmod: parseDateField(data.lastmod),\n author: data.author || 'Andrew Blase',\n category: categories[0],\n categories,\n readTime,\n featuredImage: resolveFeaturedImage(data.featuredImage, slug),\n tags: data.tags || [],\n contentType: found.contentType,\n draft: data.draft === true,\n faq: parseFaqItems(data.faq),\n howTo: parseHowToSteps(data.howTo),\n canonicalUrl: typeof data.canonicalUrl === 'string' ? data.canonicalUrl : undefined,\n articleType: typeof data.articleType === 'string' ? data.articleType : undefined,\n series: typeof data.series === 'string' ? data.series : undefined,\n aiCrawl: data.aiCrawl === true,\n }\n } catch (error) {\n reportArticlesError({\n code: 'article-load-failed',\n message: 'Unable to load article summary.',\n error,\n context: { slug },\n })\n return null\n }\n}\n\nexport const getArticleMetadata = cache(async (slug: string): Promise<Article | null> => {\n try {\n const summary = await getArticleSummary(slug)\n if (!summary) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n const toc = await extractToc(markdownContent)\n let htmlContent: string | undefined\n let mdxSource: string | undefined\n if (found.contentType === 'mdx') {\n mdxSource = markdownContent\n } else {\n htmlContent = await markdownToHtml(markdownContent, slug)\n }\n return { ...summary, content: markdownContent, htmlContent, mdxSource, toc }\n } catch (error) {\n reportArticlesError({\n code: 'article-load-failed',\n message: 'Unable to load article metadata.',\n error,\n context: { slug },\n })\n return null\n }\n})\n\nexport const getAllArticles = cache(async (): Promise<Article[]> => {\n const slugs = getAvailableArticleSlugs()\n const articles = await Promise.all(slugs.map((slug) => getArticleSummary(slug)))\n const currentDate = new Date().toISOString().split('T')[0]\n return articles\n .filter((article): article is Article => article !== null)\n .filter((article) => !article.date || article.date <= currentDate)\n .filter((article) => !(article.draft && process.env.NODE_ENV === 'production'))\n .sort((a, b) => {\n if (!a.date && !b.date) return 0\n if (!a.date) return 1\n if (!b.date) return -1\n return new Date(b.date).getTime() - new Date(a.date).getTime()\n })\n})\n\nexport async function getAdjacentArticles(\n currentSlug: string\n): Promise<{ previous: Article | null; next: Article | null }> {\n const allArticles = await getAllArticles()\n const currentIndex = allArticles.findIndex((article) => article.slug === currentSlug)\n if (currentIndex === -1) return { previous: null, next: null }\n const previous = currentIndex < allArticles.length - 1 ? allArticles[currentIndex + 1] : null\n const next = currentIndex > 0 ? allArticles[currentIndex - 1] : null\n return { previous, next }\n}\n\nexport async function getArticleMarkdown(slug: string): Promise<string | null> {\n try {\n const summary = await getArticleSummary(slug)\n if (!summary?.aiCrawl) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n return markdownContent\n } catch (error) {\n reportArticlesError({\n code: 'article-markdown-load-failed',\n message: 'Unable to load article markdown.',\n error,\n context: { slug },\n })\n return null\n }\n}\n\nexport async function getArticleMarkdownResponse(\n slug: string,\n config: ArticlesConfig\n): Promise<Response> {\n const markdown = await getArticleMarkdown(slug)\n if (markdown === null) return new Response('Not Found', { status: 404 })\n const article = await getArticleMetadata(slug)\n return new Response(markdown, {\n headers: {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n ...(article ? getArticleAiHeaders(article, config) : {}),\n },\n })\n}\n\nexport function getArticleMarkdownUrl(\n article: Pick<Article, 'slug' | 'aiCrawl'>,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): string | undefined {\n if (article.aiCrawl !== true) return undefined\n const pathname = `/articles/${article.slug}.md`\n if (!config) return pathname\n return `${config.siteUrl.replace(/\\/$/, '')}${pathname}`\n}\n\nexport function getArticleAiHeaders(\n article: Pick<Article, 'slug' | 'aiCrawl'>,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): Record<string, string> {\n const markdownUrl = getArticleMarkdownUrl(article, config)\n if (markdownUrl) {\n return {\n Link: `<${markdownUrl}>; rel=\"alternate\"; type=\"text/markdown\"`,\n }\n }\n return {\n 'X-Robots-Tag': 'noai, noimageai',\n }\n}\n\nexport async function getAiRobotsTxtRules(): Promise<string> {\n const articles = await getAllArticles()\n const blockedArticles = articles.filter((article) => article.aiCrawl !== true)\n if (blockedArticles.length === 0) return ''\n\n const aiCrawlers = [\n 'GPTBot',\n 'ChatGPT-User',\n 'CCBot',\n 'ClaudeBot',\n 'Claude-User',\n 'PerplexityBot',\n 'Google-Extended',\n ]\n const disallowRules = blockedArticles\n .map((article) => `Disallow: /articles/${article.slug}`)\n .join('\\n')\n\n return aiCrawlers\n .map((crawler) => [`User-agent: ${crawler}`, disallowRules].join('\\n'))\n .join('\\n\\n')\n}\n\nexport async function searchArticles(query: string, config?: ArticlesConfig): Promise<Article[]> {\n if (!query?.trim()) return getAllArticles()\n const articles = await getAllArticles()\n const searchTerm = query.toLowerCase().trim()\n const includeAuthor = config?.showAuthor !== false\n return articles.filter((article) => {\n const matchesTitle = article.title.toLowerCase().includes(searchTerm)\n const matchesExcerpt = article.excerpt.toLowerCase().includes(searchTerm)\n const matchesAuthor = includeAuthor && article.author.toLowerCase().includes(searchTerm)\n const matchesCategory = article.categories.some((cat) => cat.toLowerCase().includes(searchTerm))\n const matchesTags = article.tags?.some((tag) => tag.toLowerCase().includes(searchTerm))\n return (\n matchesTitle || matchesExcerpt || matchesAuthor || matchesCategory || Boolean(matchesTags)\n )\n })\n}\n\nexport function categoryToSlug(category: string): string {\n return category\n .toLowerCase()\n .replaceAll(/\\s+/g, '-')\n .replaceAll(/[^a-z0-9-]/g, '')\n}\n\nexport async function getAllCategories(): Promise<CategoryInfo[]> {\n const articles = await getAllArticles()\n const categoryMap = new Map<string, { count: number; featuredImage: string }>()\n for (const article of articles) {\n for (const cat of article.categories) {\n if (!categoryMap.has(cat)) {\n categoryMap.set(cat, { count: 0, featuredImage: article.featuredImage })\n }\n categoryMap.get(cat)!.count++\n }\n }\n return Array.from(categoryMap.entries())\n .map(([name, { count, featuredImage }]) => ({\n name,\n slug: categoryToSlug(name),\n count,\n featuredImage,\n }))\n .sort((a, b) => b.count - a.count)\n}\n\nexport async function getArticlesByCategory(categorySlug: string): Promise<Article[]> {\n const articles = await getAllArticles()\n return articles.filter((article) =>\n article.categories.some((cat) => categoryToSlug(cat) === categorySlug)\n )\n}\n\nexport { sanitizeImagePath }\n","import type { Element, Root, ElementContent } from 'hast'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSanitize from 'rehype-sanitize'\nimport rehypeSlug from 'rehype-slug'\nimport rehypeStringify from 'rehype-stringify'\nimport { remark } from 'remark'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport remarkParse from 'remark-parse'\nimport remarkRehype from 'remark-rehype'\nimport { Plugin } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport type { TocItem } from './articleTypes'\nimport { reportArticlesError } from './errorReporting'\n\n// Import the sanitizeImagePath function\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') {\n return null\n }\n\n // Remove any null bytes or control characters\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n\n // Check for absolute URLs (http/https)\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) {\n // For external URLs, just return as-is (they're safe)\n return cleanPath\n }\n\n // For relative paths, ensure they don't contain dangerous patterns\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected unsafe markdown image path.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n\n // Only allow alphanumeric characters, hyphens, underscores, dots, and forward slashes\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected markdown image path with invalid characters.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n\n // Construct safe path within articles directory\n if (cleanPath.includes('/')) {\n // Relative path, ensure it's within the article directory\n const normalizedPath = cleanPath.replaceAll('\\\\', '/')\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected markdown image path traversal attempt.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n return `/articles/${articleSlug}/${normalizedPath}`\n } else {\n // Just a filename, construct full path\n return `/articles/${articleSlug}/${cleanPath}`\n }\n}\n\nfunction styleFootnoteLinks(nodes: ElementContent[]): void {\n nodes.forEach((n) => {\n if (n.type !== 'element') return\n const el = n as Element\n if (el.tagName === 'a') {\n const isBackRef =\n el.properties?.['dataFootnoteBackref'] !== undefined ||\n (el.children[0]?.type === 'text' && el.children[0].value === '↩')\n if (isBackRef) {\n el.properties.className = 'text-primary hover:underline ml-1'\n if (typeof el.properties.href === 'string') {\n el.properties.href = el.properties.href.replaceAll('#user-content-fnref-', '#ref-')\n }\n } else {\n el.properties.className = 'text-primary hover:underline break-all'\n }\n }\n if (el.children) styleFootnoteLinks(el.children)\n })\n}\n\nfunction processFootnoteRef(node: Element): void {\n if (\n node.tagName !== 'sup' ||\n node.children?.[0]?.type !== 'element' ||\n (node.children[0] as Element).tagName !== 'a'\n )\n return\n\n const link = node.children[0] as Element\n const href = link.properties?.href\n if (typeof href !== 'string' || !href.startsWith('#user-content-fn-')) return\n\n delete link.properties.target\n delete link.properties.rel\n link.properties.className = 'text-primary hover:underline'\n link.properties.href = href.replaceAll('#user-content-fn-', '#footnote-')\n\n if (typeof node.properties?.id === 'string') {\n link.properties.id = node.properties.id.replaceAll('user-content-fnref-', 'ref-')\n delete node.properties.id\n }\n\n if (link.children?.[0]?.type === 'text') {\n link.children[0].value = `[${link.children[0].value}]`\n }\n}\n\nfunction processFootnotesSection(node: Element): void {\n const cls = node.properties?.className\n const isFootnotes =\n node.tagName === 'section' &&\n (Array.isArray(cls) ? cls.includes('footnotes') : cls === 'footnotes')\n if (!isFootnotes) return\n\n const olCandidate = node.children.find(\n (child) => child.type === 'element' && (child as Element).tagName === 'ol'\n )\n if (olCandidate?.type !== 'element') return\n\n const ol = olCandidate as Element\n ol.properties.className = 'list-decimal ml-6 space-y-2 text-sm text-muted-foreground'\n\n ol.children.forEach((li) => {\n if (li.type !== 'element' || li.tagName !== 'li') return\n const liEl = li as Element\n\n if (liEl.properties) {\n liEl.properties.className = 'pl-2'\n if (typeof liEl.properties.id === 'string') {\n liEl.properties.id = liEl.properties.id.replaceAll('user-content-fn-', 'footnote-')\n }\n }\n\n const pIndex = liEl.children.findIndex(\n (child) => child.type === 'element' && (child as Element).tagName === 'p'\n )\n if (pIndex !== -1) {\n const p = liEl.children[pIndex] as Element\n liEl.children.splice(pIndex, 1, ...p.children)\n }\n\n styleFootnoteLinks(liEl.children)\n })\n\n const hr: Element = {\n type: 'element',\n tagName: 'hr',\n properties: { className: 'my-8 border-border' },\n children: [],\n }\n const h3: Element = {\n type: 'element',\n tagName: 'h3',\n properties: { className: 'text-lg font-semibold mb-4' },\n children: [{ type: 'text', value: 'References' }],\n }\n\n node.children = [hr, h3, ol]\n if (node.properties) node.properties.className = undefined\n}\n\nexport const customRenderer: Plugin<[], Root> = () => {\n return (tree: Root) => {\n // First pass: Apply general styles\n visit(tree, 'element', (node: Element) => {\n if (node.tagName) {\n const props = node.properties || {}\n\n switch (node.tagName) {\n case 'h1':\n props.className = 'text-3xl font-bold text-foreground mt-8 mb-4 scroll-mt-20'\n break\n case 'h2':\n props.className = 'text-2xl font-semibold text-foreground mt-6 mb-3 scroll-mt-20'\n break\n case 'h3':\n props.className = 'text-xl font-semibold text-foreground mt-4 mb-2 scroll-mt-20'\n break\n case 'h4':\n props.className = 'text-lg font-semibold text-foreground mt-3 mb-2 scroll-mt-20'\n break\n case 'p':\n props.className = 'text-muted-foreground leading-relaxed mb-4'\n break\n case 'a': {\n props.className = 'text-primary hover:underline transition-colors duration-200'\n const href = typeof props.href === 'string' ? props.href : ''\n if (!href.startsWith('#')) {\n props.target = '_blank'\n props.rel = 'noopener noreferrer'\n }\n break\n }\n case 'ul':\n props.className = 'list-disc list-inside mb-4 space-y-2 ml-4'\n break\n case 'ol':\n props.className = 'list-decimal list-inside mb-4 space-y-2 ml-4'\n break\n case 'li':\n props.className = 'mb-1'\n break\n case 'blockquote':\n props.className = 'border-l-4 border-primary pl-4 italic my-4 text-muted-foreground'\n break\n case 'code':\n props.className =\n (props.className ? props.className + ' ' : '') +\n 'bg-muted px-1 py-0.5 rounded text-sm font-mono'\n break\n case 'pre':\n props.className = 'bg-muted rounded-lg p-4 overflow-x-auto my-4'\n break\n case 'img':\n props.className = 'rounded-lg my-6 w-full max-w-2xl mx-auto'\n break\n case 'table':\n props.className = 'border-collapse border border-border my-4 w-full'\n break\n case 'th':\n props.className = 'border border-border px-2 py-1 bg-muted font-semibold'\n break\n case 'td':\n props.className = 'border border-border px-2 py-1'\n break\n case 'hr':\n props.className = 'my-8 border-border'\n break\n default:\n break\n }\n\n node.properties = props\n }\n })\n\n // Second pass: Fix footnotes and references\n visit(tree, 'element', (node: Element) => {\n processFootnoteRef(node)\n processFootnotesSection(node)\n })\n }\n}\n\n// Custom rehype plugin to process image URLs\nconst rehypeProcessImages: Plugin<[{ articleSlug?: string }], Root> = (options = {}) => {\n return (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n if (node.tagName === 'img' && node.properties) {\n const src = node.properties.src\n if (src && typeof src === 'string' && options.articleSlug) {\n // Sanitize the image path\n const sanitizedSrc = sanitizeImagePath(src, options.articleSlug)\n if (sanitizedSrc) {\n node.properties.src = sanitizedSrc\n } else {\n // If sanitization fails, use a placeholder\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Using placeholder for unsafe markdown image path.',\n context: { articleSlug: options.articleSlug, path: src },\n })\n node.properties.src = '/placeholder-logo.png'\n }\n }\n }\n })\n }\n}\n\nexport async function markdownToHtml(markdown: string, articleSlug?: string) {\n try {\n // Start building the remark processor\n let processor = remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkGithubBlockquoteAlert)\n .use(remarkRehype)\n .use(customRenderer)\n .use(rehypeSlug)\n // @ts-ignore\n .use(rehypePrism)\n .use(rehypeSanitize, {\n attributes: {\n '*': ['className', 'class', 'id'],\n a: ['href', 'target', 'rel', 'id'],\n img: ['src', 'alt'],\n },\n })\n\n // Add image processing plugin if articleSlug is provided\n if (articleSlug) {\n processor = processor.use(rehypeProcessImages, { articleSlug })\n }\n\n const result = await processor.use(rehypeStringify).process(markdown)\n\n return result.toString()\n } catch (error) {\n reportArticlesError({\n code: 'markdown-conversion-failed',\n message: 'Unable to convert markdown to HTML.',\n error,\n context: { articleSlug },\n })\n // Return the original markdown as fallback\n return markdown\n }\n}\n\nfunction nodeTextValue(c: ElementContent): string {\n return c.type === 'text' ? (c as { value: string }).value : ''\n}\n\nfunction extractHeadingItem(node: Element): TocItem | null {\n const match = /^h([1-6])$/.exec(node.tagName)\n if (!match) return null\n const id = typeof node.properties?.id === 'string' ? node.properties.id : ''\n const text = node.children.map(nodeTextValue).join('')\n if (!id || !text) return null\n return { id, depth: Number.parseInt(match[1], 10), text }\n}\n\nexport async function extractToc(markdown: string): Promise<TocItem[]> {\n const headings: TocItem[] = []\n const collectHeadings: Plugin<[], Root> = () => (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n const item = extractHeadingItem(node)\n if (item) headings.push(item)\n })\n }\n await remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype)\n .use(rehypeSlug)\n .use(collectHeadings)\n .use(rehypeStringify)\n .process(markdown)\n return headings\n}\n","export type ArticlesErrorCode =\n | 'article-directory-read-failed'\n | 'article-load-failed'\n | 'article-markdown-load-failed'\n | 'markdown-conversion-failed'\n | 'unsafe-image-path'\n\nexport type ArticlesErrorContext = Readonly<Record<string, string | number | boolean | undefined>>\n\nexport type ArticlesErrorReport = Readonly<{\n code: ArticlesErrorCode\n message: string\n error?: unknown\n context?: ArticlesErrorContext\n}>\n\nexport type ArticlesErrorHandler = (report: ArticlesErrorReport) => void\n\nfunction defaultArticlesErrorHandler(report: ArticlesErrorReport): void {\n if (process.env.NODE_ENV === 'production') return\n const context = report.context ? ` ${JSON.stringify(report.context)}` : ''\n const detail = report.error instanceof Error ? `: ${report.error.message}` : ''\n console.warn(`[articles:${report.code}] ${report.message}${context}${detail}`)\n}\n\nlet articlesErrorHandler: ArticlesErrorHandler = defaultArticlesErrorHandler\n\nexport function setArticlesErrorHandler(handler?: ArticlesErrorHandler): void {\n articlesErrorHandler = handler ?? defaultArticlesErrorHandler\n}\n\nexport function reportArticlesError(report: ArticlesErrorReport): void {\n articlesErrorHandler(report)\n}\n","import type { Metadata, MetadataRoute } from 'next'\nimport {\n getArticleMetadata,\n getAllArticles,\n getAllCategories,\n getArticlesByCategory,\n getArticleMarkdownUrl,\n getAvailableArticleSlugs,\n} from './server-articles'\nimport { ArticlesConfig } from './articlesConfig'\n\nexport function generateArticleStaticParams(): { slug: string }[] {\n return getAvailableArticleSlugs().map((slug) => ({ slug }))\n}\n\nexport async function generateCategoryStaticParams(): Promise<{ category: string }[]> {\n const categories = await getAllCategories()\n return categories.map((cat) => ({ category: cat.slug }))\n}\n\nfunction resolveImageUrl(featuredImage: string, siteUrl: string): string {\n const base = siteUrl.replace(/\\/$/, '')\n if (featuredImage.startsWith('http://') || featuredImage.startsWith('https://')) {\n return featuredImage\n }\n return `${base}/${featuredImage.replace(/^\\/+/, '')}`\n}\n\nexport async function generateArticleMetadata(\n slug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const article = await getArticleMetadata(slug)\n\n if (!article) {\n return {\n title: 'Article Not Found',\n description: 'The requested article could not be found.',\n }\n }\n\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const articleUrl = `${siteUrl}/articles/${slug}`\n const canonicalUrl = article.canonicalUrl ?? articleUrl\n const imageUrl = article.featuredImage\n ? resolveImageUrl(article.featuredImage, siteUrl)\n : `${siteUrl}/placeholder-logo.png`\n const description = article.excerpt ?? `Read ${article.title} on ${config.siteName}.`\n const showAuthor = config.showAuthor !== false\n const markdownUrl = getArticleMarkdownUrl(article, config)\n\n return {\n title: `${article.title} | ${config.siteName}`,\n description,\n keywords: [...(article.tags ?? []).map((tag) => tag.toLowerCase())].join(', '),\n openGraph: {\n title: article.title,\n description,\n url: articleUrl,\n siteName: config.siteName,\n images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],\n locale: 'en_US',\n type: 'article',\n ...(article.date && { publishedTime: article.date }),\n ...(article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }),\n ...(showAuthor && { authors: [article.author] }),\n tags: article.tags ?? [],\n },\n twitter: {\n card: 'summary_large_image',\n title: article.title,\n description,\n images: [imageUrl],\n },\n alternates: {\n canonical: canonicalUrl,\n ...(markdownUrl && {\n types: {\n 'text/markdown': markdownUrl,\n },\n }),\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n other: {\n ...(showAuthor && { 'article:author': article.author }),\n ...(article.date && {\n 'article:published_time': new Date(article.date).toISOString(),\n }),\n ...(article.lastmod && {\n 'article:modified_time': new Date(article.lastmod).toISOString(),\n }),\n 'article:section': article.category,\n 'article:tag': article.tags?.join(',') ?? '',\n 'linkedin:owner': process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID ?? '',\n },\n }\n}\n\nexport function generateArticlesIndexMetadata(config: ArticlesConfig): Metadata {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const indexUrl = `${siteUrl}/articles`\n const title = `Articles | ${config.siteName}`\n const description =\n config.hero?.description ?? `Expert analysis and insights from ${config.siteName}.`\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n url: indexUrl,\n siteName: config.siteName,\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n },\n alternates: {\n canonical: indexUrl,\n types: {\n 'application/rss+xml': `${siteUrl}/articles/feed.xml`,\n },\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function generateCategoryMetadata(\n categorySlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const articles = await getArticlesByCategory(categorySlug)\n\n if (articles.length === 0) return { title: 'Category Not Found' }\n\n const categoryName = articles[0].category\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const categoryUrl = `${siteUrl}/articles/category/${categorySlug}`\n const raw = config.categoryDescriptions?.[categorySlug]\n const fallback = `Browse ${articles.length} article${articles.length === 1 ? '' : 's'} in the ${categoryName} category.`\n const description = typeof raw === 'string' ? raw : (raw?.short ?? fallback)\n\n const title = `${categoryName} Articles | ${config.siteName}`\n return {\n title,\n description,\n openGraph: {\n title: `${categoryName} Articles`,\n description,\n url: categoryUrl,\n siteName: config.siteName,\n images: [{ url: articles[0].featuredImage }],\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title: `${categoryName} Articles`,\n description,\n },\n alternates: {\n canonical: categoryUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function getArticleSitemapEntries(\n baseUrlOrConfig: string | ArticlesConfig\n): Promise<MetadataRoute.Sitemap> {\n const baseUrl = (\n typeof baseUrlOrConfig === 'string' ? baseUrlOrConfig : baseUrlOrConfig.siteUrl\n ).replace(/\\/$/, '')\n\n try {\n const [articles, categories] = await Promise.all([getAllArticles(), getAllCategories()])\n\n const articleEntries: MetadataRoute.Sitemap = articles.map((article) => {\n const dateStr = article.lastmod ?? article.date\n const lastModified = dateStr ? new Date(dateStr) : undefined\n return {\n url: `${baseUrl}/articles/${article.slug}`,\n lastModified,\n changeFrequency: 'weekly' as const,\n priority: 0.8,\n }\n })\n\n const categoryEntries: MetadataRoute.Sitemap = categories.map((cat) => ({\n url: `${baseUrl}/articles/category/${cat.slug}`,\n lastModified: new Date(),\n changeFrequency: 'weekly' as const,\n priority: 0.7,\n }))\n\n return [...articleEntries, ...categoryEntries]\n } catch {\n return []\n }\n}\n","// MDX files must use JSX prop syntax:\n// Correct: <span style={{ color: '#3b82f6' }}>Text</span>\n// Incorrect: <span style=\"color: #3b82f6\">Text</span>\n// Correct: className=\"...\"\n// Incorrect: class=\"...\"\nimport React from 'react'\nimport type { ComponentType, ImgHTMLAttributes } from 'react'\nimport * as devRuntime from 'react/jsx-dev-runtime'\nimport * as runtime from 'react/jsx-runtime'\nimport { evaluate } from '@mdx-js/mdx'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSlug from 'rehype-slug'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport { customRenderer } from './markdown'\n\ntype MdxContent = ComponentType<{\n components?: Record<string, ComponentType<unknown>>\n}>\n\nfunction makeImgComponent(basePath: string) {\n return function MdxImage({ src, alt, ...props }: ImgHTMLAttributes<HTMLImageElement>) {\n const resolvedSrc =\n src && !src.startsWith('http') && !src.startsWith('/') ? `${basePath}/${src}` : src\n return React.createElement('img', { src: resolvedSrc, alt, ...props })\n }\n}\n\nexport async function renderMdxSource(source: string, basePath?: string) {\n const isDevelopment = process.env.NODE_ENV === 'development'\n\n const mdxModule = await evaluate(source, {\n ...(isDevelopment ? devRuntime : runtime),\n development: isDevelopment,\n remarkPlugins: [remarkGfm, remarkGithubBlockquoteAlert],\n rehypePlugins: [\n customRenderer,\n rehypeSlug,\n // @ts-ignore\n rehypePrism,\n ],\n })\n\n const Content = mdxModule.default as MdxContent\n const components = basePath ? { img: makeImgComponent(basePath) } : undefined\n return <Content components={components as Record<string, ComponentType<unknown>>} />\n}\n","import { renderMdxSource } from './renderMdx'\nimport type { Article } from './articleTypes'\n\ntype ArticleContentProps = Readonly<{ article: Article; className?: string }>\n\nexport async function ArticleContent({ article, className }: ArticleContentProps) {\n if (article.contentType === 'mdx' && article.mdxSource) {\n const content = await renderMdxSource(article.mdxSource, `/articles/${article.slug}`)\n return <div className={className}>{content}</div>\n }\n return (\n <div className={className} dangerouslySetInnerHTML={{ __html: article.htmlContent || '' }} />\n )\n}\n","import type { TocItem } from './articleTypes'\n\ntype ArticleTOCProps = Readonly<{ toc: TocItem[]; className?: string }>\n\nexport function ArticleTOC({ toc, className }: ArticleTOCProps) {\n if (!toc.length) return null\n return (\n <nav\n aria-label=\"Table of contents\"\n className={`mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className ?? ''}`}\n >\n <p className=\"mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground\">\n On this page\n </p>\n <ul className=\"space-y-1 text-sm\">\n {toc.map((item) => (\n <li key={item.id} style={{ paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }}>\n <a\n href={`#${item.id}`}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n >\n {item.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n )\n}\n"],"mappings":"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","names":["sanitizeImagePath","rehypePrism","rehypeSlug","remarkGfm","remarkGithubBlockquoteAlert","remarkGfm","remarkGithubBlockquoteAlert","rehypeSlug","rehypePrism","jsx","jsx"]}
1
+ {"version":3,"sources":["../src/server-articles.ts","../src/markdown.ts","../src/errorReporting.ts","../src/seoUtils.ts","../src/renderMdx.tsx","../src/ArticleContent.tsx","../src/ArticleTOC.tsx"],"sourcesContent":["import { cache } from 'react'\nimport matter from 'gray-matter'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport readingTime from 'reading-time'\nimport { markdownToHtml, extractToc } from './markdown'\nimport type { Article, CategoryInfo, FaqItem, HowToStep } from './articleTypes'\nimport type { ArticlesConfig } from './articlesConfig'\nimport { reportArticlesError } from './errorReporting'\n\nconst articlesDirectory = path.join(/* turbopackIgnore: true */ process.cwd(), 'public/articles')\n\nfunction getReadingTime(content: string): string {\n return readingTime(content).text\n}\n\nfunction findArticleImage(slug: string): string | null {\n try {\n const articleDir = path.join(articlesDirectory, slug)\n const entries: (fs.Dirent | string)[] = fs.readdirSync(articleDir, { withFileTypes: true })\n const names = entries\n .map((entry) => {\n if (typeof entry === 'string') return entry\n if (entry && typeof entry.name === 'string') return entry.name\n return null\n })\n .filter((name): name is string => Boolean(name))\n const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp']\n const imageFileName = names.find((name) =>\n imageExtensions.some((ext) => name.toLowerCase().endsWith(ext))\n )\n return imageFileName ? sanitizeImagePath(imageFileName, slug) : null\n } catch {\n return null\n }\n}\n\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') return null\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) return cleanPath\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected unsafe article image path.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected article image path with invalid characters.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n if (cleanPath.includes('/')) {\n const normalizedPath = path.normalize(cleanPath)\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected article image path traversal attempt.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n return `/articles/${articleSlug}/${cleanPath}`\n }\n return `/articles/${articleSlug}/${cleanPath}`\n}\n\nfunction findArticleFile(slug: string): { filePath: string; contentType: 'md' | 'mdx' } | null {\n const mdPath = path.join(articlesDirectory, slug, 'article.md')\n const mdxPath = path.join(articlesDirectory, slug, 'article.mdx')\n if (fs.existsSync(mdPath)) return { filePath: mdPath, contentType: 'md' }\n if (fs.existsSync(mdxPath)) return { filePath: mdxPath, contentType: 'mdx' }\n return null\n}\n\nexport function getAvailableArticleSlugs(): string[] {\n try {\n if (!fs.existsSync(articlesDirectory)) return []\n return walkArticleDir(articlesDirectory, '')\n } catch (error) {\n reportArticlesError({\n code: 'article-directory-read-failed',\n message: 'Unable to read articles directory.',\n error,\n context: { directory: articlesDirectory },\n })\n return []\n }\n}\n\nfunction walkArticleDir(dir: string, baseSlug: string): string[] {\n const slugs: string[] = []\n try {\n const items = fs.readdirSync(dir, { withFileTypes: true })\n for (const item of items) {\n if (!item.isDirectory()) continue\n const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name\n if (findArticleFile(slug) !== null) {\n slugs.push(slug)\n }\n slugs.push(...walkArticleDir(path.join(dir, item.name), slug))\n }\n } catch {\n // ignore unreadable directories\n }\n return slugs\n}\n\nfunction parseDateField(rawDate: unknown): string | undefined {\n if (!rawDate) return undefined\n try {\n const parsed = new Date(rawDate as string)\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split('T')[0]\n } catch {\n // ignore invalid dates\n }\n return undefined\n}\n\nfunction resolveFeaturedImage(rawImage: unknown, slug: string): string {\n const img = typeof rawImage === 'string' ? rawImage : ''\n if (img && !img.startsWith('http')) return sanitizeImagePath(img, slug) || '/placeholder-logo.png'\n if (!img) return findArticleImage(slug) || '/placeholder-logo.png'\n return img\n}\n\nfunction parseFaqItems(raw: unknown): FaqItem[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const items = raw.filter(\n (item): item is FaqItem =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as FaqItem).question === 'string' &&\n typeof (item as FaqItem).answer === 'string'\n )\n return items.length ? items : undefined\n}\n\nfunction parseHowToSteps(raw: unknown): HowToStep[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const steps = raw.filter(\n (item): item is HowToStep =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as HowToStep).name === 'string' &&\n typeof (item as HowToStep).text === 'string'\n )\n return steps.length ? steps : undefined\n}\n\nasync function getArticleSummary(slug: string): Promise<Article | null> {\n try {\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { data, content: markdownContent } = matter(fileContent)\n const readTime = getReadingTime(markdownContent)\n const allTags: string[] = Array.isArray(data.tags)\n ? data.tags.filter((t: unknown) => typeof t === 'string' && String(t).trim())\n : []\n const categories: string[] =\n allTags.length > 0 ? allTags.map((t: string) => t.replaceAll('-', ' ').trim()) : ['Campaigns']\n return {\n slug,\n title: data.title || slug.replaceAll('-', ' '),\n excerpt: data.excerpt || '',\n date: parseDateField(data.date),\n lastmod: parseDateField(data.lastmod),\n author: data.author || 'Andrew Blase',\n category: categories[0],\n categories,\n readTime,\n featuredImage: resolveFeaturedImage(data.featuredImage, slug),\n tags: data.tags || [],\n contentType: found.contentType,\n draft: data.draft === true,\n faq: parseFaqItems(data.faq),\n howTo: parseHowToSteps(data.howTo),\n canonicalUrl: typeof data.canonicalUrl === 'string' ? data.canonicalUrl : undefined,\n articleType: typeof data.articleType === 'string' ? data.articleType : undefined,\n series: typeof data.series === 'string' ? data.series : undefined,\n aiCrawl: data.aiCrawl === true,\n }\n } catch (error) {\n reportArticlesError({\n code: 'article-load-failed',\n message: 'Unable to load article summary.',\n error,\n context: { slug },\n })\n return null\n }\n}\n\nexport const getArticleMetadata = cache(\n async (slug: string, config?: ArticlesConfig): Promise<Article | null> => {\n try {\n const summary = await getArticleSummary(slug)\n if (!summary) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n const toc = await extractToc(markdownContent)\n let htmlContent: string | undefined\n let mdxSource: string | undefined\n if (found.contentType === 'mdx') {\n mdxSource = markdownContent\n } else {\n htmlContent = await markdownToHtml(markdownContent, slug, config)\n }\n return { ...summary, content: markdownContent, htmlContent, mdxSource, toc }\n } catch (error) {\n reportArticlesError({\n code: 'article-load-failed',\n message: 'Unable to load article metadata.',\n error,\n context: { slug },\n })\n return null\n }\n }\n)\n\nexport const getAllArticles = cache(async (): Promise<Article[]> => {\n const slugs = getAvailableArticleSlugs()\n const articles = await Promise.all(slugs.map((slug) => getArticleSummary(slug)))\n const currentDate = new Date().toISOString().split('T')[0]\n return articles\n .filter((article): article is Article => article !== null)\n .filter((article) => !article.date || article.date <= currentDate)\n .filter((article) => !(article.draft && process.env.NODE_ENV === 'production'))\n .sort((a, b) => {\n if (!a.date && !b.date) return 0\n if (!a.date) return 1\n if (!b.date) return -1\n return new Date(b.date).getTime() - new Date(a.date).getTime()\n })\n})\n\nexport async function getAdjacentArticles(\n currentSlug: string\n): Promise<{ previous: Article | null; next: Article | null }> {\n const allArticles = await getAllArticles()\n const currentIndex = allArticles.findIndex((article) => article.slug === currentSlug)\n if (currentIndex === -1) return { previous: null, next: null }\n const previous = currentIndex < allArticles.length - 1 ? allArticles[currentIndex + 1] : null\n const next = currentIndex > 0 ? allArticles[currentIndex - 1] : null\n return { previous, next }\n}\n\nexport async function getArticleMarkdown(slug: string): Promise<string | null> {\n try {\n const summary = await getArticleSummary(slug)\n if (!summary?.aiCrawl) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n return markdownContent\n } catch (error) {\n reportArticlesError({\n code: 'article-markdown-load-failed',\n message: 'Unable to load article markdown.',\n error,\n context: { slug },\n })\n return null\n }\n}\n\nexport async function getArticleMarkdownResponse(\n slug: string,\n config: ArticlesConfig\n): Promise<Response> {\n const markdown = await getArticleMarkdown(slug)\n if (markdown === null) return new Response('Not Found', { status: 404 })\n const article = await getArticleMetadata(slug)\n return new Response(markdown, {\n headers: {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n ...(article ? getArticleAiHeaders(article, config) : {}),\n },\n })\n}\n\nexport function getArticleMarkdownUrl(\n article: Pick<Article, 'slug' | 'aiCrawl'>,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): string | undefined {\n if (article.aiCrawl !== true) return undefined\n const pathname = `/articles/${article.slug}.md`\n if (!config) return pathname\n return `${config.siteUrl.replace(/\\/$/, '')}${pathname}`\n}\n\nexport function getArticleAiHeaders(\n article: Pick<Article, 'slug' | 'aiCrawl'>,\n config?: Pick<ArticlesConfig, 'siteUrl'>\n): Record<string, string> {\n const markdownUrl = getArticleMarkdownUrl(article, config)\n if (markdownUrl) {\n return {\n Link: `<${markdownUrl}>; rel=\"alternate\"; type=\"text/markdown\"`,\n }\n }\n return {\n 'X-Robots-Tag': 'noai, noimageai',\n }\n}\n\nexport async function getAiRobotsTxtRules(): Promise<string> {\n const articles = await getAllArticles()\n const blockedArticles = articles.filter((article) => article.aiCrawl !== true)\n if (blockedArticles.length === 0) return ''\n\n const aiCrawlers = [\n 'GPTBot',\n 'ChatGPT-User',\n 'CCBot',\n 'ClaudeBot',\n 'Claude-User',\n 'PerplexityBot',\n 'Google-Extended',\n ]\n const disallowRules = blockedArticles\n .map((article) => `Disallow: /articles/${article.slug}`)\n .join('\\n')\n\n return aiCrawlers\n .map((crawler) => [`User-agent: ${crawler}`, disallowRules].join('\\n'))\n .join('\\n\\n')\n}\n\nexport async function searchArticles(query: string, config?: ArticlesConfig): Promise<Article[]> {\n if (!query?.trim()) return getAllArticles()\n const articles = await getAllArticles()\n const searchTerm = query.toLowerCase().trim()\n const includeAuthor = config?.showAuthor !== false\n return articles.filter((article) => {\n const matchesTitle = article.title.toLowerCase().includes(searchTerm)\n const matchesExcerpt = article.excerpt.toLowerCase().includes(searchTerm)\n const matchesAuthor = includeAuthor && article.author.toLowerCase().includes(searchTerm)\n const matchesCategory = article.categories.some((cat) => cat.toLowerCase().includes(searchTerm))\n const matchesTags = article.tags?.some((tag) => tag.toLowerCase().includes(searchTerm))\n return (\n matchesTitle || matchesExcerpt || matchesAuthor || matchesCategory || Boolean(matchesTags)\n )\n })\n}\n\nexport function categoryToSlug(category: string): string {\n return category\n .toLowerCase()\n .replaceAll(/\\s+/g, '-')\n .replaceAll(/[^a-z0-9-]/g, '')\n}\n\nexport async function getAllCategories(): Promise<CategoryInfo[]> {\n const articles = await getAllArticles()\n const categoryMap = new Map<string, { count: number; featuredImage: string }>()\n for (const article of articles) {\n for (const cat of article.categories) {\n if (!categoryMap.has(cat)) {\n categoryMap.set(cat, { count: 0, featuredImage: article.featuredImage })\n }\n categoryMap.get(cat)!.count++\n }\n }\n return Array.from(categoryMap.entries())\n .map(([name, { count, featuredImage }]) => ({\n name,\n slug: categoryToSlug(name),\n count,\n featuredImage,\n }))\n .sort((a, b) => b.count - a.count)\n}\n\nexport async function getArticlesByCategory(categorySlug: string): Promise<Article[]> {\n const articles = await getAllArticles()\n return articles.filter((article) =>\n article.categories.some((cat) => categoryToSlug(cat) === categorySlug)\n )\n}\n\nexport { sanitizeImagePath }\n","import type { Element, Root, ElementContent } from 'hast'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSanitize from 'rehype-sanitize'\nimport rehypeSlug from 'rehype-slug'\nimport rehypeStringify from 'rehype-stringify'\nimport { remark } from 'remark'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport remarkParse from 'remark-parse'\nimport remarkRehype from 'remark-rehype'\nimport { Plugin } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport type { TocItem } from './articleTypes'\nimport type { ArticlesConfig, LinkTargetStrategy } from './articlesConfig'\nimport { reportArticlesError } from './errorReporting'\n\ntype LinkTargetOptions = Readonly<{\n strategy?: LinkTargetStrategy\n siteUrl?: string\n}>\n\nconst DEFAULT_LINK_TARGET_STRATEGY: LinkTargetStrategy = 'external-new-tab'\n\nfunction isNonBrowserNavigationLink(href: string): boolean {\n return (\n /^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(href) &&\n !href.startsWith('http://') &&\n !href.startsWith('https://')\n )\n}\n\nfunction getOrigin(url: string | undefined): string | null {\n if (!url) return null\n try {\n return new URL(url).origin\n } catch {\n return null\n }\n}\n\nfunction isExternalHttpLink(href: string, siteUrl?: string): boolean {\n if (!href.startsWith('http://') && !href.startsWith('https://')) return false\n const siteOrigin = getOrigin(siteUrl)\n if (!siteOrigin) return true\n return getOrigin(href) !== siteOrigin\n}\n\nfunction shouldOpenInNewTab(href: string, options: LinkTargetOptions = {}): boolean {\n if (!href || href.startsWith('#') || isNonBrowserNavigationLink(href)) return false\n\n const strategy = options.strategy ?? DEFAULT_LINK_TARGET_STRATEGY\n if (strategy === 'same-tab') return false\n if (strategy === 'all-new-tab') return true\n return isExternalHttpLink(href, options.siteUrl)\n}\n\nfunction applyLinkTarget(props: Record<string, unknown>, options?: LinkTargetOptions): void {\n const href = typeof props.href === 'string' ? props.href : ''\n if (shouldOpenInNewTab(href, options)) {\n props.target = '_blank'\n props.rel = 'noopener noreferrer'\n return\n }\n delete props.target\n delete props.rel\n}\n\nfunction getLinkTargetOptions(config?: ArticlesConfig): LinkTargetOptions {\n return {\n strategy: config?.linkTargetStrategy,\n siteUrl: config?.siteUrl,\n }\n}\n\n// Import the sanitizeImagePath function\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') {\n return null\n }\n\n // Remove any null bytes or control characters\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n\n // Check for absolute URLs (http/https)\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) {\n // For external URLs, just return as-is (they're safe)\n return cleanPath\n }\n\n // For relative paths, ensure they don't contain dangerous patterns\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected unsafe markdown image path.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n\n // Only allow alphanumeric characters, hyphens, underscores, dots, and forward slashes\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected markdown image path with invalid characters.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n\n // Construct safe path within articles directory\n if (cleanPath.includes('/')) {\n // Relative path, ensure it's within the article directory\n const normalizedPath = cleanPath.replaceAll('\\\\', '/')\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Rejected markdown image path traversal attempt.',\n context: { articleSlug, path: cleanPath },\n })\n return null\n }\n return `/articles/${articleSlug}/${normalizedPath}`\n } else {\n // Just a filename, construct full path\n return `/articles/${articleSlug}/${cleanPath}`\n }\n}\n\nfunction styleFootnoteLinks(nodes: ElementContent[]): void {\n nodes.forEach((n) => {\n if (n.type !== 'element') return\n const el = n as Element\n if (el.tagName === 'a') {\n const isBackRef =\n el.properties?.['dataFootnoteBackref'] !== undefined ||\n (el.children[0]?.type === 'text' && el.children[0].value === '↩')\n if (isBackRef) {\n el.properties.className = 'text-primary hover:underline ml-1'\n if (typeof el.properties.href === 'string') {\n el.properties.href = el.properties.href.replaceAll('#user-content-fnref-', '#ref-')\n }\n } else {\n el.properties.className = 'text-primary hover:underline break-all'\n }\n }\n if (el.children) styleFootnoteLinks(el.children)\n })\n}\n\nfunction processFootnoteRef(node: Element): void {\n if (\n node.tagName !== 'sup' ||\n node.children?.[0]?.type !== 'element' ||\n (node.children[0] as Element).tagName !== 'a'\n )\n return\n\n const link = node.children[0] as Element\n const href = link.properties?.href\n if (typeof href !== 'string' || !href.startsWith('#user-content-fn-')) return\n\n delete link.properties.target\n delete link.properties.rel\n link.properties.className = 'text-primary hover:underline'\n link.properties.href = href.replaceAll('#user-content-fn-', '#footnote-')\n\n if (typeof node.properties?.id === 'string') {\n link.properties.id = node.properties.id.replaceAll('user-content-fnref-', 'ref-')\n delete node.properties.id\n }\n\n if (link.children?.[0]?.type === 'text') {\n link.children[0].value = `[${link.children[0].value}]`\n }\n}\n\nfunction processFootnotesSection(node: Element): void {\n const cls = node.properties?.className\n const isFootnotes =\n node.tagName === 'section' &&\n (Array.isArray(cls) ? cls.includes('footnotes') : cls === 'footnotes')\n if (!isFootnotes) return\n\n const olCandidate = node.children.find(\n (child) => child.type === 'element' && (child as Element).tagName === 'ol'\n )\n if (olCandidate?.type !== 'element') return\n\n const ol = olCandidate as Element\n ol.properties.className = 'list-decimal ml-6 space-y-2 text-sm text-muted-foreground'\n\n ol.children.forEach((li) => {\n if (li.type !== 'element' || li.tagName !== 'li') return\n const liEl = li as Element\n\n if (liEl.properties) {\n liEl.properties.className = 'pl-2'\n if (typeof liEl.properties.id === 'string') {\n liEl.properties.id = liEl.properties.id.replaceAll('user-content-fn-', 'footnote-')\n }\n }\n\n const pIndex = liEl.children.findIndex(\n (child) => child.type === 'element' && (child as Element).tagName === 'p'\n )\n if (pIndex !== -1) {\n const p = liEl.children[pIndex] as Element\n liEl.children.splice(pIndex, 1, ...p.children)\n }\n\n styleFootnoteLinks(liEl.children)\n })\n\n const hr: Element = {\n type: 'element',\n tagName: 'hr',\n properties: { className: 'my-8 border-border' },\n children: [],\n }\n const h3: Element = {\n type: 'element',\n tagName: 'h3',\n properties: { className: 'text-lg font-semibold mb-4' },\n children: [{ type: 'text', value: 'References' }],\n }\n\n node.children = [hr, h3, ol]\n if (node.properties) node.properties.className = undefined\n}\n\nexport const customRenderer: Plugin<[LinkTargetOptions?], Root> = (linkTargetOptions = {}) => {\n return (tree: Root) => {\n // First pass: Apply general styles\n visit(tree, 'element', (node: Element) => {\n if (node.tagName) {\n const props = node.properties || {}\n\n switch (node.tagName) {\n case 'h1':\n props.className = 'text-3xl font-bold text-foreground mt-8 mb-4 scroll-mt-20'\n break\n case 'h2':\n props.className = 'text-2xl font-semibold text-foreground mt-6 mb-3 scroll-mt-20'\n break\n case 'h3':\n props.className = 'text-xl font-semibold text-foreground mt-4 mb-2 scroll-mt-20'\n break\n case 'h4':\n props.className = 'text-lg font-semibold text-foreground mt-3 mb-2 scroll-mt-20'\n break\n case 'p':\n props.className = 'text-muted-foreground leading-relaxed mb-4'\n break\n case 'a': {\n props.className = 'text-primary hover:underline transition-colors duration-200'\n applyLinkTarget(props, linkTargetOptions)\n break\n }\n case 'ul':\n props.className = 'list-disc list-inside mb-4 space-y-2 ml-4'\n break\n case 'ol':\n props.className = 'list-decimal list-inside mb-4 space-y-2 ml-4'\n break\n case 'li':\n props.className = 'mb-1'\n break\n case 'blockquote':\n props.className = 'border-l-4 border-primary pl-4 italic my-4 text-muted-foreground'\n break\n case 'code':\n props.className =\n (props.className ? props.className + ' ' : '') +\n 'bg-muted px-1 py-0.5 rounded text-sm font-mono'\n break\n case 'pre':\n props.className = 'bg-muted rounded-lg p-4 overflow-x-auto my-4'\n break\n case 'img':\n props.className = 'rounded-lg my-6 w-full max-w-2xl mx-auto'\n break\n case 'table':\n props.className = 'border-collapse border border-border my-4 w-full'\n break\n case 'th':\n props.className = 'border border-border px-2 py-1 bg-muted font-semibold'\n break\n case 'td':\n props.className = 'border border-border px-2 py-1'\n break\n case 'hr':\n props.className = 'my-8 border-border'\n break\n default:\n break\n }\n\n node.properties = props\n }\n })\n\n // Second pass: Fix footnotes and references\n visit(tree, 'element', (node: Element) => {\n processFootnoteRef(node)\n processFootnotesSection(node)\n })\n }\n}\n\n// Custom rehype plugin to process image URLs\nconst rehypeProcessImages: Plugin<[{ articleSlug?: string }], Root> = (options = {}) => {\n return (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n if (node.tagName === 'img' && node.properties) {\n const src = node.properties.src\n if (src && typeof src === 'string' && options.articleSlug) {\n // Sanitize the image path\n const sanitizedSrc = sanitizeImagePath(src, options.articleSlug)\n if (sanitizedSrc) {\n node.properties.src = sanitizedSrc\n } else {\n // If sanitization fails, use a placeholder\n reportArticlesError({\n code: 'unsafe-image-path',\n message: 'Using placeholder for unsafe markdown image path.',\n context: { articleSlug: options.articleSlug, path: src },\n })\n node.properties.src = '/placeholder-logo.png'\n }\n }\n }\n })\n }\n}\n\nexport async function markdownToHtml(\n markdown: string,\n articleSlug?: string,\n config?: ArticlesConfig\n) {\n try {\n // Start building the remark processor\n let processor = remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkGithubBlockquoteAlert)\n .use(remarkRehype)\n .use(customRenderer, getLinkTargetOptions(config))\n .use(rehypeSlug)\n // @ts-ignore\n .use(rehypePrism)\n .use(rehypeSanitize, {\n attributes: {\n '*': ['className', 'class', 'id'],\n a: ['href', 'target', 'rel', 'id'],\n img: ['src', 'alt'],\n },\n })\n\n // Add image processing plugin if articleSlug is provided\n if (articleSlug) {\n processor = processor.use(rehypeProcessImages, { articleSlug })\n }\n\n const result = await processor.use(rehypeStringify).process(markdown)\n\n return result.toString()\n } catch (error) {\n reportArticlesError({\n code: 'markdown-conversion-failed',\n message: 'Unable to convert markdown to HTML.',\n error,\n context: { articleSlug },\n })\n // Return the original markdown as fallback\n return markdown\n }\n}\n\nfunction nodeTextValue(c: ElementContent): string {\n return c.type === 'text' ? (c as { value: string }).value : ''\n}\n\nfunction extractHeadingItem(node: Element): TocItem | null {\n const match = /^h([1-6])$/.exec(node.tagName)\n if (!match) return null\n const id = typeof node.properties?.id === 'string' ? node.properties.id : ''\n const text = node.children.map(nodeTextValue).join('')\n if (!id || !text) return null\n return { id, depth: Number.parseInt(match[1], 10), text }\n}\n\nexport async function extractToc(markdown: string): Promise<TocItem[]> {\n const headings: TocItem[] = []\n const collectHeadings: Plugin<[], Root> = () => (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n const item = extractHeadingItem(node)\n if (item) headings.push(item)\n })\n }\n await remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype)\n .use(rehypeSlug)\n .use(collectHeadings)\n .use(rehypeStringify)\n .process(markdown)\n return headings\n}\n","export type ArticlesErrorCode =\n | 'article-directory-read-failed'\n | 'article-load-failed'\n | 'article-markdown-load-failed'\n | 'markdown-conversion-failed'\n | 'unsafe-image-path'\n\nexport type ArticlesErrorContext = Readonly<Record<string, string | number | boolean | undefined>>\n\nexport type ArticlesErrorReport = Readonly<{\n code: ArticlesErrorCode\n message: string\n error?: unknown\n context?: ArticlesErrorContext\n}>\n\nexport type ArticlesErrorHandler = (report: ArticlesErrorReport) => void\n\nfunction defaultArticlesErrorHandler(report: ArticlesErrorReport): void {\n if (process.env.NODE_ENV === 'production') return\n const context = report.context ? ` ${JSON.stringify(report.context)}` : ''\n const detail = report.error instanceof Error ? `: ${report.error.message}` : ''\n console.warn(`[articles:${report.code}] ${report.message}${context}${detail}`)\n}\n\nlet articlesErrorHandler: ArticlesErrorHandler = defaultArticlesErrorHandler\n\nexport function setArticlesErrorHandler(handler?: ArticlesErrorHandler): void {\n articlesErrorHandler = handler ?? defaultArticlesErrorHandler\n}\n\nexport function reportArticlesError(report: ArticlesErrorReport): void {\n articlesErrorHandler(report)\n}\n","import type { Metadata, MetadataRoute } from 'next'\nimport {\n getArticleMetadata,\n getAllArticles,\n getAllCategories,\n getArticlesByCategory,\n getArticleMarkdownUrl,\n getAvailableArticleSlugs,\n} from './server-articles'\nimport { ArticlesConfig } from './articlesConfig'\nimport type { Article } from './articleTypes'\n\nfunction escapeXml(str: string): string {\n return str\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;')\n}\n\nexport function generateRssFeed(articles: Article[], config: ArticlesConfig): string {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const showAuthor = config.showAuthor !== false\n\n const items = articles\n .map((article) => {\n const url = `${siteUrl}/articles/${article.slug}`\n const pubDate = article.date ? new Date(article.date).toUTCString() : ''\n const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : ''\n\n return [\n ' <item>',\n ` <title><![CDATA[${article.title}]]></title>`,\n ` <link>${url}</link>`,\n ` <guid isPermaLink=\"true\">${url}</guid>`,\n pubDate ? ` <pubDate>${pubDate}</pubDate>` : '',\n article.excerpt ? ` <description><![CDATA[${article.excerpt}]]></description>` : '',\n showAuthor && article.author ? ` <author>${escapeXml(article.author)}</author>` : '',\n article.category ? ` <category><![CDATA[${article.category}]]></category>` : '',\n imageUrl\n ? ` <media:content url=\"${imageUrl}\" medium=\"image\" width=\"1200\" height=\"630\"/>`\n : '',\n ' </item>',\n ]\n .filter(Boolean)\n .join('\\n')\n })\n .join('\\n')\n\n const description = config.description ?? `${config.siteName} articles`\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:media=\"http://search.yahoo.com/mrss/\">\n <channel>\n <title><![CDATA[${config.siteName}]]></title>\n <link>${siteUrl}/articles</link>\n <description><![CDATA[${description}]]></description>\n <language>en</language>\n <atom:link href=\"${siteUrl}/articles/feed.xml\" rel=\"self\" type=\"application/rss+xml\" />\n${items}\n </channel>\n</rss>`\n}\n\nexport function generateArticleStaticParams(): { slug: string }[] {\n return getAvailableArticleSlugs().map((slug) => ({ slug }))\n}\n\nexport async function generateCategoryStaticParams(): Promise<{ category: string }[]> {\n const categories = await getAllCategories()\n return categories.map((cat) => ({ category: cat.slug }))\n}\n\nfunction resolveImageUrl(featuredImage: string, siteUrl: string): string {\n const base = siteUrl.replace(/\\/$/, '')\n if (featuredImage.startsWith('http://') || featuredImage.startsWith('https://')) {\n return featuredImage\n }\n return `${base}/${featuredImage.replace(/^\\/+/, '')}`\n}\n\nexport async function generateArticleMetadata(\n slug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const article = await getArticleMetadata(slug)\n\n if (!article) {\n return {\n title: 'Article Not Found',\n description: 'The requested article could not be found.',\n }\n }\n\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const articleUrl = `${siteUrl}/articles/${slug}`\n const canonicalUrl = article.canonicalUrl ?? articleUrl\n const imageUrl = article.featuredImage\n ? resolveImageUrl(article.featuredImage, siteUrl)\n : `${siteUrl}/placeholder-logo.png`\n const description = article.excerpt ?? `Read ${article.title} on ${config.siteName}.`\n const showAuthor = config.showAuthor !== false\n const markdownUrl = getArticleMarkdownUrl(article, config)\n\n return {\n title: `${article.title} | ${config.siteName}`,\n description,\n keywords: [...(article.tags ?? []).map((tag) => tag.toLowerCase())].join(', '),\n openGraph: {\n title: article.title,\n description,\n url: articleUrl,\n siteName: config.siteName,\n images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],\n locale: 'en_US',\n type: 'article',\n ...(article.date && { publishedTime: article.date }),\n ...(article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }),\n ...(showAuthor && { authors: [article.author] }),\n tags: article.tags ?? [],\n },\n twitter: {\n card: 'summary_large_image',\n title: article.title,\n description,\n images: [imageUrl],\n },\n alternates: {\n canonical: canonicalUrl,\n ...(markdownUrl && {\n types: {\n 'text/markdown': markdownUrl,\n },\n }),\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n other: {\n ...(showAuthor && { 'article:author': article.author }),\n ...(article.date && {\n 'article:published_time': new Date(article.date).toISOString(),\n }),\n ...(article.lastmod && {\n 'article:modified_time': new Date(article.lastmod).toISOString(),\n }),\n 'article:section': article.category,\n 'article:tag': article.tags?.join(',') ?? '',\n 'linkedin:owner': process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID ?? '',\n },\n }\n}\n\nexport function generateArticlesIndexMetadata(config: ArticlesConfig): Metadata {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const indexUrl = `${siteUrl}/articles`\n const title = `Articles | ${config.siteName}`\n const description =\n config.hero?.description ?? `Expert analysis and insights from ${config.siteName}.`\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n url: indexUrl,\n siteName: config.siteName,\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n },\n alternates: {\n canonical: indexUrl,\n types: {\n 'application/rss+xml': `${siteUrl}/articles/feed.xml`,\n },\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function generateCategoryMetadata(\n categorySlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const articles = await getArticlesByCategory(categorySlug)\n\n if (articles.length === 0) return { title: 'Category Not Found' }\n\n const categoryName = articles[0].category\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const categoryUrl = `${siteUrl}/articles/category/${categorySlug}`\n const raw = config.categoryDescriptions?.[categorySlug]\n const fallback = `Browse ${articles.length} article${articles.length === 1 ? '' : 's'} in the ${categoryName} category.`\n const description = typeof raw === 'string' ? raw : (raw?.short ?? fallback)\n\n const title = `${categoryName} Articles | ${config.siteName}`\n return {\n title,\n description,\n openGraph: {\n title: `${categoryName} Articles`,\n description,\n url: categoryUrl,\n siteName: config.siteName,\n images: [{ url: articles[0].featuredImage }],\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title: `${categoryName} Articles`,\n description,\n },\n alternates: {\n canonical: categoryUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function getArticleSitemapEntries(\n baseUrlOrConfig: string | ArticlesConfig\n): Promise<MetadataRoute.Sitemap> {\n const baseUrl = (\n typeof baseUrlOrConfig === 'string' ? baseUrlOrConfig : baseUrlOrConfig.siteUrl\n ).replace(/\\/$/, '')\n\n try {\n const [articles, categories] = await Promise.all([getAllArticles(), getAllCategories()])\n\n const articleEntries: MetadataRoute.Sitemap = articles.map((article) => {\n const dateStr = article.lastmod ?? article.date\n const lastModified = dateStr ? new Date(dateStr) : undefined\n return {\n url: `${baseUrl}/articles/${article.slug}`,\n lastModified,\n changeFrequency: 'weekly' as const,\n priority: 0.8,\n }\n })\n\n const categoryEntries: MetadataRoute.Sitemap = categories.map((cat) => ({\n url: `${baseUrl}/articles/category/${cat.slug}`,\n lastModified: new Date(),\n changeFrequency: 'weekly' as const,\n priority: 0.7,\n }))\n\n return [...articleEntries, ...categoryEntries]\n } catch {\n return []\n }\n}\n","// MDX files must use JSX prop syntax:\n// Correct: <span style={{ color: '#3b82f6' }}>Text</span>\n// Incorrect: <span style=\"color: #3b82f6\">Text</span>\n// Correct: className=\"...\"\n// Incorrect: class=\"...\"\nimport React from 'react'\nimport type { ComponentType, ImgHTMLAttributes } from 'react'\nimport * as devRuntime from 'react/jsx-dev-runtime'\nimport * as runtime from 'react/jsx-runtime'\nimport { evaluate } from '@mdx-js/mdx'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSlug from 'rehype-slug'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport { customRenderer } from './markdown'\nimport type { ArticlesConfig } from './articlesConfig'\n\ntype MdxContent = ComponentType<{\n components?: Record<string, ComponentType<unknown>>\n}>\n\nfunction makeImgComponent(basePath: string) {\n return function MdxImage({ src, alt, ...props }: ImgHTMLAttributes<HTMLImageElement>) {\n const resolvedSrc =\n src && !src.startsWith('http') && !src.startsWith('/') ? `${basePath}/${src}` : src\n return React.createElement('img', { src: resolvedSrc, alt, ...props })\n }\n}\n\nexport async function renderMdxSource(source: string, basePath?: string, config?: ArticlesConfig) {\n const isDevelopment = process.env.NODE_ENV === 'development'\n\n const mdxModule = await evaluate(source, {\n ...(isDevelopment ? devRuntime : runtime),\n development: isDevelopment,\n remarkPlugins: [remarkGfm, remarkGithubBlockquoteAlert],\n rehypePlugins: [\n [customRenderer, { strategy: config?.linkTargetStrategy, siteUrl: config?.siteUrl }],\n rehypeSlug,\n // @ts-ignore\n rehypePrism,\n ],\n })\n\n const Content = mdxModule.default as MdxContent\n const components = basePath ? { img: makeImgComponent(basePath) } : undefined\n return <Content components={components as Record<string, ComponentType<unknown>>} />\n}\n","import { renderMdxSource } from './renderMdx'\nimport type { Article } from './articleTypes'\nimport type { ArticlesConfig } from './articlesConfig'\n\ntype ArticleContentProps = Readonly<{\n article: Article\n className?: string\n config?: ArticlesConfig\n}>\n\nexport async function ArticleContent({ article, className, config }: ArticleContentProps) {\n if (article.contentType === 'mdx' && article.mdxSource) {\n const content = await renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config)\n return <div className={className}>{content}</div>\n }\n return (\n <div className={className} dangerouslySetInnerHTML={{ __html: article.htmlContent || '' }} />\n )\n}\n","import type { TocItem } from './articleTypes'\n\ntype ArticleTOCProps = Readonly<{ toc: TocItem[]; className?: string }>\n\nexport function ArticleTOC({ toc, className }: ArticleTOCProps) {\n if (!toc.length) return null\n return (\n <nav\n aria-label=\"Table of contents\"\n className={`mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className ?? ''}`}\n >\n <p className=\"mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground\">\n On this page\n </p>\n <ul className=\"space-y-1 text-sm\">\n {toc.map((item) => (\n <li key={item.id} style={{ paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }}>\n <a\n href={`#${item.id}`}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n >\n {item.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n )\n}\n"],"mappings":"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","names":["sanitizeImagePath","rehypePrism","rehypeSlug","remarkGfm","remarkGithubBlockquoteAlert","remarkGfm","remarkGithubBlockquoteAlert","rehypeSlug","rehypePrism","jsx","jsx"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullstackdatasolutions/articles",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "funding": {
@@ -1,11 +1,16 @@
1
1
  import { renderMdxSource } from './renderMdx'
2
2
  import type { Article } from './articleTypes'
3
+ import type { ArticlesConfig } from './articlesConfig'
3
4
 
4
- type ArticleContentProps = Readonly<{ article: Article; className?: string }>
5
+ type ArticleContentProps = Readonly<{
6
+ article: Article
7
+ className?: string
8
+ config?: ArticlesConfig
9
+ }>
5
10
 
6
- export async function ArticleContent({ article, className }: ArticleContentProps) {
11
+ export async function ArticleContent({ article, className, config }: ArticleContentProps) {
7
12
  if (article.contentType === 'mdx' && article.mdxSource) {
8
- const content = await renderMdxSource(article.mdxSource, `/articles/${article.slug}`)
13
+ const content = await renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config)
9
14
  return <div className={className}>{content}</div>
10
15
  }
11
16
  return (
@@ -115,7 +115,11 @@ describe('ArticleContent', () => {
115
115
  it('passes basePath derived from article slug to renderMdxSource', async () => {
116
116
  const article: Article = { ...baseArticle, contentType: 'mdx', mdxSource: '# Hello MDX' }
117
117
  await ArticleContent({ article })
118
- expect(mockRenderMdxSource).toHaveBeenCalledWith('# Hello MDX', '/articles/test-slug')
118
+ expect(mockRenderMdxSource).toHaveBeenCalledWith(
119
+ '# Hello MDX',
120
+ '/articles/test-slug',
121
+ undefined
122
+ )
119
123
  })
120
124
 
121
125
  it('passes basePath with nested slug to renderMdxSource', async () => {
@@ -128,8 +132,20 @@ describe('ArticleContent', () => {
128
132
  await ArticleContent({ article })
129
133
  expect(mockRenderMdxSource).toHaveBeenCalledWith(
130
134
  '# Barbarian Guide',
131
- '/articles/tales-of-the-valiant/tov-barbarian-guide'
135
+ '/articles/tales-of-the-valiant/tov-barbarian-guide',
136
+ undefined
132
137
  )
133
138
  })
139
+
140
+ it('passes config to renderMdxSource', async () => {
141
+ const article: Article = { ...baseArticle, contentType: 'mdx', mdxSource: '# Hello MDX' }
142
+ const config = {
143
+ siteUrl: 'https://example.com',
144
+ siteName: 'Example',
145
+ linkTargetStrategy: 'external-new-tab' as const,
146
+ }
147
+ await ArticleContent({ article, config })
148
+ expect(mockRenderMdxSource).toHaveBeenCalledWith('# Hello MDX', '/articles/test-slug', config)
149
+ })
134
150
  })
135
151
  })