@rahuldshetty/inscribe 0.0.1 → 0.0.3
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/README.md +5 -5
- package/cli/api/builder.ts +36 -3
- package/cli/api/renderer.ts +19 -6
- package/cli/api/server.ts +10 -0
- package/cli/schemas/inscribe.ts +1 -0
- package/cli/utils/markdown.ts +29 -3
- package/dist/index.js +50 -6267
- package/docs/inscribe.png +0 -0
- package/package.json +6 -3
- package/template/inscribe.yaml +1 -0
- package/template/layouts/blog.njk +2 -2
- package/template/layouts/blog_index.njk +2 -2
- package/template/layouts/doc.njk +4 -4
- package/template/layouts/doc_index.njk +5 -5
- package/template/layouts/home.njk +2 -2
- package/template/layouts/partials/header.njk +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="
|
|
3
|
-
<h1>Inscribe</h1>
|
|
2
|
+
<img src="docs/inscribe.png" alt="Inscribe Logo" width="240" />
|
|
3
|
+
<!-- <h1>Inscribe</h1> -->
|
|
4
4
|
<p><strong>A minimalist, high-performance Static Site Generator (SSG)</strong></p>
|
|
5
5
|
<p>
|
|
6
6
|
<img src="https://img.shields.io/badge/status-under%20development-orange" alt="Project Status" />
|
|
@@ -21,8 +21,8 @@ Inscribe is a modern static site generator built with **Bun**, **MDX**, and **Pr
|
|
|
21
21
|
- [x] **CLI** – Simple commands to scaffold, develop, and build your site.
|
|
22
22
|
- [x] **Dev Server** – Local development server with instant live reload.
|
|
23
23
|
- [x] **MDX & Markdown** – Write content using powerful MDX and standard Markdown.
|
|
24
|
+
- [x] **Themes** – Customizable and extensible theme system.
|
|
24
25
|
- [ ] **Search** – Integrated full-text search.
|
|
25
|
-
- [ ] **Themes** – Customizable and extensible theme system.
|
|
26
26
|
- [ ] **Plugins** – Flexible plugin architecture for extending functionality.
|
|
27
27
|
|
|
28
28
|
## 🚀 Quick Start
|
|
@@ -31,10 +31,10 @@ Inscribe is a modern static site generator built with **Bun**, **MDX**, and **Pr
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
# Using Bun (Recommended)
|
|
34
|
-
bun install -g inscribe
|
|
34
|
+
bun install -g @rahuldshetty/inscribe
|
|
35
35
|
|
|
36
36
|
# Using npm
|
|
37
|
-
npm install -g inscribe
|
|
37
|
+
npm install -g @rahuldshetty/inscribe
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
### Usage
|
package/cli/api/builder.ts
CHANGED
|
@@ -14,6 +14,14 @@ export interface BuildOptions {
|
|
|
14
14
|
env: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
const normalizeUrl = (url: string, config: InscribeConfig) => {
|
|
18
|
+
if (url.startsWith('http')) return url;
|
|
19
|
+
let base = config.base_url || '/';
|
|
20
|
+
if (!base.endsWith('/')) base += '/';
|
|
21
|
+
const suffix = url.startsWith('/') ? url.slice(1) : url;
|
|
22
|
+
return base + suffix;
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
const buildSection = async (
|
|
18
26
|
type: 'blog' | 'doc',
|
|
19
27
|
sourceDir: string,
|
|
@@ -102,7 +110,7 @@ export async function build(options: BuildOptions) {
|
|
|
102
110
|
if (isRelease) blogIndex = await minifyHtml(blogIndex);
|
|
103
111
|
await fs.writeFile(path.join(outputDir, "blogs", "index.html"), blogIndex);
|
|
104
112
|
|
|
105
|
-
if (!redirectUrl) redirectUrl = "/blogs/";
|
|
113
|
+
if (!redirectUrl) redirectUrl = normalizeUrl("/blogs/", inscribe);
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
// Build docs
|
|
@@ -120,11 +128,11 @@ export async function build(options: BuildOptions) {
|
|
|
120
128
|
if (docs.length > 0) {
|
|
121
129
|
const firstLevelDoc = docs.find(p => !((p as any).relativePath).includes('/') && !((p as any).relativePath).includes('\\'));
|
|
122
130
|
const firstDocSlug = (firstLevelDoc || docs[0]).metadata.slug;
|
|
123
|
-
const redirectHtml = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url
|
|
131
|
+
const redirectHtml = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${normalizeUrl(`/doc/${firstDocSlug}`, inscribe)}"></head><body>Redirecting...</body></html>`;
|
|
124
132
|
await fs.writeFile(path.join(outputDir, "docs", "index.html"), redirectHtml);
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
if (!redirectUrl) redirectUrl = "/docs/";
|
|
135
|
+
if (!redirectUrl) redirectUrl = normalizeUrl("/docs/", inscribe);
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
// Generate index.html
|
|
@@ -142,4 +150,29 @@ export async function build(options: BuildOptions) {
|
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
await fs.writeFile(path.join(outputDir, "index.html"), indexPage);
|
|
153
|
+
|
|
154
|
+
// Copy static assets
|
|
155
|
+
console.log("Copying static assets...");
|
|
156
|
+
const excludedExtensions = [".md", ".mdx", ".njk", ".yaml", ".yml"];
|
|
157
|
+
const excludedDirs = ["layouts", ".git", "node_modules"];
|
|
158
|
+
|
|
159
|
+
const copyRecursive = async (src: string, dest: string) => {
|
|
160
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
const srcPath = path.join(src, entry.name);
|
|
163
|
+
const destPath = path.join(dest, entry.name);
|
|
164
|
+
|
|
165
|
+
if (entry.isDirectory()) {
|
|
166
|
+
if (excludedDirs.includes(entry.name)) continue;
|
|
167
|
+
await fs.ensureDir(destPath);
|
|
168
|
+
await copyRecursive(srcPath, destPath);
|
|
169
|
+
} else {
|
|
170
|
+
if (excludedExtensions.includes(path.extname(entry.name))) continue;
|
|
171
|
+
if (entry.name === "inscribe.yaml" || entry.name === "inscribe.yml") continue;
|
|
172
|
+
await fs.copy(srcPath, destPath);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
await copyRecursive(sourceDir, outputDir);
|
|
145
178
|
}
|
package/cli/api/renderer.ts
CHANGED
|
@@ -26,16 +26,29 @@ export const parseBlogPost = async (filePath: string) => {
|
|
|
26
26
|
* 1. User project layouts
|
|
27
27
|
* 2. CLI built-in layouts
|
|
28
28
|
*/
|
|
29
|
-
const getRenderer = (sourceDir: string) => {
|
|
29
|
+
const getRenderer = (sourceDir: string, config: InscribeConfig) => {
|
|
30
30
|
const userLayouts = path.resolve(sourceDir, "layouts");
|
|
31
31
|
const builtInLayouts = path.resolve(__dirname, "../../template/layouts");
|
|
32
32
|
|
|
33
33
|
const searchPaths = [userLayouts, builtInLayouts];
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const env = new nunjucks.Environment(
|
|
36
36
|
new nunjucks.FileSystemLoader(searchPaths),
|
|
37
37
|
{ autoescape: true }
|
|
38
38
|
);
|
|
39
|
+
|
|
40
|
+
env.addFilter('url', (urlPath: string) => {
|
|
41
|
+
if (!urlPath) return urlPath;
|
|
42
|
+
if (urlPath.startsWith('http') || urlPath.startsWith('//') || urlPath.startsWith('data:')) return urlPath;
|
|
43
|
+
|
|
44
|
+
let base = config.base_url || '/';
|
|
45
|
+
if (!base.endsWith('/')) base += '/';
|
|
46
|
+
|
|
47
|
+
const pathSuffix = urlPath.startsWith('/') ? urlPath.slice(1) : urlPath;
|
|
48
|
+
return base + pathSuffix;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return env;
|
|
39
52
|
};
|
|
40
53
|
|
|
41
54
|
import { FolderMetadata } from "../schemas/folder";
|
|
@@ -127,8 +140,8 @@ export const renderSectionPage = async (
|
|
|
127
140
|
navState: NavState,
|
|
128
141
|
isDev: boolean = false
|
|
129
142
|
) => {
|
|
130
|
-
const html = await markdown2HTML(post.markdown, post.isMDX);
|
|
131
|
-
const env = getRenderer(sourceDir);
|
|
143
|
+
const html = await markdown2HTML(post.markdown, post.isMDX, inscribe);
|
|
144
|
+
const env = getRenderer(sourceDir, inscribe);
|
|
132
145
|
const themeCSS = resolveThemeCSS(inscribe.theme ?? 'default', sourceDir);
|
|
133
146
|
|
|
134
147
|
const template = type === 'blog' ? "blog.njk" : "doc.njk";
|
|
@@ -160,7 +173,7 @@ export const renderSectionIndexPage = (
|
|
|
160
173
|
navState: NavState,
|
|
161
174
|
isDev: boolean = false
|
|
162
175
|
) => {
|
|
163
|
-
const env = getRenderer(sourceDir);
|
|
176
|
+
const env = getRenderer(sourceDir, inscribe);
|
|
164
177
|
const themeCSS = resolveThemeCSS(inscribe.theme ?? 'default', sourceDir);
|
|
165
178
|
|
|
166
179
|
const template = type === 'blog' ? "blog_index.njk" : "doc_index.njk";
|
|
@@ -187,7 +200,7 @@ export const renderHomePage = (
|
|
|
187
200
|
navState: NavState,
|
|
188
201
|
isDev: boolean = false
|
|
189
202
|
) => {
|
|
190
|
-
const env = getRenderer(sourceDir);
|
|
203
|
+
const env = getRenderer(sourceDir, inscribe);
|
|
191
204
|
const themeCSS = resolveThemeCSS(inscribe.theme ?? 'default', sourceDir);
|
|
192
205
|
|
|
193
206
|
return env.render("home.njk", {
|
package/cli/api/server.ts
CHANGED
|
@@ -127,6 +127,16 @@ export const LocalServer = (sourceDir: string, isDev: boolean = false, port = 30
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
// Static files — serve if file exists in sourceDir
|
|
131
|
+
const relativePath = url.pathname.slice(1);
|
|
132
|
+
if (relativePath) {
|
|
133
|
+
const staticFilePath = path.join(sourceDir, relativePath);
|
|
134
|
+
if (fs.existsSync(staticFilePath) && fs.statSync(staticFilePath).isFile()) {
|
|
135
|
+
const file = Bun.file(staticFilePath);
|
|
136
|
+
return new Response(file);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
130
140
|
return new Response("Not Found", { status: 404 });
|
|
131
141
|
},
|
|
132
142
|
websocket: {
|
package/cli/schemas/inscribe.ts
CHANGED
|
@@ -9,6 +9,7 @@ export const InscribeSchema = z.object({
|
|
|
9
9
|
blog_path: z.string().default('blog').optional(),
|
|
10
10
|
doc_path: z.string().default('docs').optional(),
|
|
11
11
|
show_doc_nav: z.preprocess((val) => (typeof val === "string" ? val.toLowerCase() === "true" : val), z.boolean()).default(true).optional(),
|
|
12
|
+
base_url: z.string().default('/').optional(),
|
|
12
13
|
})
|
|
13
14
|
|
|
14
15
|
export type InscribeConfig = z.infer<typeof InscribeSchema>;
|
package/cli/utils/markdown.ts
CHANGED
|
@@ -46,7 +46,33 @@ export function parseFrontMatter(content: string) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
import { InscribeConfig } from "../schemas/inscribe";
|
|
50
|
+
|
|
51
|
+
export const markdown2HTML = async (content: string, isMDX: boolean = false, config?: InscribeConfig) => {
|
|
52
|
+
const base = config?.base_url || '/';
|
|
53
|
+
const normalizedBase = base.endsWith('/') ? base : base + '/';
|
|
54
|
+
|
|
55
|
+
const renderer = new marked.Renderer();
|
|
56
|
+
|
|
57
|
+
// Custom renderer to handle base_url for links and images
|
|
58
|
+
renderer.link = ({ href, title, text }: any) => {
|
|
59
|
+
let finalHref = href;
|
|
60
|
+
if (href && !href.startsWith('http') && !href.startsWith('//') && !href.startsWith('#')) {
|
|
61
|
+
const pathSuffix = href.startsWith('/') ? href.slice(1) : href;
|
|
62
|
+
finalHref = normalizedBase + pathSuffix;
|
|
63
|
+
}
|
|
64
|
+
return `<a href="${finalHref}"${title ? ` title="${title}"` : ""}>${text}</a>`;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
renderer.image = ({ href, title, text }: any) => {
|
|
68
|
+
let finalHref = href;
|
|
69
|
+
if (href && !href.startsWith('http') && !href.startsWith('//') && !href.startsWith('data:')) {
|
|
70
|
+
const pathSuffix = href.startsWith('/') ? href.slice(1) : href;
|
|
71
|
+
finalHref = normalizedBase + pathSuffix;
|
|
72
|
+
}
|
|
73
|
+
return `<img src="${finalHref}" alt="${text || ""}"${title ? ` title="${title}"` : ""}>`;
|
|
74
|
+
};
|
|
75
|
+
|
|
50
76
|
if (isMDX) {
|
|
51
77
|
try {
|
|
52
78
|
// compile MDX -> JS
|
|
@@ -64,9 +90,9 @@ export const markdown2HTML = async (content: string, isMDX: boolean = false) =>
|
|
|
64
90
|
return html;
|
|
65
91
|
} catch (e) {
|
|
66
92
|
console.error("MDX compilation error:", e);
|
|
67
|
-
return await marked(content);
|
|
93
|
+
return await marked(content, { renderer });
|
|
68
94
|
}
|
|
69
95
|
}
|
|
70
|
-
const html = await marked(content)
|
|
96
|
+
const html = await marked(content, { renderer })
|
|
71
97
|
return html;
|
|
72
98
|
}
|