@bivabdas/sharq 1.0.2 → 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 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 indexHtml = await renderIndexPage(config, posts);
58
- const archiveHtml = await renderArchivePage(config, posts);
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 generateSitemap(config, posts);
63
- console.log(`[sharq] refreshed homepage and sitemap for ${posts.length} post${posts.length === 1 ? "" : "s"}`);
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 === "blog") {
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
- posts.map(async (post) => {
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, posts);
147
- const archiveHtml = await renderArchivePage(outputConfig, posts);
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 generateSitemap(outputConfig, posts);
195
+ await writeDocsPage(outputConfig, docsPage);
196
+ await generateSitemap(outputConfig, regularPosts, docsPage);
152
197
 
153
- console.log(`[sharq] built ${posts.length} post${posts.length === 1 ? "" : "s"} to ${outputDir}`);
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 outputPath = path.join(config.blogOutputDir, `${slug}.html`);
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
- title: escapeHtml(`${post.title} | ${config.siteTitle}`),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivabdas/sharq",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A tiny static publishing framework for landing pages and markdown-driven sites.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "author": "Bivab Das",
30
30
  "license": "MIT",
31
- "homepage": "https://github.com/Bivab0/sharq#readme",
31
+ "homepage": "https://www.sharq.site",
32
32
  "repository": {
33
33
  "type": "git",
34
34
  "url": "git+https://github.com/Bivab0/sharq.git"
@@ -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>{{title}}</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">