1+ import fs from 'fs/promises' ;
2+ import path from 'path' ;
3+ import { createMarkdownExit } from 'markdown-exit' ;
4+ import Shiki from '@shikijs/markdown-exit' ;
5+ import matter from 'gray-matter' ;
6+
7+ const CONTENT_DIR = './content' ;
8+ const DIST_DIR = './dist' ;
9+ const TEMPLATE_PATH = './src/template.html' ;
10+
11+ async function build ( ) {
12+ // 1. Initialize markdown-exit using the factory helper
13+ const md = createMarkdownExit ( ) ;
14+
15+ // 2. Add Shiki plugin for syntax highlighting
16+ // This will automatically handle tokenizing code blocks asynchronously
17+ md . use ( Shiki ( {
18+ themes : {
19+ light : 'github-light' ,
20+ dark : 'github-dark' ,
21+ }
22+ } ) ) ;
23+
24+ // 3. Prepare directories and load the HTML template
25+ await fs . mkdir ( DIST_DIR , { recursive : true } ) ;
26+ const template = await fs . readFile ( TEMPLATE_PATH , 'utf-8' ) ;
27+
28+ // Copy over the global CSS file
29+ await fs . copyFile ( './src/style.css' , path . join ( DIST_DIR , 'style.css' ) ) ;
30+
31+ // 4. Recursive function to read all markdown files and mirror the folder structure
32+ async function processDirectory ( dir : string ) {
33+ const entries = await fs . readdir ( dir , { withFileTypes : true } ) ;
34+
35+ for ( const entry of entries ) {
36+ const fullPath = path . join ( dir , entry . name ) ;
37+
38+ // Get the path relative to the content folder (e.g., "dev/my-post.md")
39+ const relativePath = path . relative ( CONTENT_DIR , fullPath ) ;
40+
41+ if ( entry . isDirectory ( ) ) {
42+ // If it's a folder, create the equivalent folder in /dist
43+ const destDir = path . join ( DIST_DIR , relativePath ) ;
44+ await fs . mkdir ( destDir , { recursive : true } ) ;
45+ await processDirectory ( fullPath ) ;
46+ } else if ( entry . name . endsWith ( '.md' ) ) {
47+ // If it's a markdown file, figure out the destination HTML path
48+ const destPath = path . join ( DIST_DIR , relativePath ) . replace ( / \. m d $ / , '.html' ) ;
49+ const fileContent = await fs . readFile ( fullPath , 'utf-8' ) ;
50+
51+ // Parse YAML frontmatter (like title) and the raw markdown body
52+ const { data, content } = matter ( fileContent ) ;
53+ const title = data . title || 'My Blog' ;
54+
55+ // Render markdown asynchronously (required for the Shiki plugin to work)
56+ const htmlContent = await md . renderAsync ( content ) ;
57+
58+ // Calculate correct relative path for the CSS file based on folder depth
59+ const depth = relativePath . split ( path . sep ) . length - 1 ;
60+ const cssPath = depth === 0 ? './style.css' : '../' . repeat ( depth ) + 'style.css' ;
61+
62+ // Inject everything into the template shell
63+ const finalHtml = template
64+ . replace ( '{{TITLE}}' , title )
65+ . replace ( '{{CSS_PATH}}' , cssPath )
66+ . replace ( '{{CONTENT}}' , htmlContent ) ;
67+
68+ // Write the compiled HTML file
69+ await fs . writeFile ( destPath , finalHtml ) ;
70+ console . log ( `Built: ${ destPath } ` ) ;
71+ }
72+ }
73+ }
74+
75+ // Kick off the build process
76+ await processDirectory ( CONTENT_DIR ) ;
77+ console . log ( '✅ Build complete!' ) ;
78+ }
79+
80+ build ( ) . catch ( console . error ) ;
0 commit comments