@fullstackdatasolutions/articles 0.8.1 → 0.8.2

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.
@@ -0,0 +1,652 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __export = (target, all) => {
26
+ for (var name in all)
27
+ __defProp(target, name, { get: all[name], enumerable: true });
28
+ };
29
+ var __copyProps = (to, from, except, desc) => {
30
+ if (from && typeof from === "object" || typeof from === "function") {
31
+ for (let key of __getOwnPropNames(from))
32
+ if (!__hasOwnProp.call(to, key) && key !== except)
33
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
34
+ }
35
+ return to;
36
+ };
37
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
38
+ // If the importer is in node compatibility mode or this is not an ESM
39
+ // file that has been converted to a CommonJS file using a Babel-
40
+ // compatible transform (i.e. "__esModule" has not been set), then set
41
+ // "default" to the CommonJS "module.exports" for node compatibility.
42
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
43
+ mod
44
+ ));
45
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
46
+ var __async = (__this, __arguments, generator) => {
47
+ return new Promise((resolve, reject) => {
48
+ var fulfilled = (value) => {
49
+ try {
50
+ step(generator.next(value));
51
+ } catch (e) {
52
+ reject(e);
53
+ }
54
+ };
55
+ var rejected = (value) => {
56
+ try {
57
+ step(generator.throw(value));
58
+ } catch (e) {
59
+ reject(e);
60
+ }
61
+ };
62
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
63
+ step((generator = generator.apply(__this, __arguments)).next());
64
+ });
65
+ };
66
+
67
+ // src/nextjs.ts
68
+ var nextjs_exports = {};
69
+ __export(nextjs_exports, {
70
+ createArticleMarkdownHandler: () => createArticleMarkdownHandler
71
+ });
72
+ module.exports = __toCommonJS(nextjs_exports);
73
+
74
+ // src/server-articles.ts
75
+ var import_react = require("react");
76
+ var import_gray_matter = __toESM(require("gray-matter"), 1);
77
+ var import_node_fs = __toESM(require("fs"), 1);
78
+ var import_node_path = __toESM(require("path"), 1);
79
+ var import_reading_time = __toESM(require("reading-time"), 1);
80
+
81
+ // src/markdown.ts
82
+ var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
83
+ var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
84
+ var import_rehype_slug = __toESM(require("rehype-slug"), 1);
85
+ var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
86
+ var import_remark = require("remark");
87
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
88
+ var import_remark_github_blockquote_alert = __toESM(require("remark-github-blockquote-alert"), 1);
89
+ var import_remark_parse = __toESM(require("remark-parse"), 1);
90
+ var import_remark_rehype = __toESM(require("remark-rehype"), 1);
91
+ var import_unist_util_visit = require("unist-util-visit");
92
+
93
+ // src/errorReporting.ts
94
+ function defaultArticlesErrorHandler(report) {
95
+ if (process.env.NODE_ENV === "production") return;
96
+ const context = report.context ? ` ${JSON.stringify(report.context)}` : "";
97
+ const detail = report.error instanceof Error ? `: ${report.error.message}` : "";
98
+ console.warn(`[articles:${report.code}] ${report.message}${context}${detail}`);
99
+ }
100
+ var articlesErrorHandler = defaultArticlesErrorHandler;
101
+ function reportArticlesError(report) {
102
+ articlesErrorHandler(report);
103
+ }
104
+
105
+ // src/markdown.ts
106
+ function sanitizeImagePath(rawPath, articleSlug) {
107
+ if (!rawPath || typeof rawPath !== "string") {
108
+ return null;
109
+ }
110
+ const cleanPath = rawPath.replaceAll(/[\x00-\x1f\x7f-\x9f]/g, "");
111
+ if (cleanPath.startsWith("http://") || cleanPath.startsWith("https://")) {
112
+ return cleanPath;
113
+ }
114
+ if (cleanPath.includes("..") || cleanPath.includes("\\") || cleanPath.startsWith("/")) {
115
+ reportArticlesError({
116
+ code: "unsafe-image-path",
117
+ message: "Rejected unsafe markdown image path.",
118
+ context: { articleSlug, path: cleanPath }
119
+ });
120
+ return null;
121
+ }
122
+ if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {
123
+ reportArticlesError({
124
+ code: "unsafe-image-path",
125
+ message: "Rejected markdown image path with invalid characters.",
126
+ context: { articleSlug, path: cleanPath }
127
+ });
128
+ return null;
129
+ }
130
+ if (cleanPath.includes("/")) {
131
+ const normalizedPath = cleanPath.replaceAll("\\", "/");
132
+ if (normalizedPath.startsWith("..") || normalizedPath.includes("../")) {
133
+ reportArticlesError({
134
+ code: "unsafe-image-path",
135
+ message: "Rejected markdown image path traversal attempt.",
136
+ context: { articleSlug, path: cleanPath }
137
+ });
138
+ return null;
139
+ }
140
+ return `/articles/${articleSlug}/${normalizedPath}`;
141
+ } else {
142
+ return `/articles/${articleSlug}/${cleanPath}`;
143
+ }
144
+ }
145
+ function styleFootnoteLinks(nodes) {
146
+ nodes.forEach((n) => {
147
+ var _a, _b;
148
+ if (n.type !== "element") return;
149
+ const el = n;
150
+ if (el.tagName === "a") {
151
+ const isBackRef = ((_a = el.properties) == null ? void 0 : _a["dataFootnoteBackref"]) !== void 0 || ((_b = el.children[0]) == null ? void 0 : _b.type) === "text" && el.children[0].value === "\u21A9";
152
+ if (isBackRef) {
153
+ el.properties.className = "text-primary hover:underline ml-1";
154
+ if (typeof el.properties.href === "string") {
155
+ el.properties.href = el.properties.href.replaceAll("#user-content-fnref-", "#ref-");
156
+ }
157
+ } else {
158
+ el.properties.className = "text-primary hover:underline break-all";
159
+ }
160
+ }
161
+ if (el.children) styleFootnoteLinks(el.children);
162
+ });
163
+ }
164
+ function processFootnoteRef(node) {
165
+ var _a, _b, _c, _d, _e, _f;
166
+ if (node.tagName !== "sup" || ((_b = (_a = node.children) == null ? void 0 : _a[0]) == null ? void 0 : _b.type) !== "element" || node.children[0].tagName !== "a")
167
+ return;
168
+ const link = node.children[0];
169
+ const href = (_c = link.properties) == null ? void 0 : _c.href;
170
+ if (typeof href !== "string" || !href.startsWith("#user-content-fn-")) return;
171
+ delete link.properties.target;
172
+ delete link.properties.rel;
173
+ link.properties.className = "text-primary hover:underline";
174
+ link.properties.href = href.replaceAll("#user-content-fn-", "#footnote-");
175
+ if (typeof ((_d = node.properties) == null ? void 0 : _d.id) === "string") {
176
+ link.properties.id = node.properties.id.replaceAll("user-content-fnref-", "ref-");
177
+ delete node.properties.id;
178
+ }
179
+ if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
180
+ link.children[0].value = `[${link.children[0].value}]`;
181
+ }
182
+ }
183
+ function processFootnotesSection(node) {
184
+ var _a;
185
+ const cls = (_a = node.properties) == null ? void 0 : _a.className;
186
+ const isFootnotes = node.tagName === "section" && (Array.isArray(cls) ? cls.includes("footnotes") : cls === "footnotes");
187
+ if (!isFootnotes) return;
188
+ const olCandidate = node.children.find(
189
+ (child) => child.type === "element" && child.tagName === "ol"
190
+ );
191
+ if ((olCandidate == null ? void 0 : olCandidate.type) !== "element") return;
192
+ const ol = olCandidate;
193
+ ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
194
+ ol.children.forEach((li) => {
195
+ if (li.type !== "element" || li.tagName !== "li") return;
196
+ const liEl = li;
197
+ if (liEl.properties) {
198
+ liEl.properties.className = "pl-2";
199
+ if (typeof liEl.properties.id === "string") {
200
+ liEl.properties.id = liEl.properties.id.replaceAll("user-content-fn-", "footnote-");
201
+ }
202
+ }
203
+ const pIndex = liEl.children.findIndex(
204
+ (child) => child.type === "element" && child.tagName === "p"
205
+ );
206
+ if (pIndex !== -1) {
207
+ const p = liEl.children[pIndex];
208
+ liEl.children.splice(pIndex, 1, ...p.children);
209
+ }
210
+ styleFootnoteLinks(liEl.children);
211
+ });
212
+ const hr = {
213
+ type: "element",
214
+ tagName: "hr",
215
+ properties: { className: "my-8 border-border" },
216
+ children: []
217
+ };
218
+ const h3 = {
219
+ type: "element",
220
+ tagName: "h3",
221
+ properties: { className: "text-lg font-semibold mb-4" },
222
+ children: [{ type: "text", value: "References" }]
223
+ };
224
+ node.children = [hr, h3, ol];
225
+ if (node.properties) node.properties.className = void 0;
226
+ }
227
+ var customRenderer = () => {
228
+ return (tree) => {
229
+ (0, import_unist_util_visit.visit)(tree, "element", (node) => {
230
+ if (node.tagName) {
231
+ const props = node.properties || {};
232
+ switch (node.tagName) {
233
+ case "h1":
234
+ props.className = "text-3xl font-bold text-foreground mt-8 mb-4 scroll-mt-20";
235
+ break;
236
+ case "h2":
237
+ props.className = "text-2xl font-semibold text-foreground mt-6 mb-3 scroll-mt-20";
238
+ break;
239
+ case "h3":
240
+ props.className = "text-xl font-semibold text-foreground mt-4 mb-2 scroll-mt-20";
241
+ break;
242
+ case "h4":
243
+ props.className = "text-lg font-semibold text-foreground mt-3 mb-2 scroll-mt-20";
244
+ break;
245
+ case "p":
246
+ props.className = "text-muted-foreground leading-relaxed mb-4";
247
+ break;
248
+ case "a": {
249
+ props.className = "text-primary hover:underline transition-colors duration-200";
250
+ const href = typeof props.href === "string" ? props.href : "";
251
+ if (!href.startsWith("#")) {
252
+ props.target = "_blank";
253
+ props.rel = "noopener noreferrer";
254
+ }
255
+ break;
256
+ }
257
+ case "ul":
258
+ props.className = "list-disc list-inside mb-4 space-y-2 ml-4";
259
+ break;
260
+ case "ol":
261
+ props.className = "list-decimal list-inside mb-4 space-y-2 ml-4";
262
+ break;
263
+ case "li":
264
+ props.className = "mb-1";
265
+ break;
266
+ case "blockquote":
267
+ props.className = "border-l-4 border-primary pl-4 italic my-4 text-muted-foreground";
268
+ break;
269
+ case "code":
270
+ props.className = (props.className ? props.className + " " : "") + "bg-muted px-1 py-0.5 rounded text-sm font-mono";
271
+ break;
272
+ case "pre":
273
+ props.className = "bg-muted rounded-lg p-4 overflow-x-auto my-4";
274
+ break;
275
+ case "img":
276
+ props.className = "rounded-lg my-6 w-full max-w-2xl mx-auto";
277
+ break;
278
+ case "table":
279
+ props.className = "border-collapse border border-border my-4 w-full";
280
+ break;
281
+ case "th":
282
+ props.className = "border border-border px-2 py-1 bg-muted font-semibold";
283
+ break;
284
+ case "td":
285
+ props.className = "border border-border px-2 py-1";
286
+ break;
287
+ case "hr":
288
+ props.className = "my-8 border-border";
289
+ break;
290
+ default:
291
+ break;
292
+ }
293
+ node.properties = props;
294
+ }
295
+ });
296
+ (0, import_unist_util_visit.visit)(tree, "element", (node) => {
297
+ processFootnoteRef(node);
298
+ processFootnotesSection(node);
299
+ });
300
+ };
301
+ };
302
+ var rehypeProcessImages = (options = {}) => {
303
+ return (tree) => {
304
+ (0, import_unist_util_visit.visit)(tree, "element", (node) => {
305
+ if (node.tagName === "img" && node.properties) {
306
+ const src = node.properties.src;
307
+ if (src && typeof src === "string" && options.articleSlug) {
308
+ const sanitizedSrc = sanitizeImagePath(src, options.articleSlug);
309
+ if (sanitizedSrc) {
310
+ node.properties.src = sanitizedSrc;
311
+ } else {
312
+ reportArticlesError({
313
+ code: "unsafe-image-path",
314
+ message: "Using placeholder for unsafe markdown image path.",
315
+ context: { articleSlug: options.articleSlug, path: src }
316
+ });
317
+ node.properties.src = "/placeholder-logo.png";
318
+ }
319
+ }
320
+ }
321
+ });
322
+ };
323
+ };
324
+ function markdownToHtml(markdown, articleSlug) {
325
+ return __async(this, null, function* () {
326
+ try {
327
+ let processor = (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_github_blockquote_alert.default).use(import_remark_rehype.default).use(customRenderer).use(import_rehype_slug.default).use(import_rehype_prism_plus.default).use(import_rehype_sanitize.default, {
328
+ attributes: {
329
+ "*": ["className", "class", "id"],
330
+ a: ["href", "target", "rel", "id"],
331
+ img: ["src", "alt"]
332
+ }
333
+ });
334
+ if (articleSlug) {
335
+ processor = processor.use(rehypeProcessImages, { articleSlug });
336
+ }
337
+ const result = yield processor.use(import_rehype_stringify.default).process(markdown);
338
+ return result.toString();
339
+ } catch (error) {
340
+ reportArticlesError({
341
+ code: "markdown-conversion-failed",
342
+ message: "Unable to convert markdown to HTML.",
343
+ error,
344
+ context: { articleSlug }
345
+ });
346
+ return markdown;
347
+ }
348
+ });
349
+ }
350
+ function nodeTextValue(c) {
351
+ return c.type === "text" ? c.value : "";
352
+ }
353
+ function extractHeadingItem(node) {
354
+ var _a;
355
+ const match = /^h([1-6])$/.exec(node.tagName);
356
+ if (!match) return null;
357
+ const id = typeof ((_a = node.properties) == null ? void 0 : _a.id) === "string" ? node.properties.id : "";
358
+ const text = node.children.map(nodeTextValue).join("");
359
+ if (!id || !text) return null;
360
+ return { id, depth: Number.parseInt(match[1], 10), text };
361
+ }
362
+ function extractToc(markdown) {
363
+ return __async(this, null, function* () {
364
+ const headings = [];
365
+ const collectHeadings = () => (tree) => {
366
+ (0, import_unist_util_visit.visit)(tree, "element", (node) => {
367
+ const item = extractHeadingItem(node);
368
+ if (item) headings.push(item);
369
+ });
370
+ };
371
+ yield (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(import_rehype_slug.default).use(collectHeadings).use(import_rehype_stringify.default).process(markdown);
372
+ return headings;
373
+ });
374
+ }
375
+
376
+ // src/server-articles.ts
377
+ var articlesDirectory = import_node_path.default.join(
378
+ /* turbopackIgnore: true */
379
+ process.cwd(),
380
+ "public/articles"
381
+ );
382
+ function getReadingTime(content) {
383
+ return (0, import_reading_time.default)(content).text;
384
+ }
385
+ function findArticleImage(slug) {
386
+ try {
387
+ const articleDir = import_node_path.default.join(articlesDirectory, slug);
388
+ const entries = import_node_fs.default.readdirSync(articleDir, { withFileTypes: true });
389
+ const names = entries.map((entry) => {
390
+ if (typeof entry === "string") return entry;
391
+ if (entry && typeof entry.name === "string") return entry.name;
392
+ return null;
393
+ }).filter((name) => Boolean(name));
394
+ const imageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
395
+ const imageFileName = names.find(
396
+ (name) => imageExtensions.some((ext) => name.toLowerCase().endsWith(ext))
397
+ );
398
+ return imageFileName ? sanitizeImagePath2(imageFileName, slug) : null;
399
+ } catch (e) {
400
+ return null;
401
+ }
402
+ }
403
+ function sanitizeImagePath2(rawPath, articleSlug) {
404
+ if (!rawPath || typeof rawPath !== "string") return null;
405
+ const cleanPath = rawPath.replaceAll(/[\x00-\x1f\x7f-\x9f]/g, "");
406
+ if (cleanPath.startsWith("http://") || cleanPath.startsWith("https://")) return cleanPath;
407
+ if (cleanPath.includes("..") || cleanPath.includes("\\") || cleanPath.startsWith("/")) {
408
+ reportArticlesError({
409
+ code: "unsafe-image-path",
410
+ message: "Rejected unsafe article image path.",
411
+ context: { articleSlug, path: cleanPath }
412
+ });
413
+ return null;
414
+ }
415
+ if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {
416
+ reportArticlesError({
417
+ code: "unsafe-image-path",
418
+ message: "Rejected article image path with invalid characters.",
419
+ context: { articleSlug, path: cleanPath }
420
+ });
421
+ return null;
422
+ }
423
+ if (cleanPath.includes("/")) {
424
+ const normalizedPath = import_node_path.default.normalize(cleanPath);
425
+ if (normalizedPath.startsWith("..") || normalizedPath.includes("../")) {
426
+ reportArticlesError({
427
+ code: "unsafe-image-path",
428
+ message: "Rejected article image path traversal attempt.",
429
+ context: { articleSlug, path: cleanPath }
430
+ });
431
+ return null;
432
+ }
433
+ return `/articles/${articleSlug}/${cleanPath}`;
434
+ }
435
+ return `/articles/${articleSlug}/${cleanPath}`;
436
+ }
437
+ function findArticleFile(slug) {
438
+ const mdPath = import_node_path.default.join(articlesDirectory, slug, "article.md");
439
+ const mdxPath = import_node_path.default.join(articlesDirectory, slug, "article.mdx");
440
+ if (import_node_fs.default.existsSync(mdPath)) return { filePath: mdPath, contentType: "md" };
441
+ if (import_node_fs.default.existsSync(mdxPath)) return { filePath: mdxPath, contentType: "mdx" };
442
+ return null;
443
+ }
444
+ function getAvailableArticleSlugs() {
445
+ try {
446
+ if (!import_node_fs.default.existsSync(articlesDirectory)) return [];
447
+ return walkArticleDir(articlesDirectory, "");
448
+ } catch (error) {
449
+ reportArticlesError({
450
+ code: "article-directory-read-failed",
451
+ message: "Unable to read articles directory.",
452
+ error,
453
+ context: { directory: articlesDirectory }
454
+ });
455
+ return [];
456
+ }
457
+ }
458
+ function walkArticleDir(dir, baseSlug) {
459
+ const slugs = [];
460
+ try {
461
+ const items = import_node_fs.default.readdirSync(dir, { withFileTypes: true });
462
+ for (const item of items) {
463
+ if (!item.isDirectory()) continue;
464
+ const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name;
465
+ if (findArticleFile(slug) !== null) {
466
+ slugs.push(slug);
467
+ }
468
+ slugs.push(...walkArticleDir(import_node_path.default.join(dir, item.name), slug));
469
+ }
470
+ } catch (e) {
471
+ }
472
+ return slugs;
473
+ }
474
+ function parseDateField(rawDate) {
475
+ if (!rawDate) return void 0;
476
+ try {
477
+ const parsed = new Date(rawDate);
478
+ if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split("T")[0];
479
+ } catch (e) {
480
+ }
481
+ return void 0;
482
+ }
483
+ function resolveFeaturedImage(rawImage, slug) {
484
+ const img = typeof rawImage === "string" ? rawImage : "";
485
+ if (img && !img.startsWith("http")) return sanitizeImagePath2(img, slug) || "/placeholder-logo.png";
486
+ if (!img) return findArticleImage(slug) || "/placeholder-logo.png";
487
+ return img;
488
+ }
489
+ function parseFaqItems(raw) {
490
+ if (!Array.isArray(raw)) return void 0;
491
+ const items = raw.filter(
492
+ (item) => typeof item === "object" && item !== null && typeof item.question === "string" && typeof item.answer === "string"
493
+ );
494
+ return items.length ? items : void 0;
495
+ }
496
+ function parseHowToSteps(raw) {
497
+ if (!Array.isArray(raw)) return void 0;
498
+ const steps = raw.filter(
499
+ (item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.text === "string"
500
+ );
501
+ return steps.length ? steps : void 0;
502
+ }
503
+ function getArticleSummary(slug) {
504
+ return __async(this, null, function* () {
505
+ try {
506
+ const found = findArticleFile(slug);
507
+ if (!found) return null;
508
+ const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
509
+ const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
510
+ const readTime = getReadingTime(markdownContent);
511
+ const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
512
+ const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
513
+ return {
514
+ slug,
515
+ title: data.title || slug.replaceAll("-", " "),
516
+ excerpt: data.excerpt || "",
517
+ date: parseDateField(data.date),
518
+ lastmod: parseDateField(data.lastmod),
519
+ author: data.author || "Andrew Blase",
520
+ category: categories[0],
521
+ categories,
522
+ readTime,
523
+ featuredImage: resolveFeaturedImage(data.featuredImage, slug),
524
+ tags: data.tags || [],
525
+ contentType: found.contentType,
526
+ draft: data.draft === true,
527
+ faq: parseFaqItems(data.faq),
528
+ howTo: parseHowToSteps(data.howTo),
529
+ canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
530
+ articleType: typeof data.articleType === "string" ? data.articleType : void 0,
531
+ series: typeof data.series === "string" ? data.series : void 0,
532
+ aiCrawl: data.aiCrawl === true
533
+ };
534
+ } catch (error) {
535
+ reportArticlesError({
536
+ code: "article-load-failed",
537
+ message: "Unable to load article summary.",
538
+ error,
539
+ context: { slug }
540
+ });
541
+ return null;
542
+ }
543
+ });
544
+ }
545
+ var getArticleMetadata = (0, import_react.cache)((slug) => __async(void 0, null, function* () {
546
+ try {
547
+ const summary = yield getArticleSummary(slug);
548
+ if (!summary) return null;
549
+ const found = findArticleFile(slug);
550
+ if (!found) return null;
551
+ const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
552
+ const { content: markdownContent } = (0, import_gray_matter.default)(fileContent);
553
+ const toc = yield extractToc(markdownContent);
554
+ let htmlContent;
555
+ let mdxSource;
556
+ if (found.contentType === "mdx") {
557
+ mdxSource = markdownContent;
558
+ } else {
559
+ htmlContent = yield markdownToHtml(markdownContent, slug);
560
+ }
561
+ return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
562
+ } catch (error) {
563
+ reportArticlesError({
564
+ code: "article-load-failed",
565
+ message: "Unable to load article metadata.",
566
+ error,
567
+ context: { slug }
568
+ });
569
+ return null;
570
+ }
571
+ }));
572
+ var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, function* () {
573
+ const slugs = getAvailableArticleSlugs();
574
+ const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
575
+ const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
576
+ return articles.filter((article) => article !== null).filter((article) => !article.date || article.date <= currentDate).filter((article) => !(article.draft && process.env.NODE_ENV === "production")).sort((a, b) => {
577
+ if (!a.date && !b.date) return 0;
578
+ if (!a.date) return 1;
579
+ if (!b.date) return -1;
580
+ return new Date(b.date).getTime() - new Date(a.date).getTime();
581
+ });
582
+ }));
583
+ function getArticleMarkdown(slug) {
584
+ return __async(this, null, function* () {
585
+ try {
586
+ const summary = yield getArticleSummary(slug);
587
+ if (!(summary == null ? void 0 : summary.aiCrawl)) return null;
588
+ const found = findArticleFile(slug);
589
+ if (!found) return null;
590
+ const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
591
+ const { content: markdownContent } = (0, import_gray_matter.default)(fileContent);
592
+ return markdownContent;
593
+ } catch (error) {
594
+ reportArticlesError({
595
+ code: "article-markdown-load-failed",
596
+ message: "Unable to load article markdown.",
597
+ error,
598
+ context: { slug }
599
+ });
600
+ return null;
601
+ }
602
+ });
603
+ }
604
+ function getArticleMarkdownResponse(slug, config) {
605
+ return __async(this, null, function* () {
606
+ const markdown = yield getArticleMarkdown(slug);
607
+ if (markdown === null) return new Response("Not Found", { status: 404 });
608
+ const article = yield getArticleMetadata(slug);
609
+ return new Response(markdown, {
610
+ headers: __spreadValues({
611
+ "Content-Type": "text/markdown; charset=utf-8",
612
+ "Cache-Control": "public, max-age=3600, s-maxage=3600"
613
+ }, article ? getArticleAiHeaders(article, config) : {})
614
+ });
615
+ });
616
+ }
617
+ function getArticleMarkdownUrl(article, config) {
618
+ if (article.aiCrawl !== true) return void 0;
619
+ const pathname = `/articles/${article.slug}.md`;
620
+ if (!config) return pathname;
621
+ return `${config.siteUrl.replace(/\/$/, "")}${pathname}`;
622
+ }
623
+ function getArticleAiHeaders(article, config) {
624
+ const markdownUrl = getArticleMarkdownUrl(article, config);
625
+ if (markdownUrl) {
626
+ return {
627
+ Link: `<${markdownUrl}>; rel="alternate"; type="text/markdown"`
628
+ };
629
+ }
630
+ return {
631
+ "X-Robots-Tag": "noai, noimageai"
632
+ };
633
+ }
634
+
635
+ // src/nextjs.ts
636
+ function createArticleMarkdownHandler(config) {
637
+ function GET(_request, context) {
638
+ return __async(this, null, function* () {
639
+ const params = yield context.params;
640
+ const slugParts = params["slug"];
641
+ const slug = Array.isArray(slugParts) ? slugParts.join("/") : slugParts != null ? slugParts : "";
642
+ const cleanSlug = slug.endsWith(".md") ? slug.slice(0, -3) : slug;
643
+ return getArticleMarkdownResponse(cleanSlug, config);
644
+ });
645
+ }
646
+ return { GET };
647
+ }
648
+ // Annotate the CommonJS export names for ESM import in node:
649
+ 0 && (module.exports = {
650
+ createArticleMarkdownHandler
651
+ });
652
+ //# sourceMappingURL=nextjs.cjs.map