@mars-stack/blog 0.1.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/chunk-ATB5QJFK.js +14 -0
- package/dist/chunk-FWLT7XZQ.js +83 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/schemas.d.ts +14 -0
- package/dist/schemas.js +1 -0
- package/dist/server/index.d.ts +20 -0
- package/dist/server/index.js +2 -0
- package/package.json +58 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// src/schemas.ts
|
|
4
|
+
var frontmatterSchema = z.object({
|
|
5
|
+
title: z.string().min(1),
|
|
6
|
+
description: z.string().min(1).max(160),
|
|
7
|
+
date: z.string().refine((val) => !isNaN(Date.parse(val)), "Invalid date"),
|
|
8
|
+
author: z.string().default("Team"),
|
|
9
|
+
tags: z.array(z.string()).default([]),
|
|
10
|
+
image: z.string().optional(),
|
|
11
|
+
published: z.boolean().default(true)
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export { frontmatterSchema };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { frontmatterSchema } from './chunk-ATB5QJFK.js';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
import readingTime from 'reading-time';
|
|
6
|
+
|
|
7
|
+
var DEFAULT_CONTENT_DIR = path.join(process.cwd(), "content", "blog");
|
|
8
|
+
var DEFAULT_EXCERPT_LENGTH = 200;
|
|
9
|
+
function getAllPosts(config) {
|
|
10
|
+
const contentDir = config?.contentDir ?? DEFAULT_CONTENT_DIR;
|
|
11
|
+
const excerptLength = config?.excerptLength ?? DEFAULT_EXCERPT_LENGTH;
|
|
12
|
+
if (!fs.existsSync(contentDir)) return [];
|
|
13
|
+
const files = fs.readdirSync(contentDir).filter((f) => f.endsWith(".mdx"));
|
|
14
|
+
const posts = files.map((filename) => {
|
|
15
|
+
const slug = filename.replace(/\.mdx$/, "");
|
|
16
|
+
const filePath = path.join(contentDir, filename);
|
|
17
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
18
|
+
const { data, content } = matter(raw);
|
|
19
|
+
const parsed = frontmatterSchema.safeParse(data);
|
|
20
|
+
if (!parsed.success) {
|
|
21
|
+
console.warn(`Invalid frontmatter in ${filename}:`, parsed.error.message);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (!parsed.data.published) return null;
|
|
25
|
+
const stats = readingTime(content);
|
|
26
|
+
const excerpt = content.replace(/^#+\s.*$/gm, "").trim().slice(0, excerptLength) + "...";
|
|
27
|
+
return {
|
|
28
|
+
slug,
|
|
29
|
+
frontmatter: parsed.data,
|
|
30
|
+
content,
|
|
31
|
+
readingTime: stats.text,
|
|
32
|
+
excerpt
|
|
33
|
+
};
|
|
34
|
+
}).filter(Boolean);
|
|
35
|
+
return posts.sort(
|
|
36
|
+
(a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime()
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function getPostBySlug(slug, config) {
|
|
40
|
+
const contentDir = config?.contentDir ?? DEFAULT_CONTENT_DIR;
|
|
41
|
+
const excerptLength = config?.excerptLength ?? DEFAULT_EXCERPT_LENGTH;
|
|
42
|
+
const filePath = path.join(contentDir, `${slug}.mdx`);
|
|
43
|
+
if (!fs.existsSync(filePath)) return null;
|
|
44
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
45
|
+
const { data, content } = matter(raw);
|
|
46
|
+
const parsed = frontmatterSchema.safeParse(data);
|
|
47
|
+
if (!parsed.success) return null;
|
|
48
|
+
const stats = readingTime(content);
|
|
49
|
+
const excerpt = content.replace(/^#+\s.*$/gm, "").trim().slice(0, excerptLength) + "...";
|
|
50
|
+
return {
|
|
51
|
+
slug,
|
|
52
|
+
frontmatter: parsed.data,
|
|
53
|
+
content,
|
|
54
|
+
readingTime: stats.text,
|
|
55
|
+
excerpt
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function generateRSSFeed(posts, siteUrl, title) {
|
|
59
|
+
const feedTitle = title ?? "Blog";
|
|
60
|
+
const items = posts.map(
|
|
61
|
+
(post) => `
|
|
62
|
+
<item>
|
|
63
|
+
<title><![CDATA[${post.frontmatter.title}]]></title>
|
|
64
|
+
<link>${siteUrl}/blog/${post.slug}</link>
|
|
65
|
+
<guid isPermaLink="true">${siteUrl}/blog/${post.slug}</guid>
|
|
66
|
+
<description><![CDATA[${post.frontmatter.description}]]></description>
|
|
67
|
+
<pubDate>${new Date(post.frontmatter.date).toUTCString()}</pubDate>
|
|
68
|
+
${post.frontmatter.tags.map((tag) => `<category>${tag}</category>`).join("\n ")}
|
|
69
|
+
</item>`
|
|
70
|
+
).join("\n");
|
|
71
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
72
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
73
|
+
<channel>
|
|
74
|
+
<title>${feedTitle}</title>
|
|
75
|
+
<link>${siteUrl}/blog</link>
|
|
76
|
+
<description>Latest posts</description>
|
|
77
|
+
<atom:link href="${siteUrl}/feed.xml" rel="self" type="application/rss+xml" />
|
|
78
|
+
${items}
|
|
79
|
+
</channel>
|
|
80
|
+
</rss>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { generateRSSFeed, getAllPosts, getPostBySlug };
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const frontmatterSchema: z.ZodObject<{
|
|
4
|
+
title: z.ZodString;
|
|
5
|
+
description: z.ZodString;
|
|
6
|
+
date: z.ZodString;
|
|
7
|
+
author: z.ZodDefault<z.ZodString>;
|
|
8
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
9
|
+
image: z.ZodOptional<z.ZodString>;
|
|
10
|
+
published: z.ZodDefault<z.ZodBoolean>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
type Frontmatter = z.infer<typeof frontmatterSchema>;
|
|
13
|
+
|
|
14
|
+
export { type Frontmatter, frontmatterSchema };
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { frontmatterSchema } from './chunk-ATB5QJFK.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Frontmatter } from '../schemas.js';
|
|
2
|
+
import 'zod';
|
|
3
|
+
|
|
4
|
+
interface BlogPost {
|
|
5
|
+
slug: string;
|
|
6
|
+
frontmatter: Frontmatter;
|
|
7
|
+
content: string;
|
|
8
|
+
readingTime: string;
|
|
9
|
+
excerpt: string;
|
|
10
|
+
}
|
|
11
|
+
interface BlogConfig {
|
|
12
|
+
contentDir?: string;
|
|
13
|
+
excerptLength?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare function getAllPosts(config?: BlogConfig): BlogPost[];
|
|
17
|
+
declare function getPostBySlug(slug: string, config?: BlogConfig): BlogPost | null;
|
|
18
|
+
declare function generateRSSFeed(posts: BlogPost[], siteUrl: string, title?: string): string;
|
|
19
|
+
|
|
20
|
+
export { type BlogConfig as B, type BlogPost as a, generateRSSFeed, getAllPosts, getPostBySlug };
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mars-stack/blog",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Blog module for MARS applications — MDX content, frontmatter validation, RSS generation",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/greaveselliott/mars.git",
|
|
9
|
+
"directory": "packages/blog"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./server": {
|
|
18
|
+
"types": "./dist/server/index.d.ts",
|
|
19
|
+
"import": "./dist/server/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./schemas": {
|
|
22
|
+
"types": "./dist/schemas.d.ts",
|
|
23
|
+
"import": "./dist/schemas.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "restricted"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsup --watch",
|
|
35
|
+
"test": "vitest run --passWithNoTests",
|
|
36
|
+
"prepublishOnly": "yarn build"
|
|
37
|
+
},
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"gray-matter": "^4.0.3",
|
|
41
|
+
"reading-time": "^1.5.0",
|
|
42
|
+
"zod": "^4.3.6"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"react": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"react": "^19.0.0",
|
|
54
|
+
"tsup": "^8.0.0",
|
|
55
|
+
"typescript": "^5.7.0",
|
|
56
|
+
"vitest": "^3.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|