@fullstackdatasolutions/articles 0.7.2 → 0.8.1

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.d.cts CHANGED
@@ -37,6 +37,7 @@ interface Article {
37
37
  canonicalUrl?: string;
38
38
  articleType?: string;
39
39
  series?: string;
40
+ aiCrawl?: boolean;
40
41
  }
41
42
  interface CategoryInfo {
42
43
  name: string;
@@ -45,19 +46,6 @@ interface CategoryInfo {
45
46
  featuredImage: string;
46
47
  }
47
48
 
48
- declare function sanitizeImagePath(rawPath: string, articleSlug: string): string | null;
49
- declare function getAvailableArticleSlugs(): string[];
50
- declare const getArticleMetadata: (slug: string) => Promise<Article | null>;
51
- declare const getAllArticles: () => Promise<Article[]>;
52
- declare function getAdjacentArticles(currentSlug: string): Promise<{
53
- previous: Article | null;
54
- next: Article | null;
55
- }>;
56
- declare function searchArticles(query: string): Promise<Article[]>;
57
- declare function categoryToSlug(category: string): string;
58
- declare function getAllCategories(): Promise<CategoryInfo[]>;
59
- declare function getArticlesByCategory(categorySlug: string): Promise<Article[]>;
60
-
61
49
  /** Keys for each renderable section of the articles listing page. */
62
50
  type ArticlesSection = 'hero' | 'search' | 'featured' | 'latest' | 'categories' | 'newsletter';
63
51
  /**
@@ -149,8 +137,28 @@ interface ArticlesConfig {
149
137
  showToc?: boolean;
150
138
  /** Set to false to hide the "Back to Articles" navigation link on article detail pages. Default: true. */
151
139
  showBackToArticles?: boolean;
140
+ /** Set to false to hide author names from UI and metadata. Default: true. */
141
+ showAuthor?: boolean;
152
142
  }
153
143
 
144
+ declare function sanitizeImagePath(rawPath: string, articleSlug: string): string | null;
145
+ declare function getAvailableArticleSlugs(): string[];
146
+ declare const getArticleMetadata: (slug: string) => Promise<Article | null>;
147
+ declare const getAllArticles: () => Promise<Article[]>;
148
+ declare function getAdjacentArticles(currentSlug: string): Promise<{
149
+ previous: Article | null;
150
+ next: Article | null;
151
+ }>;
152
+ declare function getArticleMarkdown(slug: string): Promise<string | null>;
153
+ declare function getArticleMarkdownResponse(slug: string, config: ArticlesConfig): Promise<Response>;
154
+ declare function getArticleMarkdownUrl(article: Pick<Article, 'slug' | 'aiCrawl'>, config?: Pick<ArticlesConfig, 'siteUrl'>): string | undefined;
155
+ declare function getArticleAiHeaders(article: Pick<Article, 'slug' | 'aiCrawl'>, config?: Pick<ArticlesConfig, 'siteUrl'>): Record<string, string>;
156
+ declare function getAiRobotsTxtRules(): Promise<string>;
157
+ declare function searchArticles(query: string, config?: ArticlesConfig): Promise<Article[]>;
158
+ declare function categoryToSlug(category: string): string;
159
+ declare function getAllCategories(): Promise<CategoryInfo[]>;
160
+ declare function getArticlesByCategory(categorySlug: string): Promise<Article[]>;
161
+
154
162
  declare function generateArticleStaticParams(): {
155
163
  slug: string;
156
164
  }[];
@@ -165,6 +173,17 @@ declare function getArticleSitemapEntries(baseUrlOrConfig: string | ArticlesConf
165
173
  declare function markdownToHtml(markdown: string, articleSlug?: string): Promise<string>;
166
174
  declare function extractToc(markdown: string): Promise<TocItem[]>;
167
175
 
176
+ type ArticlesErrorCode = 'article-directory-read-failed' | 'article-load-failed' | 'article-markdown-load-failed' | 'markdown-conversion-failed' | 'unsafe-image-path';
177
+ type ArticlesErrorContext = Readonly<Record<string, string | number | boolean | undefined>>;
178
+ type ArticlesErrorReport = Readonly<{
179
+ code: ArticlesErrorCode;
180
+ message: string;
181
+ error?: unknown;
182
+ context?: ArticlesErrorContext;
183
+ }>;
184
+ type ArticlesErrorHandler = (report: ArticlesErrorReport) => void;
185
+ declare function setArticlesErrorHandler(handler?: ArticlesErrorHandler): void;
186
+
168
187
  type ArticleContentProps = Readonly<{
169
188
  article: Article;
170
189
  className?: string;
@@ -177,4 +196,4 @@ type ArticleTOCProps = Readonly<{
177
196
  }>;
178
197
  declare function ArticleTOC({ toc, className }: ArticleTOCProps): react_jsx_runtime.JSX.Element | null;
179
198
 
180
- export { type Article, ArticleContent, ArticleTOC, type ArticlesConfig, type CategoryInfo, type TocItem, categoryToSlug, extractToc, generateArticleMetadata, generateArticleStaticParams, generateArticlesIndexMetadata, generateCategoryMetadata, generateCategoryStaticParams, getAdjacentArticles, getAllArticles, getAllCategories, getArticleMetadata, getArticleSitemapEntries, getArticlesByCategory, getAvailableArticleSlugs, markdownToHtml, sanitizeImagePath, searchArticles };
199
+ export { type Article, ArticleContent, ArticleTOC, type ArticlesConfig, type ArticlesErrorCode, type ArticlesErrorContext, type ArticlesErrorHandler, type ArticlesErrorReport, type CategoryInfo, type TocItem, categoryToSlug, extractToc, generateArticleMetadata, generateArticleStaticParams, generateArticlesIndexMetadata, generateCategoryMetadata, generateCategoryStaticParams, getAdjacentArticles, getAiRobotsTxtRules, getAllArticles, getAllCategories, getArticleAiHeaders, getArticleMarkdown, getArticleMarkdownResponse, getArticleMarkdownUrl, getArticleMetadata, getArticleSitemapEntries, getArticlesByCategory, getAvailableArticleSlugs, markdownToHtml, sanitizeImagePath, searchArticles, setArticlesErrorHandler };
package/dist/server.d.ts CHANGED
@@ -37,6 +37,7 @@ interface Article {
37
37
  canonicalUrl?: string;
38
38
  articleType?: string;
39
39
  series?: string;
40
+ aiCrawl?: boolean;
40
41
  }
41
42
  interface CategoryInfo {
42
43
  name: string;
@@ -45,19 +46,6 @@ interface CategoryInfo {
45
46
  featuredImage: string;
46
47
  }
47
48
 
48
- declare function sanitizeImagePath(rawPath: string, articleSlug: string): string | null;
49
- declare function getAvailableArticleSlugs(): string[];
50
- declare const getArticleMetadata: (slug: string) => Promise<Article | null>;
51
- declare const getAllArticles: () => Promise<Article[]>;
52
- declare function getAdjacentArticles(currentSlug: string): Promise<{
53
- previous: Article | null;
54
- next: Article | null;
55
- }>;
56
- declare function searchArticles(query: string): Promise<Article[]>;
57
- declare function categoryToSlug(category: string): string;
58
- declare function getAllCategories(): Promise<CategoryInfo[]>;
59
- declare function getArticlesByCategory(categorySlug: string): Promise<Article[]>;
60
-
61
49
  /** Keys for each renderable section of the articles listing page. */
62
50
  type ArticlesSection = 'hero' | 'search' | 'featured' | 'latest' | 'categories' | 'newsletter';
63
51
  /**
@@ -149,8 +137,28 @@ interface ArticlesConfig {
149
137
  showToc?: boolean;
150
138
  /** Set to false to hide the "Back to Articles" navigation link on article detail pages. Default: true. */
151
139
  showBackToArticles?: boolean;
140
+ /** Set to false to hide author names from UI and metadata. Default: true. */
141
+ showAuthor?: boolean;
152
142
  }
153
143
 
144
+ declare function sanitizeImagePath(rawPath: string, articleSlug: string): string | null;
145
+ declare function getAvailableArticleSlugs(): string[];
146
+ declare const getArticleMetadata: (slug: string) => Promise<Article | null>;
147
+ declare const getAllArticles: () => Promise<Article[]>;
148
+ declare function getAdjacentArticles(currentSlug: string): Promise<{
149
+ previous: Article | null;
150
+ next: Article | null;
151
+ }>;
152
+ declare function getArticleMarkdown(slug: string): Promise<string | null>;
153
+ declare function getArticleMarkdownResponse(slug: string, config: ArticlesConfig): Promise<Response>;
154
+ declare function getArticleMarkdownUrl(article: Pick<Article, 'slug' | 'aiCrawl'>, config?: Pick<ArticlesConfig, 'siteUrl'>): string | undefined;
155
+ declare function getArticleAiHeaders(article: Pick<Article, 'slug' | 'aiCrawl'>, config?: Pick<ArticlesConfig, 'siteUrl'>): Record<string, string>;
156
+ declare function getAiRobotsTxtRules(): Promise<string>;
157
+ declare function searchArticles(query: string, config?: ArticlesConfig): Promise<Article[]>;
158
+ declare function categoryToSlug(category: string): string;
159
+ declare function getAllCategories(): Promise<CategoryInfo[]>;
160
+ declare function getArticlesByCategory(categorySlug: string): Promise<Article[]>;
161
+
154
162
  declare function generateArticleStaticParams(): {
155
163
  slug: string;
156
164
  }[];
@@ -165,6 +173,17 @@ declare function getArticleSitemapEntries(baseUrlOrConfig: string | ArticlesConf
165
173
  declare function markdownToHtml(markdown: string, articleSlug?: string): Promise<string>;
166
174
  declare function extractToc(markdown: string): Promise<TocItem[]>;
167
175
 
176
+ type ArticlesErrorCode = 'article-directory-read-failed' | 'article-load-failed' | 'article-markdown-load-failed' | 'markdown-conversion-failed' | 'unsafe-image-path';
177
+ type ArticlesErrorContext = Readonly<Record<string, string | number | boolean | undefined>>;
178
+ type ArticlesErrorReport = Readonly<{
179
+ code: ArticlesErrorCode;
180
+ message: string;
181
+ error?: unknown;
182
+ context?: ArticlesErrorContext;
183
+ }>;
184
+ type ArticlesErrorHandler = (report: ArticlesErrorReport) => void;
185
+ declare function setArticlesErrorHandler(handler?: ArticlesErrorHandler): void;
186
+
168
187
  type ArticleContentProps = Readonly<{
169
188
  article: Article;
170
189
  className?: string;
@@ -177,4 +196,4 @@ type ArticleTOCProps = Readonly<{
177
196
  }>;
178
197
  declare function ArticleTOC({ toc, className }: ArticleTOCProps): react_jsx_runtime.JSX.Element | null;
179
198
 
180
- export { type Article, ArticleContent, ArticleTOC, type ArticlesConfig, type CategoryInfo, type TocItem, categoryToSlug, extractToc, generateArticleMetadata, generateArticleStaticParams, generateArticlesIndexMetadata, generateCategoryMetadata, generateCategoryStaticParams, getAdjacentArticles, getAllArticles, getAllCategories, getArticleMetadata, getArticleSitemapEntries, getArticlesByCategory, getAvailableArticleSlugs, markdownToHtml, sanitizeImagePath, searchArticles };
199
+ export { type Article, ArticleContent, ArticleTOC, type ArticlesConfig, type ArticlesErrorCode, type ArticlesErrorContext, type ArticlesErrorHandler, type ArticlesErrorReport, type CategoryInfo, type TocItem, categoryToSlug, extractToc, generateArticleMetadata, generateArticleStaticParams, generateArticlesIndexMetadata, generateCategoryMetadata, generateCategoryStaticParams, getAdjacentArticles, getAiRobotsTxtRules, getAllArticles, getAllCategories, getArticleAiHeaders, getArticleMarkdown, getArticleMarkdownResponse, getArticleMarkdownUrl, getArticleMetadata, getArticleSitemapEntries, getArticlesByCategory, getAvailableArticleSlugs, markdownToHtml, sanitizeImagePath, searchArticles, setArticlesErrorHandler };
package/dist/server.js CHANGED
@@ -68,6 +68,23 @@ import remarkGithubBlockquoteAlert from "remark-github-blockquote-alert";
68
68
  import remarkParse from "remark-parse";
69
69
  import remarkRehype from "remark-rehype";
70
70
  import { visit } from "unist-util-visit";
71
+
72
+ // src/errorReporting.ts
73
+ function defaultArticlesErrorHandler(report) {
74
+ if (process.env.NODE_ENV === "production") return;
75
+ const context = report.context ? ` ${JSON.stringify(report.context)}` : "";
76
+ const detail = report.error instanceof Error ? `: ${report.error.message}` : "";
77
+ console.warn(`[articles:${report.code}] ${report.message}${context}${detail}`);
78
+ }
79
+ var articlesErrorHandler = defaultArticlesErrorHandler;
80
+ function setArticlesErrorHandler(handler) {
81
+ articlesErrorHandler = handler != null ? handler : defaultArticlesErrorHandler;
82
+ }
83
+ function reportArticlesError(report) {
84
+ articlesErrorHandler(report);
85
+ }
86
+
87
+ // src/markdown.ts
71
88
  function sanitizeImagePath(rawPath, articleSlug) {
72
89
  if (!rawPath || typeof rawPath !== "string") {
73
90
  return null;
@@ -77,17 +94,29 @@ function sanitizeImagePath(rawPath, articleSlug) {
77
94
  return cleanPath;
78
95
  }
79
96
  if (cleanPath.includes("..") || cleanPath.includes("\\") || cleanPath.startsWith("/")) {
80
- console.warn(`Potentially unsafe image path detected: ${cleanPath}`);
97
+ reportArticlesError({
98
+ code: "unsafe-image-path",
99
+ message: "Rejected unsafe markdown image path.",
100
+ context: { articleSlug, path: cleanPath }
101
+ });
81
102
  return null;
82
103
  }
83
104
  if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {
84
- console.warn(`Invalid characters in image path: ${cleanPath}`);
105
+ reportArticlesError({
106
+ code: "unsafe-image-path",
107
+ message: "Rejected markdown image path with invalid characters.",
108
+ context: { articleSlug, path: cleanPath }
109
+ });
85
110
  return null;
86
111
  }
87
112
  if (cleanPath.includes("/")) {
88
113
  const normalizedPath = cleanPath.replaceAll("\\", "/");
89
114
  if (normalizedPath.startsWith("..") || normalizedPath.includes("../")) {
90
- console.warn(`Path traversal attempt detected: ${cleanPath}`);
115
+ reportArticlesError({
116
+ code: "unsafe-image-path",
117
+ message: "Rejected markdown image path traversal attempt.",
118
+ context: { articleSlug, path: cleanPath }
119
+ });
91
120
  return null;
92
121
  }
93
122
  return `/articles/${articleSlug}/${normalizedPath}`;
@@ -262,7 +291,11 @@ var rehypeProcessImages = (options = {}) => {
262
291
  if (sanitizedSrc) {
263
292
  node.properties.src = sanitizedSrc;
264
293
  } else {
265
- console.warn(`Using placeholder for unsafe image path: ${src}`);
294
+ reportArticlesError({
295
+ code: "unsafe-image-path",
296
+ message: "Using placeholder for unsafe markdown image path.",
297
+ context: { articleSlug: options.articleSlug, path: src }
298
+ });
266
299
  node.properties.src = "/placeholder-logo.png";
267
300
  }
268
301
  }
@@ -286,7 +319,12 @@ function markdownToHtml(markdown, articleSlug) {
286
319
  const result = yield processor.use(rehypeStringify).process(markdown);
287
320
  return result.toString();
288
321
  } catch (error) {
289
- console.error("Markdown conversion error:", error);
322
+ reportArticlesError({
323
+ code: "markdown-conversion-failed",
324
+ message: "Unable to convert markdown to HTML.",
325
+ error,
326
+ context: { articleSlug }
327
+ });
290
328
  return markdown;
291
329
  }
292
330
  });
@@ -318,7 +356,11 @@ function extractToc(markdown) {
318
356
  }
319
357
 
320
358
  // src/server-articles.ts
321
- var articlesDirectory = path.join(process.cwd(), "public/articles");
359
+ var articlesDirectory = path.join(
360
+ /* turbopackIgnore: true */
361
+ process.cwd(),
362
+ "public/articles"
363
+ );
322
364
  function getReadingTime(content) {
323
365
  return readingTime(content).text;
324
366
  }
@@ -345,17 +387,29 @@ function sanitizeImagePath2(rawPath, articleSlug) {
345
387
  const cleanPath = rawPath.replaceAll(/[\x00-\x1f\x7f-\x9f]/g, "");
346
388
  if (cleanPath.startsWith("http://") || cleanPath.startsWith("https://")) return cleanPath;
347
389
  if (cleanPath.includes("..") || cleanPath.includes("\\") || cleanPath.startsWith("/")) {
348
- console.warn(`Potentially unsafe image path detected: ${cleanPath}`);
390
+ reportArticlesError({
391
+ code: "unsafe-image-path",
392
+ message: "Rejected unsafe article image path.",
393
+ context: { articleSlug, path: cleanPath }
394
+ });
349
395
  return null;
350
396
  }
351
397
  if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {
352
- console.warn(`Invalid characters in image path: ${cleanPath}`);
398
+ reportArticlesError({
399
+ code: "unsafe-image-path",
400
+ message: "Rejected article image path with invalid characters.",
401
+ context: { articleSlug, path: cleanPath }
402
+ });
353
403
  return null;
354
404
  }
355
405
  if (cleanPath.includes("/")) {
356
406
  const normalizedPath = path.normalize(cleanPath);
357
407
  if (normalizedPath.startsWith("..") || normalizedPath.includes("../")) {
358
- console.warn(`Path traversal attempt detected: ${cleanPath}`);
408
+ reportArticlesError({
409
+ code: "unsafe-image-path",
410
+ message: "Rejected article image path traversal attempt.",
411
+ context: { articleSlug, path: cleanPath }
412
+ });
359
413
  return null;
360
414
  }
361
415
  return `/articles/${articleSlug}/${cleanPath}`;
@@ -374,7 +428,12 @@ function getAvailableArticleSlugs() {
374
428
  if (!fs.existsSync(articlesDirectory)) return [];
375
429
  return walkArticleDir(articlesDirectory, "");
376
430
  } catch (error) {
377
- console.error("Error reading articles directory:", error);
431
+ reportArticlesError({
432
+ code: "article-directory-read-failed",
433
+ message: "Unable to read articles directory.",
434
+ error,
435
+ context: { directory: articlesDirectory }
436
+ });
378
437
  return [];
379
438
  }
380
439
  }
@@ -451,10 +510,16 @@ function getArticleSummary(slug) {
451
510
  howTo: parseHowToSteps(data.howTo),
452
511
  canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
453
512
  articleType: typeof data.articleType === "string" ? data.articleType : void 0,
454
- series: typeof data.series === "string" ? data.series : void 0
513
+ series: typeof data.series === "string" ? data.series : void 0,
514
+ aiCrawl: data.aiCrawl === true
455
515
  };
456
516
  } catch (error) {
457
- console.error(`Error loading article ${slug}:`, error);
517
+ reportArticlesError({
518
+ code: "article-load-failed",
519
+ message: "Unable to load article summary.",
520
+ error,
521
+ context: { slug }
522
+ });
458
523
  return null;
459
524
  }
460
525
  });
@@ -477,7 +542,12 @@ var getArticleMetadata = cache((slug) => __async(void 0, null, function* () {
477
542
  }
478
543
  return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
479
544
  } catch (error) {
480
- console.error(`Error loading article ${slug}:`, error);
545
+ reportArticlesError({
546
+ code: "article-load-failed",
547
+ message: "Unable to load article metadata.",
548
+ error,
549
+ context: { slug }
550
+ });
481
551
  return null;
482
552
  }
483
553
  }));
@@ -502,16 +572,86 @@ function getAdjacentArticles(currentSlug) {
502
572
  return { previous, next };
503
573
  });
504
574
  }
505
- function searchArticles(query) {
575
+ function getArticleMarkdown(slug) {
576
+ return __async(this, null, function* () {
577
+ try {
578
+ const summary = yield getArticleSummary(slug);
579
+ if (!(summary == null ? void 0 : summary.aiCrawl)) return null;
580
+ const found = findArticleFile(slug);
581
+ if (!found) return null;
582
+ const fileContent = fs.readFileSync(found.filePath, "utf8");
583
+ const { content: markdownContent } = matter(fileContent);
584
+ return markdownContent;
585
+ } catch (error) {
586
+ reportArticlesError({
587
+ code: "article-markdown-load-failed",
588
+ message: "Unable to load article markdown.",
589
+ error,
590
+ context: { slug }
591
+ });
592
+ return null;
593
+ }
594
+ });
595
+ }
596
+ function getArticleMarkdownResponse(slug, config) {
597
+ return __async(this, null, function* () {
598
+ const markdown = yield getArticleMarkdown(slug);
599
+ if (markdown === null) return new Response("Not Found", { status: 404 });
600
+ const article = yield getArticleMetadata(slug);
601
+ return new Response(markdown, {
602
+ headers: __spreadValues({
603
+ "Content-Type": "text/markdown; charset=utf-8",
604
+ "Cache-Control": "public, max-age=3600, s-maxage=3600"
605
+ }, article ? getArticleAiHeaders(article, config) : {})
606
+ });
607
+ });
608
+ }
609
+ function getArticleMarkdownUrl(article, config) {
610
+ if (article.aiCrawl !== true) return void 0;
611
+ const pathname = `/articles/${article.slug}.md`;
612
+ if (!config) return pathname;
613
+ return `${config.siteUrl.replace(/\/$/, "")}${pathname}`;
614
+ }
615
+ function getArticleAiHeaders(article, config) {
616
+ const markdownUrl = getArticleMarkdownUrl(article, config);
617
+ if (markdownUrl) {
618
+ return {
619
+ Link: `<${markdownUrl}>; rel="alternate"; type="text/markdown"`
620
+ };
621
+ }
622
+ return {
623
+ "X-Robots-Tag": "noai, noimageai"
624
+ };
625
+ }
626
+ function getAiRobotsTxtRules() {
627
+ return __async(this, null, function* () {
628
+ const articles = yield getAllArticles();
629
+ const blockedArticles = articles.filter((article) => article.aiCrawl !== true);
630
+ if (blockedArticles.length === 0) return "";
631
+ const aiCrawlers = [
632
+ "GPTBot",
633
+ "ChatGPT-User",
634
+ "CCBot",
635
+ "ClaudeBot",
636
+ "Claude-User",
637
+ "PerplexityBot",
638
+ "Google-Extended"
639
+ ];
640
+ const disallowRules = blockedArticles.map((article) => `Disallow: /articles/${article.slug}`).join("\n");
641
+ return aiCrawlers.map((crawler) => [`User-agent: ${crawler}`, disallowRules].join("\n")).join("\n\n");
642
+ });
643
+ }
644
+ function searchArticles(query, config) {
506
645
  return __async(this, null, function* () {
507
646
  if (!(query == null ? void 0 : query.trim())) return getAllArticles();
508
647
  const articles = yield getAllArticles();
509
648
  const searchTerm = query.toLowerCase().trim();
649
+ const includeAuthor = (config == null ? void 0 : config.showAuthor) !== false;
510
650
  return articles.filter((article) => {
511
651
  var _a;
512
652
  const matchesTitle = article.title.toLowerCase().includes(searchTerm);
513
653
  const matchesExcerpt = article.excerpt.toLowerCase().includes(searchTerm);
514
- const matchesAuthor = article.author.toLowerCase().includes(searchTerm);
654
+ const matchesAuthor = includeAuthor && article.author.toLowerCase().includes(searchTerm);
515
655
  const matchesCategory = article.categories.some((cat) => cat.toLowerCase().includes(searchTerm));
516
656
  const matchesTags = (_a = article.tags) == null ? void 0 : _a.some((tag) => tag.toLowerCase().includes(searchTerm));
517
657
  return matchesTitle || matchesExcerpt || matchesAuthor || matchesCategory || Boolean(matchesTags);
@@ -582,11 +722,13 @@ function generateArticleMetadata(slug, config) {
582
722
  const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
583
723
  const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : `${siteUrl}/placeholder-logo.png`;
584
724
  const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
725
+ const showAuthor = config.showAuthor !== false;
726
+ const markdownUrl = getArticleMarkdownUrl(article, config);
585
727
  return {
586
728
  title: `${article.title} | ${config.siteName}`,
587
729
  description,
588
730
  keywords: [...((_c = article.tags) != null ? _c : []).map((tag) => tag.toLowerCase())].join(", "),
589
- openGraph: __spreadProps(__spreadValues(__spreadValues({
731
+ openGraph: __spreadProps(__spreadValues(__spreadValues(__spreadValues({
590
732
  title: article.title,
591
733
  description,
592
734
  url: articleUrl,
@@ -594,8 +736,7 @@ function generateArticleMetadata(slug, config) {
594
736
  images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
595
737
  locale: "en_US",
596
738
  type: "article"
597
- }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), {
598
- authors: [article.author],
739
+ }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && { authors: [article.author] }), {
599
740
  tags: (_d = article.tags) != null ? _d : []
600
741
  }),
601
742
  twitter: {
@@ -604,9 +745,13 @@ function generateArticleMetadata(slug, config) {
604
745
  description,
605
746
  images: [imageUrl]
606
747
  },
607
- alternates: {
748
+ alternates: __spreadValues({
608
749
  canonical: canonicalUrl
609
- },
750
+ }, markdownUrl && {
751
+ types: {
752
+ "text/markdown": markdownUrl
753
+ }
754
+ }),
610
755
  robots: {
611
756
  index: true,
612
757
  follow: true,
@@ -618,9 +763,7 @@ function generateArticleMetadata(slug, config) {
618
763
  "max-snippet": -1
619
764
  }
620
765
  },
621
- other: __spreadProps(__spreadValues(__spreadValues({
622
- "article:author": article.author
623
- }, article.date && {
766
+ other: __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, showAuthor && { "article:author": article.author }), article.date && {
624
767
  "article:published_time": new Date(article.date).toISOString()
625
768
  }), article.lastmod && {
626
769
  "article:modified_time": new Date(article.lastmod).toISOString()
@@ -830,14 +973,20 @@ export {
830
973
  generateCategoryMetadata,
831
974
  generateCategoryStaticParams,
832
975
  getAdjacentArticles,
976
+ getAiRobotsTxtRules,
833
977
  getAllArticles,
834
978
  getAllCategories,
979
+ getArticleAiHeaders,
980
+ getArticleMarkdown,
981
+ getArticleMarkdownResponse,
982
+ getArticleMarkdownUrl,
835
983
  getArticleMetadata,
836
984
  getArticleSitemapEntries,
837
985
  getArticlesByCategory,
838
986
  getAvailableArticleSlugs,
839
987
  markdownToHtml,
840
988
  sanitizeImagePath2 as sanitizeImagePath,
841
- searchArticles
989
+ searchArticles,
990
+ setArticlesErrorHandler
842
991
  };
843
992
  //# sourceMappingURL=server.js.map