@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
- <img src="https://via.placeholder.com/150x150.png?text=Inscribe" alt="Inscribe Logo" width="120" />
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-ssg
34
+ bun install -g @rahuldshetty/inscribe
35
35
 
36
36
  # Using npm
37
- npm install -g inscribe-ssg
37
+ npm install -g @rahuldshetty/inscribe
38
38
  ```
39
39
 
40
40
  ### Usage
@@ -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=/doc/${firstDocSlug}"></head><body>Redirecting...</body></html>`;
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
  }
@@ -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
- return new nunjucks.Environment(
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: {
@@ -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>;
@@ -46,7 +46,33 @@ export function parseFrontMatter(content: string) {
46
46
  }
47
47
  }
48
48
 
49
- export const markdown2HTML = async (content: string, isMDX: boolean = false) => {
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
  }