@rahuldshetty/inscribe 0.0.1

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/cli/index.ts ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env bun
2
+ import { Cli, defineCommand } from "clerc";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { LocalServer } from "./api/server";
6
+ import { build } from "./api/builder";
7
+
8
+ const templateDir = path.resolve(__dirname, "../template")
9
+
10
+ const cli = Cli({
11
+ name: "inscribe",
12
+ scriptName: "inscribe",
13
+ description: "A simple static website generator for blogs and portfolio.",
14
+ version: "0.0.1",
15
+ });
16
+
17
+ const initCommand = defineCommand(
18
+ {
19
+ name: "init",
20
+ description: "Create scaffold for blog/docs",
21
+ },
22
+ async (ctx) => {
23
+ const target = process.cwd();
24
+ // check whether target is empty
25
+ if (fs.readdirSync(target).length > 0) {
26
+ console.log("Target directory is not empty");
27
+ return;
28
+ }
29
+ await fs.copy(templateDir, target, {
30
+ overwrite: false,
31
+ });
32
+ console.log("Docs scaffold created");
33
+ }
34
+ );
35
+
36
+ const devCommand = defineCommand(
37
+ {
38
+ name: "dev",
39
+ description: "Run development server",
40
+ parameters: [
41
+ "[path]",
42
+ ],
43
+ flags: {
44
+ port: {
45
+ type: Number,
46
+ alias: "p",
47
+ default: 3000,
48
+ description: "Port to run development server on",
49
+ },
50
+ },
51
+ },
52
+ async (ctx) => {
53
+ const sourceDir = path.resolve(process.cwd(), ctx.parameters.path || ".");
54
+ const port = ctx.flags.port;
55
+
56
+ if (!fs.existsSync(sourceDir)) {
57
+ console.error(`Error: directory not found ${sourceDir}`);
58
+ return;
59
+ }
60
+
61
+ console.log(`Starting dev server for: ${sourceDir} on port ${port}`);
62
+
63
+ const server = Bun.serve(LocalServer(sourceDir, true, port));
64
+
65
+ // Watch for changes and notify clients via WebSocket
66
+ let debounceTimer: Timer | null = null;
67
+ fs.watch(sourceDir, { recursive: true }, (event, filename) => {
68
+ if (filename && (filename.endsWith(".md") || filename.endsWith(".yaml") || filename.endsWith(".yml") || filename.endsWith(".json"))) {
69
+ if (debounceTimer) clearTimeout(debounceTimer);
70
+ debounceTimer = setTimeout(() => {
71
+ console.log(`File changed: ${filename}. Triggering reload...`);
72
+ server.publish("reload", "reload");
73
+ }, 100);
74
+ }
75
+ });
76
+
77
+ console.log(`Server running at http://localhost:${server.port}`);
78
+ }
79
+ );
80
+
81
+ const buildCommand = defineCommand(
82
+ {
83
+ name: "build",
84
+ description: "Build static website",
85
+ parameters: ["[path]"],
86
+ flags: {
87
+ output: {
88
+ type: String,
89
+ alias: "o",
90
+ default: "./dist",
91
+ description: "Output directory",
92
+ },
93
+ env: {
94
+ type: String,
95
+ default: "release",
96
+ description: "Build environment (dev or release)",
97
+ },
98
+ },
99
+ },
100
+ async (ctx) => {
101
+ const sourceDir = path.resolve(process.cwd(), ctx.parameters.path || ".");
102
+ const outputDir = path.resolve(process.cwd(), ctx.flags.output);
103
+
104
+ console.log(`Building static site from: ${sourceDir}`);
105
+ console.log(`Output directory: ${outputDir}`);
106
+ console.log(`Environment: ${ctx.flags.env}`);
107
+
108
+ try {
109
+ await build({
110
+ sourceDir,
111
+ outputDir,
112
+ env: ctx.flags.env,
113
+ });
114
+ console.log("Build completed successfully!");
115
+ } catch (error: any) {
116
+ console.error(`Build failed: ${error.message}`);
117
+ }
118
+ }
119
+ );
120
+
121
+
122
+ cli.command(initCommand);
123
+ cli.command(devCommand);
124
+ cli.command(buildCommand);
125
+
126
+ // Parse arguments and run!
127
+ cli.parse();
@@ -0,0 +1,23 @@
1
+ import * as z from "zod";
2
+
3
+ export const BlogMetadataSchema = z.object({
4
+ title: z.string(),
5
+ author: z.string(),
6
+ date: z.string(),
7
+ slug: z.string(),
8
+ draft: z.preprocess((val) => (typeof val === "string" ? val.toLowerCase() === "true" : val), z.boolean()).default(true),
9
+ description: z.string().default("").optional(),
10
+ cover: z.string().default("").optional(),
11
+ cover_alt: z.string().default("").optional(),
12
+ tags: z.string().transform(val => val.split(",").map(t => t.trim())).optional(),
13
+ showToc: z.preprocess((val) => (typeof val === "string" ? val.toLowerCase() === "true" : val), z.boolean()).default(false),
14
+ weight: z.number().default(0).optional(),
15
+ });
16
+
17
+ export const BlogScehma = z.object({
18
+ metadata: BlogMetadataSchema,
19
+ markdown: z.string(),
20
+ isMDX: z.boolean().default(false),
21
+ })
22
+
23
+ export type Blog = z.infer<typeof BlogScehma>;
@@ -0,0 +1,8 @@
1
+ import * as z from "zod";
2
+
3
+ export const FolderMetadataSchema = z.object({
4
+ title: z.string().optional(),
5
+ weight: z.number().default(0).optional(),
6
+ });
7
+
8
+ export type FolderMetadata = z.infer<typeof FolderMetadataSchema>;
@@ -0,0 +1,14 @@
1
+ import * as z from "zod";
2
+
3
+ export const InscribeSchema = z.object({
4
+ title: z.string().default('Inscribe').optional(),
5
+ tagline: z.string().default('Inscribe Blog').optional(),
6
+ favicon: z.string().default('favicon.ico').optional(),
7
+ theme: z.string().default('default'),
8
+ show_home: z.preprocess((val) => (typeof val === "string" ? val.toLowerCase() === "true" : val), z.boolean()).default(true),
9
+ blog_path: z.string().default('blog').optional(),
10
+ doc_path: z.string().default('docs').optional(),
11
+ show_doc_nav: z.preprocess((val) => (typeof val === "string" ? val.toLowerCase() === "true" : val), z.boolean()).default(true).optional(),
12
+ })
13
+
14
+ export type InscribeConfig = z.infer<typeof InscribeSchema>;
@@ -0,0 +1,72 @@
1
+ import { marked } from "marked";
2
+ import { parse as yamlParse } from "yaml";
3
+ import { compile, run } from "@mdx-js/mdx";
4
+ import * as jsxRuntime from "preact/jsx-runtime"
5
+ import render from "preact-render-to-string"
6
+ import fs from "fs-extra";
7
+ import path from "path";
8
+ import { FolderMetadataSchema, FolderMetadata } from "../schemas/folder";
9
+
10
+ export const parseFolderMetadata = (dirPath: string): FolderMetadata => {
11
+ const indexPath = path.join(dirPath, "index.md");
12
+ if (fs.existsSync(indexPath)) {
13
+ const content = fs.readFileSync(indexPath, "utf-8");
14
+ const { data } = parseFrontMatter(content);
15
+ const validated = FolderMetadataSchema.safeParse(data);
16
+ if (validated.success) {
17
+ return validated.data;
18
+ }
19
+ }
20
+ return { title: path.basename(dirPath), weight: 0 };
21
+ };
22
+
23
+ export function parseFrontMatter(content: string) {
24
+ const regex = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?([\s\S]*)$/;
25
+ const match = content.match(regex);
26
+ if (!match) return { data: {}, body: content };
27
+
28
+ const yamlStr = match[1];
29
+ const body = match[2];
30
+
31
+ try {
32
+ const data = yamlParse(yamlStr);
33
+ return { data: data || {}, body };
34
+ } catch (e) {
35
+ // Fallback to manual parsing for "invalid" YAML with nested quotes
36
+ const data: Record<string, any> = {};
37
+ yamlStr.split(/\r?\n/).forEach(line => {
38
+ const index = line.indexOf(":");
39
+ if (index !== -1) {
40
+ const key = line.slice(0, index).trim();
41
+ const value = line.slice(index + 1).trim().replace(/^["']|["']$/g, "");
42
+ if (key) data[key] = value;
43
+ }
44
+ });
45
+ return { data, body };
46
+ }
47
+ }
48
+
49
+ export const markdown2HTML = async (content: string, isMDX: boolean = false) => {
50
+ if (isMDX) {
51
+ try {
52
+ // compile MDX -> JS
53
+ const compiled = await compile(content, {
54
+ outputFormat: "function-body",
55
+ development: false
56
+ });
57
+
58
+ // execute compiled module
59
+ const { default: Content } = await run(compiled, jsxRuntime);
60
+
61
+ // render html
62
+ const html = render(Content({}));
63
+
64
+ return html;
65
+ } catch (e) {
66
+ console.error("MDX compilation error:", e);
67
+ return await marked(content);
68
+ }
69
+ }
70
+ const html = await marked(content)
71
+ return html;
72
+ }
@@ -0,0 +1,20 @@
1
+ import { minify } from "html-minifier-terser";
2
+
3
+ /**
4
+ * Robust HTML minifier using html-minifier-terser.
5
+ * @param html The HTML string to minify.
6
+ * @returns The minified HTML string.
7
+ */
8
+ export async function minifyHtml(html: string): Promise<string> {
9
+ return await minify(html, {
10
+ collapseWhitespace: true,
11
+ removeComments: true,
12
+ minifyJS: true,
13
+ minifyCSS: true,
14
+ caseSensitive: true,
15
+ removeRedundantAttributes: true,
16
+ removeScriptTypeAttributes: true,
17
+ removeStyleLinkTypeAttributes: true,
18
+ useShortDoctype: true,
19
+ });
20
+ }