@bivabdas/sharq 1.0.3 → 1.0.4
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/lib/config.js +14 -0
- package/lib/generator.js +59 -13
- package/lib/renderer.js +2 -1
- package/lib/server.js +13 -0
- package/lib/sitemap.js +2 -1
- package/package.json +1 -1
- package/scaffold/template/templates/post.html +1 -1
package/lib/config.js
CHANGED
|
@@ -2,8 +2,18 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
|
|
5
|
+
function normalizeRouteSegment(value, fallback) {
|
|
6
|
+
const normalized = String(value || fallback)
|
|
7
|
+
.trim()
|
|
8
|
+
.replace(/^\/+|\/+$/g, "");
|
|
9
|
+
|
|
10
|
+
return normalized || fallback;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
export function createConfig(rootDir, overrides = {}) {
|
|
6
14
|
const buildDirName = overrides.buildDirName || "sharq";
|
|
15
|
+
const docsRouteSegment = normalizeRouteSegment(overrides.docsRouteSegment, "doc");
|
|
16
|
+
const docsRoutePath = `/${docsRouteSegment}`;
|
|
7
17
|
|
|
8
18
|
return {
|
|
9
19
|
rootDir,
|
|
@@ -12,11 +22,15 @@ export function createConfig(rootDir, overrides = {}) {
|
|
|
12
22
|
siteDescription:
|
|
13
23
|
overrides.siteDescription || "A tiny publishing surface that renders posts from Markdown and serves static HTML.",
|
|
14
24
|
buildDirName,
|
|
25
|
+
docsSourceSlug: overrides.docsSourceSlug || "",
|
|
26
|
+
docsRouteSegment,
|
|
27
|
+
docsRoutePath,
|
|
15
28
|
contentDir: path.join(rootDir, "content"),
|
|
16
29
|
stylesDir: path.join(rootDir, "styles"),
|
|
17
30
|
templatesDir: path.join(rootDir, "templates"),
|
|
18
31
|
publicDir: path.join(rootDir, "public"),
|
|
19
32
|
blogOutputDir: path.join(rootDir, "public", "blog"),
|
|
33
|
+
docsPagePath: path.join(rootDir, "public", docsRouteSegment, "index.html"),
|
|
20
34
|
sitemapPath: path.join(rootDir, "public", "sitemap.xml"),
|
|
21
35
|
homePagePath: path.join(rootDir, "public", "index.html"),
|
|
22
36
|
archivePagePath: path.join(rootDir, "public", "archive.html"),
|
package/lib/generator.js
CHANGED
|
@@ -21,6 +21,37 @@ function assertValidSlug(slug) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function splitPosts(config, posts) {
|
|
25
|
+
if (!config.docsSourceSlug) {
|
|
26
|
+
return { docsPage: null, regularPosts: posts };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const docsPage = posts.find((post) => post.slug === config.docsSourceSlug) || null;
|
|
30
|
+
const regularPosts = posts.filter((post) => post.slug !== config.docsSourceSlug);
|
|
31
|
+
return { docsPage, regularPosts };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function writeDocsPage(config, docsPage) {
|
|
35
|
+
if (!docsPage) {
|
|
36
|
+
await fs.rm(config.docsPagePath, { force: true });
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await fs.mkdir(path.dirname(config.docsPagePath), { recursive: true });
|
|
41
|
+
const html = await renderPostPage(config, docsPage);
|
|
42
|
+
await fs.writeFile(config.docsPagePath, html, "utf8");
|
|
43
|
+
console.log(`[sharq] generated ${config.docsRoutePath}`);
|
|
44
|
+
return { ...docsPage, outputPath: config.docsPagePath, html };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function removeStaleDocsBlogPage(config) {
|
|
48
|
+
if (!config.docsSourceSlug) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await fs.rm(path.join(config.blogOutputDir, `${config.docsSourceSlug}.html`), { force: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
export async function listContentSlugs(config) {
|
|
25
56
|
await ensureDirectories(config);
|
|
26
57
|
const entries = await fs.readdir(config.contentDir, { withFileTypes: true });
|
|
@@ -42,6 +73,14 @@ export async function generatePost(config, slug) {
|
|
|
42
73
|
assertValidSlug(slug);
|
|
43
74
|
|
|
44
75
|
const post = await readPostSource(config, slug);
|
|
76
|
+
|
|
77
|
+
if (config.docsSourceSlug && slug === config.docsSourceSlug) {
|
|
78
|
+
const docsPage = await writeDocsPage(config, post);
|
|
79
|
+
await removeStaleDocsBlogPage(config);
|
|
80
|
+
await refreshSiteArtifacts(config);
|
|
81
|
+
return docsPage;
|
|
82
|
+
}
|
|
83
|
+
|
|
45
84
|
const html = await renderPostPage(config, post);
|
|
46
85
|
const outputPath = path.join(config.blogOutputDir, `${slug}.html`);
|
|
47
86
|
|
|
@@ -54,15 +93,18 @@ export async function generatePost(config, slug) {
|
|
|
54
93
|
|
|
55
94
|
export async function refreshSiteArtifacts(config) {
|
|
56
95
|
const posts = await getAllPosts(config);
|
|
57
|
-
const
|
|
58
|
-
const
|
|
96
|
+
const { docsPage, regularPosts } = splitPosts(config, posts);
|
|
97
|
+
const indexHtml = await renderIndexPage(config, regularPosts);
|
|
98
|
+
const archiveHtml = await renderArchivePage(config, regularPosts);
|
|
59
99
|
|
|
60
100
|
await fs.writeFile(config.homePagePath, indexHtml, "utf8");
|
|
61
101
|
await fs.writeFile(config.archivePagePath, archiveHtml, "utf8");
|
|
62
|
-
await
|
|
63
|
-
|
|
102
|
+
await writeDocsPage(config, docsPage);
|
|
103
|
+
await removeStaleDocsBlogPage(config);
|
|
104
|
+
await generateSitemap(config, regularPosts, docsPage);
|
|
105
|
+
console.log(`[sharq] refreshed homepage and sitemap for ${regularPosts.length} post${regularPosts.length === 1 ? "" : "s"}`);
|
|
64
106
|
|
|
65
|
-
return posts;
|
|
107
|
+
return { posts: regularPosts, docsPage };
|
|
66
108
|
}
|
|
67
109
|
|
|
68
110
|
async function copyDirectory(sourceDir, targetDir) {
|
|
@@ -86,7 +128,7 @@ async function copyStaticFiles(config, outputDir) {
|
|
|
86
128
|
const entries = await fs.readdir(config.publicDir, { withFileTypes: true });
|
|
87
129
|
|
|
88
130
|
for (const entry of entries) {
|
|
89
|
-
if (entry.name
|
|
131
|
+
if (["blog", config.docsRouteSegment].includes(entry.name)) {
|
|
90
132
|
continue;
|
|
91
133
|
}
|
|
92
134
|
|
|
@@ -115,6 +157,7 @@ function createOutputConfig(config, outputDir) {
|
|
|
115
157
|
...config,
|
|
116
158
|
publicDir: outputDir,
|
|
117
159
|
blogOutputDir: path.join(outputDir, "blog"),
|
|
160
|
+
docsPagePath: path.join(outputDir, config.docsRouteSegment, "index.html"),
|
|
118
161
|
sitemapPath: path.join(outputDir, "sitemap.xml"),
|
|
119
162
|
homePagePath: path.join(outputDir, "index.html"),
|
|
120
163
|
archivePagePath: path.join(outputDir, "archive.html"),
|
|
@@ -135,28 +178,31 @@ export async function buildStaticSite(config, outputDir = config.buildDir) {
|
|
|
135
178
|
await copyStaticFiles(config, outputDir);
|
|
136
179
|
|
|
137
180
|
const posts = await getAllPosts(config);
|
|
181
|
+
const { docsPage, regularPosts } = splitPosts(outputConfig, posts);
|
|
138
182
|
await Promise.all(
|
|
139
|
-
|
|
183
|
+
regularPosts.map(async (post) => {
|
|
140
184
|
const postHtml = await renderPostPage(outputConfig, post);
|
|
141
185
|
const outputPath = path.join(outputConfig.blogOutputDir, `${post.slug}.html`);
|
|
142
186
|
await fs.writeFile(outputPath, postHtml, "utf8");
|
|
143
187
|
})
|
|
144
188
|
);
|
|
145
189
|
|
|
146
|
-
const indexHtml = await renderIndexPage(outputConfig,
|
|
147
|
-
const archiveHtml = await renderArchivePage(outputConfig,
|
|
190
|
+
const indexHtml = await renderIndexPage(outputConfig, regularPosts);
|
|
191
|
+
const archiveHtml = await renderArchivePage(outputConfig, regularPosts);
|
|
148
192
|
|
|
149
193
|
await fs.writeFile(outputConfig.homePagePath, indexHtml, "utf8");
|
|
150
194
|
await fs.writeFile(outputConfig.archivePagePath, archiveHtml, "utf8");
|
|
151
|
-
await
|
|
195
|
+
await writeDocsPage(outputConfig, docsPage);
|
|
196
|
+
await generateSitemap(outputConfig, regularPosts, docsPage);
|
|
152
197
|
|
|
153
|
-
console.log(`[sharq] built ${
|
|
154
|
-
return { outputDir, posts };
|
|
198
|
+
console.log(`[sharq] built ${regularPosts.length} post${regularPosts.length === 1 ? "" : "s"} to ${outputDir}`);
|
|
199
|
+
return { outputDir, posts: regularPosts, docsPage };
|
|
155
200
|
}
|
|
156
201
|
|
|
157
202
|
export async function ensurePostGenerated(config, slug) {
|
|
158
203
|
assertValidSlug(slug);
|
|
159
|
-
const
|
|
204
|
+
const isDocsPage = config.docsSourceSlug && slug === config.docsSourceSlug;
|
|
205
|
+
const outputPath = isDocsPage ? config.docsPagePath : path.join(config.blogOutputDir, `${slug}.html`);
|
|
160
206
|
const sourcePath = path.join(config.contentDir, `${slug}.md`);
|
|
161
207
|
|
|
162
208
|
try {
|
package/lib/renderer.js
CHANGED
|
@@ -228,7 +228,8 @@ export async function renderPostPage(config, post) {
|
|
|
228
228
|
.join("");
|
|
229
229
|
|
|
230
230
|
return applyTemplate(template, {
|
|
231
|
-
|
|
231
|
+
pageTitle: escapeHtml(`${post.title} | ${config.siteTitle}`),
|
|
232
|
+
title: escapeHtml(post.title),
|
|
232
233
|
description: escapeHtml(post.description),
|
|
233
234
|
date: escapeHtml(post.date),
|
|
234
235
|
author: escapeHtml(post.author || "sharq"),
|
package/lib/server.js
CHANGED
|
@@ -95,6 +95,13 @@ function createApp(config) {
|
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
if (pathname === config.docsRoutePath || pathname === `${config.docsRoutePath}/`) {
|
|
99
|
+
await refreshSiteArtifacts(config);
|
|
100
|
+
await sendFile(response, config.docsPagePath);
|
|
101
|
+
logRequest(request.method || "GET", pathname, 200);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
98
105
|
if (pathname.startsWith("/blog/")) {
|
|
99
106
|
const slug = pathname.replace("/blog/", "").replace(/\/+$/, "");
|
|
100
107
|
|
|
@@ -103,6 +110,12 @@ function createApp(config) {
|
|
|
103
110
|
return;
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
if (config.docsSourceSlug && slug === config.docsSourceSlug) {
|
|
114
|
+
notFound(response, "Requested content does not exist.");
|
|
115
|
+
logRequest(request.method || "GET", pathname, 404);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
106
119
|
const filePath = await ensurePostGenerated(config, slug);
|
|
107
120
|
await sendFile(response, filePath);
|
|
108
121
|
logRequest(request.method || "GET", pathname, 200);
|
package/lib/sitemap.js
CHANGED
|
@@ -4,10 +4,11 @@ function buildUrl(config, pathname) {
|
|
|
4
4
|
return `${config.siteUrl}${pathname}`;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export async function generateSitemap(config, posts) {
|
|
7
|
+
export async function generateSitemap(config, posts, docsPage = null) {
|
|
8
8
|
const urls = [
|
|
9
9
|
buildUrl(config, "/"),
|
|
10
10
|
buildUrl(config, "/archive"),
|
|
11
|
+
...(docsPage ? [buildUrl(config, config.docsRoutePath)] : []),
|
|
11
12
|
...posts.map((post) => buildUrl(config, `/blog/${post.slug}`))
|
|
12
13
|
];
|
|
13
14
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>{{
|
|
6
|
+
<title>{{pageTitle}}</title>
|
|
7
7
|
<meta name="description" content="{{description}}">
|
|
8
8
|
<link rel="stylesheet" href="/assets/site.css">
|
|
9
9
|
<link rel="stylesheet" href="/assets/overrides.css">
|