@jsnchn/buntastic 0.0.2
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/AGENTS.md +96 -0
- package/README.md +222 -0
- package/content/404.md +10 -0
- package/content/about.md +23 -0
- package/content/index.md +33 -0
- package/content/posts/draft.md +13 -0
- package/content/posts/hello.md +42 -0
- package/content/posts/index.md +10 -0
- package/package.json +31 -0
- package/public/style.css +136 -0
- package/src/index.ts +452 -0
- package/src/layouts/base.html +25 -0
- package/src/layouts/page.html +7 -0
- package/src/layouts/post.html +12 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Buntastic
|
|
2
|
+
|
|
3
|
+
A simple static site generator built with Bun.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
buntastic/
|
|
9
|
+
├── content/ # File-based routing source
|
|
10
|
+
│ ├── index.md # → /
|
|
11
|
+
│ ├── about.md # → /about
|
|
12
|
+
│ ├── 404.md # → /404.html
|
|
13
|
+
│ └── posts/
|
|
14
|
+
│ ├── index.md # → /posts (with collection)
|
|
15
|
+
│ └── *.md # → /posts/*
|
|
16
|
+
├── src/
|
|
17
|
+
│ ├── index.ts # Main build script
|
|
18
|
+
│ └── layouts/ # HTML layouts
|
|
19
|
+
│ ├── base.html # Root layout
|
|
20
|
+
│ ├── page.html # extends: base.html
|
|
21
|
+
│ └── post.html # extends: base.html
|
|
22
|
+
├── public/ # Static assets
|
|
23
|
+
└── package.json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bun run build # Production build (excludes drafts)
|
|
30
|
+
bun run build:drafts # Build with drafts included
|
|
31
|
+
bun run dev # Watch mode + dev server
|
|
32
|
+
bun run preview # Serve dist folder
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **Zero dependencies** - Uses only Bun's built-in APIs
|
|
38
|
+
- **File-based routing** - `content/*.md` → `/`, `content/posts/*.md` → `/posts/*`
|
|
39
|
+
- **Layout inheritance** - Layouts can extend other layouts via `extends:` frontmatter
|
|
40
|
+
- **Collections** - `{{ collection }}` variable available in index pages
|
|
41
|
+
- **Draft mode** - `draft: true` in frontmatter, included with `--drafts` flag
|
|
42
|
+
- **404 page** - `content/404.md` automatically becomes `/404.html`
|
|
43
|
+
- **Asset co-location** - Non-markdown files in content/ are copied to dist
|
|
44
|
+
|
|
45
|
+
## Frontmatter
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
---
|
|
49
|
+
title: Page Title
|
|
50
|
+
date: 2024-01-15
|
|
51
|
+
layout: post # Uses layouts/post.html
|
|
52
|
+
description: Meta description
|
|
53
|
+
draft: false # Set true to exclude from production build
|
|
54
|
+
---
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Layout Variables
|
|
58
|
+
|
|
59
|
+
| Variable | Description |
|
|
60
|
+
|----------|-------------|
|
|
61
|
+
| `{{ title }}` | Page title from frontmatter |
|
|
62
|
+
| `{{ content \| safe }}` | Rendered markdown HTML |
|
|
63
|
+
| `{{ date }}` | Page date |
|
|
64
|
+
| `{{ description }}` | Page description |
|
|
65
|
+
| `{{ url }}` | Current page URL |
|
|
66
|
+
| `{{ collection }}` | Array of posts in current folder (for index pages) |
|
|
67
|
+
|
|
68
|
+
## Layout System
|
|
69
|
+
|
|
70
|
+
Layouts support inheritance via frontmatter:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<!-- layouts/post.html -->
|
|
74
|
+
---
|
|
75
|
+
extends: base.html
|
|
76
|
+
---
|
|
77
|
+
<article class="post">
|
|
78
|
+
<h1>{{ title }}</h1>
|
|
79
|
+
{{ content | safe }}
|
|
80
|
+
</article>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The `{{ content | safe }}` placeholder is where child content gets injected.
|
|
84
|
+
|
|
85
|
+
## Development
|
|
86
|
+
|
|
87
|
+
1. Add content to `content/` folder
|
|
88
|
+
2. Run `bun run dev` for development
|
|
89
|
+
3. Run `bun run build` for production
|
|
90
|
+
|
|
91
|
+
## Tech Stack
|
|
92
|
+
|
|
93
|
+
- [Bun](https://bun.sh) - JavaScript runtime
|
|
94
|
+
- Bun.markdown - Built-in GFM markdown parser
|
|
95
|
+
- Bun.serve - HTTP server
|
|
96
|
+
- Bun.watch - File watching
|
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Buntastic
|
|
2
|
+
|
|
3
|
+
A simple static site generator built with Bun.
|
|
4
|
+
|
|
5
|
+
## Why Buntastic?
|
|
6
|
+
|
|
7
|
+
- **Zero dependencies** - Uses only Bun's built-in APIs
|
|
8
|
+
- **Fast** - Powered by Bun's native markdown parser
|
|
9
|
+
- **Simple** - No complex configuration needed
|
|
10
|
+
- **Flexible** - Layouts with inheritance, collections, drafts
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
### As a CLI tool (recommended)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Install Bun (if needed)
|
|
18
|
+
curl -fsSL https://bun.sh/install | bash
|
|
19
|
+
|
|
20
|
+
# Install buntastic globally
|
|
21
|
+
bun install -g buntastic
|
|
22
|
+
|
|
23
|
+
# Or use npx without installing
|
|
24
|
+
npx buntastic build
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### For development
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Clone the repo
|
|
31
|
+
git clone https://github.com/jsnchn/buntastic.git
|
|
32
|
+
cd buntastic
|
|
33
|
+
|
|
34
|
+
# Start the dev server
|
|
35
|
+
bun run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Visit `http://localhost:3000` to see your site.
|
|
39
|
+
|
|
40
|
+
## Project Structure
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
buntastic/
|
|
44
|
+
├── content/ # Your markdown content
|
|
45
|
+
│ ├── index.md # → /
|
|
46
|
+
│ ├── about.md # → /about
|
|
47
|
+
│ └── posts/
|
|
48
|
+
│ └── *.md # → /posts/*
|
|
49
|
+
├── src/
|
|
50
|
+
│ ├── index.ts # Build script
|
|
51
|
+
│ └── layouts/ # HTML templates
|
|
52
|
+
├── public/ # Static assets (CSS, images)
|
|
53
|
+
└── package.json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Commands
|
|
57
|
+
|
|
58
|
+
| Command | Description |
|
|
59
|
+
|---------|-------------|
|
|
60
|
+
| `buntastic build` | Build for production (excludes drafts) |
|
|
61
|
+
| `buntastic build --drafts` | Build with drafts included |
|
|
62
|
+
| `buntastic dev` | Watch mode + dev server |
|
|
63
|
+
| `buntastic preview` | Serve the built `dist/` folder |
|
|
64
|
+
|
|
65
|
+
Or with bun run (if using from source):
|
|
66
|
+
|
|
67
|
+
| Command | Description |
|
|
68
|
+
|---------|-------------|
|
|
69
|
+
| `bun run build` | Build for production (excludes drafts) |
|
|
70
|
+
| `bun run build:drafts` | Build with drafts included |
|
|
71
|
+
| `bun run dev` | Watch mode + dev server |
|
|
72
|
+
| `bun run preview` | Serve the built `dist/` folder |
|
|
73
|
+
|
|
74
|
+
## Writing Content
|
|
75
|
+
|
|
76
|
+
Create markdown files in `content/`:
|
|
77
|
+
|
|
78
|
+
```markdown
|
|
79
|
+
---
|
|
80
|
+
title: My Post
|
|
81
|
+
date: 2024-01-15
|
|
82
|
+
layout: post
|
|
83
|
+
description: A short description
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
# Hello World
|
|
87
|
+
|
|
88
|
+
Your content here...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Frontmatter Options
|
|
92
|
+
|
|
93
|
+
| Field | Description |
|
|
94
|
+
|-------|-------------|
|
|
95
|
+
| `title` | Page title |
|
|
96
|
+
| `date` | Publication date |
|
|
97
|
+
| `layout` | Layout to use (page, post, or custom) |
|
|
98
|
+
| `description` | Meta description |
|
|
99
|
+
| `draft` | Set `true` to exclude from production |
|
|
100
|
+
|
|
101
|
+
### File-Based Routing
|
|
102
|
+
|
|
103
|
+
| Content Path | Output URL |
|
|
104
|
+
|--------------|------------|
|
|
105
|
+
| `content/index.md` | `/` |
|
|
106
|
+
| `content/about.md` | `/about` |
|
|
107
|
+
| `content/posts/hello.md` | `/posts/hello` |
|
|
108
|
+
|
|
109
|
+
## Layouts
|
|
110
|
+
|
|
111
|
+
### Creating Layouts
|
|
112
|
+
|
|
113
|
+
Layouts live in `src/layouts/`. Create HTML files with frontmatter:
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<!-- layouts/post.html -->
|
|
117
|
+
---
|
|
118
|
+
extends: base.html
|
|
119
|
+
---
|
|
120
|
+
<article class="post">
|
|
121
|
+
<h1>{{ title }}</h1>
|
|
122
|
+
<time>{{ date }}</time>
|
|
123
|
+
{{ content | safe }}
|
|
124
|
+
</article>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Layout Variables
|
|
128
|
+
|
|
129
|
+
| Variable | Description |
|
|
130
|
+
|----------|-------------|
|
|
131
|
+
| `{{ title }}` | Page title |
|
|
132
|
+
| `{{ content \| safe }}` | Rendered markdown HTML |
|
|
133
|
+
| `{{ date }}` | Page date |
|
|
134
|
+
| `{{ description }}` | Meta description |
|
|
135
|
+
| `{{ url }}` | Current page URL |
|
|
136
|
+
| `{{ collection }}` | List of posts in current folder (for index pages) |
|
|
137
|
+
|
|
138
|
+
### Layout Inheritance
|
|
139
|
+
|
|
140
|
+
Use `extends:` to build on top of other layouts:
|
|
141
|
+
|
|
142
|
+
```html
|
|
143
|
+
<!-- layouts/base.html -->
|
|
144
|
+
<!DOCTYPE html>
|
|
145
|
+
<html>
|
|
146
|
+
<head>
|
|
147
|
+
<title>{{ title }}</title>
|
|
148
|
+
</head>
|
|
149
|
+
<body>
|
|
150
|
+
<main>{{ content | safe }}</main>
|
|
151
|
+
</body>
|
|
152
|
+
</html>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Collections
|
|
156
|
+
|
|
157
|
+
For folder index pages (e.g., `content/posts/index.md`), use `{{ collection }}` to list all pages in that folder:
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<h1>Blog Posts</h1>
|
|
161
|
+
<ul>
|
|
162
|
+
{{ collection }}
|
|
163
|
+
</ul>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Renders as:
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<li><a href="/posts/hello">Hello World</a> - <time>2024-01-15</time></li>
|
|
170
|
+
<li><a href="/posts/other">Other Post</a></li>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Static Assets
|
|
174
|
+
|
|
175
|
+
Place CSS, images, or other files in `public/`:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
public/
|
|
179
|
+
└── style.css
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
They'll be available at `/style.css` in the built site.
|
|
183
|
+
|
|
184
|
+
You can also co-locate assets with content:
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
content/
|
|
188
|
+
└── posts/
|
|
189
|
+
└── my-post/
|
|
190
|
+
├── index.md
|
|
191
|
+
└── image.png
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The image will be available at `/posts/my-post/image.png`.
|
|
195
|
+
|
|
196
|
+
## 404 Page
|
|
197
|
+
|
|
198
|
+
Create `content/404.md` to have a custom 404 page at `/404.html`.
|
|
199
|
+
|
|
200
|
+
## Drafts
|
|
201
|
+
|
|
202
|
+
Set `draft: true` in frontmatter to exclude a page from production builds:
|
|
203
|
+
|
|
204
|
+
```yaml
|
|
205
|
+
---
|
|
206
|
+
title: Work in Progress
|
|
207
|
+
draft: true
|
|
208
|
+
---
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Drafts are included when running `bun run build:drafts`.
|
|
212
|
+
|
|
213
|
+
## Tech Stack
|
|
214
|
+
|
|
215
|
+
- [Bun](https://bun.sh) - JavaScript runtime
|
|
216
|
+
- Bun.markdown - Built-in GFM markdown parser
|
|
217
|
+
- Bun.serve - HTTP server
|
|
218
|
+
- Bun.watch - File watching
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
MIT
|
package/content/404.md
ADDED
package/content/about.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: About
|
|
3
|
+
description: About BunPress
|
|
4
|
+
layout: page
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# About BunPress
|
|
8
|
+
|
|
9
|
+
BunPress is a minimal static site generator that uses Bun's built-in markdown parser.
|
|
10
|
+
|
|
11
|
+
## Why BunPress?
|
|
12
|
+
|
|
13
|
+
- **Zero dependencies** - Uses only Bun's built-in APIs
|
|
14
|
+
- **Fast** - Powered by Bun's native markdown parser
|
|
15
|
+
- **Simple** - No complex configuration needed
|
|
16
|
+
- **Flexible** - Layouts with inheritance
|
|
17
|
+
|
|
18
|
+
## Tech Stack
|
|
19
|
+
|
|
20
|
+
- [Bun](https://bun.sh) - JavaScript runtime
|
|
21
|
+
- Bun.markdown - Built-in markdown parser
|
|
22
|
+
- Bun.serve - HTTP server
|
|
23
|
+
- Bun.watch - File watching
|
package/content/index.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Welcome
|
|
3
|
+
description: Welcome to BunPress
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Welcome to BunPress
|
|
7
|
+
|
|
8
|
+
This is a simple static site generator built with **Bun**.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- File-based routing
|
|
13
|
+
- Markdown support
|
|
14
|
+
- Layouts with inheritance
|
|
15
|
+
- Collections
|
|
16
|
+
- Draft mode
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
1. Add content to the `content/` folder
|
|
21
|
+
2. Run `bun run build`
|
|
22
|
+
3. View the output in `dist/`
|
|
23
|
+
|
|
24
|
+
## Code Example
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
const greeting = "Hello, BunPress!";
|
|
28
|
+
console.log(greeting);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Try It
|
|
32
|
+
|
|
33
|
+
Check out the [posts](/posts) page to see more!
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Hello World
|
|
3
|
+
date: 2024-01-15
|
|
4
|
+
layout: post
|
|
5
|
+
description: My first blog post
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Hello World
|
|
9
|
+
|
|
10
|
+
This is my first blog post on **BunPress**!
|
|
11
|
+
|
|
12
|
+
## Introduction
|
|
13
|
+
|
|
14
|
+
Static site generators are great for blogs and documentation. BunPress makes it simple.
|
|
15
|
+
|
|
16
|
+
### Features I love:
|
|
17
|
+
|
|
18
|
+
1. Markdown support
|
|
19
|
+
2. File-based routing
|
|
20
|
+
3. Layouts
|
|
21
|
+
4. Collections
|
|
22
|
+
|
|
23
|
+
> "Simplicity is the ultimate sophistication." - Leonardo da Vinci
|
|
24
|
+
|
|
25
|
+
## Code
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
console.log("Hello from BunPress!");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Tables
|
|
32
|
+
|
|
33
|
+
| Feature | Status |
|
|
34
|
+
|---------|--------|
|
|
35
|
+
| Markdown | ✅ |
|
|
36
|
+
| Routing | ✅ |
|
|
37
|
+
| Layouts | ✅ |
|
|
38
|
+
| Collections | ✅ |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
Thanks for reading!
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jsnchn/buntastic",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "A simple static site generator built with Bun",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"buntastic": "src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "buntastic build",
|
|
11
|
+
"build:drafts": "buntastic build --drafts",
|
|
12
|
+
"dev": "buntastic dev",
|
|
13
|
+
"preview": "buntastic preview"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"static-site-generator",
|
|
17
|
+
"ssg",
|
|
18
|
+
"blog",
|
|
19
|
+
"markdown",
|
|
20
|
+
"bun"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/jsnchn/buntastic.git"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"private": false
|
|
31
|
+
}
|
package/public/style.css
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif;
|
|
9
|
+
line-height: 1.6;
|
|
10
|
+
color: #333;
|
|
11
|
+
max-width: 800px;
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
padding: 2rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
header {
|
|
17
|
+
padding: 1rem 0;
|
|
18
|
+
border-bottom: 1px solid #eee;
|
|
19
|
+
margin-bottom: 2rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
nav a {
|
|
23
|
+
margin-right: 1rem;
|
|
24
|
+
color: #0066cc;
|
|
25
|
+
text-decoration: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
nav a:hover {
|
|
29
|
+
text-decoration: underline;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main {
|
|
33
|
+
min-height: 60vh;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
h1, h2, h3, h4, h5, h6 {
|
|
37
|
+
margin: 1.5rem 0 1rem;
|
|
38
|
+
line-height: 1.3;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
h1 { font-size: 2rem; }
|
|
42
|
+
h2 { font-size: 1.5rem; }
|
|
43
|
+
h3 { font-size: 1.25rem; }
|
|
44
|
+
|
|
45
|
+
p {
|
|
46
|
+
margin: 1rem 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
a {
|
|
50
|
+
color: #0066cc;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
code {
|
|
54
|
+
background: #f4f4f4;
|
|
55
|
+
padding: 0.2rem 0.4rem;
|
|
56
|
+
border-radius: 3px;
|
|
57
|
+
font-size: 0.9em;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pre {
|
|
61
|
+
background: #f4f4f4;
|
|
62
|
+
padding: 1rem;
|
|
63
|
+
overflow-x: auto;
|
|
64
|
+
border-radius: 5px;
|
|
65
|
+
margin: 1rem 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pre code {
|
|
69
|
+
background: none;
|
|
70
|
+
padding: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
blockquote {
|
|
74
|
+
border-left: 4px solid #ddd;
|
|
75
|
+
padding-left: 1rem;
|
|
76
|
+
margin: 1rem 0;
|
|
77
|
+
color: #666;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ul, ol {
|
|
81
|
+
margin: 1rem 0;
|
|
82
|
+
padding-left: 2rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
li {
|
|
86
|
+
margin: 0.5rem 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
table {
|
|
90
|
+
width: 100%;
|
|
91
|
+
border-collapse: collapse;
|
|
92
|
+
margin: 1rem 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
th, td {
|
|
96
|
+
border: 1px solid #ddd;
|
|
97
|
+
padding: 0.5rem;
|
|
98
|
+
text-align: left;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
th {
|
|
102
|
+
background: #f4f4f4;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
hr {
|
|
106
|
+
border: none;
|
|
107
|
+
border-top: 1px solid #eee;
|
|
108
|
+
margin: 2rem 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
img {
|
|
112
|
+
max-width: 100%;
|
|
113
|
+
height: auto;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.post header {
|
|
117
|
+
border-bottom: none;
|
|
118
|
+
margin-bottom: 1rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.post time {
|
|
122
|
+
color: #666;
|
|
123
|
+
font-size: 0.9rem;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.page h1 {
|
|
127
|
+
margin-top: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
footer {
|
|
131
|
+
padding: 2rem 0;
|
|
132
|
+
border-top: 1px solid #eee;
|
|
133
|
+
margin-top: 3rem;
|
|
134
|
+
text-align: center;
|
|
135
|
+
color: #666;
|
|
136
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { mkdir, writeFile, readFile, copyFile, exists } from "fs/promises";
|
|
3
|
+
import { join, relative, dirname, extname } from "path";
|
|
4
|
+
import { Glob } from "bun";
|
|
5
|
+
|
|
6
|
+
const CONTENT_DIR = join(process.cwd(), "content");
|
|
7
|
+
const LAYOUTS_DIR = join(process.cwd(), "src/layouts");
|
|
8
|
+
const PUBLIC_DIR = join(process.cwd(), "public");
|
|
9
|
+
const DIST_DIR = join(process.cwd(), "dist");
|
|
10
|
+
|
|
11
|
+
interface Frontmatter {
|
|
12
|
+
title?: string;
|
|
13
|
+
date?: string;
|
|
14
|
+
layout?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
draft?: boolean;
|
|
17
|
+
extends?: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Page {
|
|
22
|
+
url: string;
|
|
23
|
+
filePath: string;
|
|
24
|
+
frontmatter: Frontmatter;
|
|
25
|
+
content: string;
|
|
26
|
+
html: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface CollectionItem {
|
|
30
|
+
url: string;
|
|
31
|
+
title: string;
|
|
32
|
+
date: string;
|
|
33
|
+
description: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseFrontmatter(content: string): { frontmatter: Frontmatter; content: string } {
|
|
37
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
38
|
+
if (!match) {
|
|
39
|
+
return { frontmatter: {}, content };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const yamlStr = match[1];
|
|
43
|
+
const body = match[2];
|
|
44
|
+
|
|
45
|
+
const frontmatter: Frontmatter = {};
|
|
46
|
+
for (const line of yamlStr.split("\n")) {
|
|
47
|
+
const colonIdx = line.indexOf(":");
|
|
48
|
+
if (colonIdx === -1) continue;
|
|
49
|
+
const key = line.slice(0, colonIdx).trim();
|
|
50
|
+
let value: unknown = line.slice(colonIdx + 1).trim();
|
|
51
|
+
if (value === "true") value = true;
|
|
52
|
+
else if (value === "false") value = false;
|
|
53
|
+
frontmatter[key] = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { frontmatter, content: body };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function renderMarkdown(content: string): string {
|
|
60
|
+
return Bun.markdown.html(content, {
|
|
61
|
+
tables: true,
|
|
62
|
+
strikethrough: true,
|
|
63
|
+
tasklists: true,
|
|
64
|
+
autolinks: true,
|
|
65
|
+
headings: true,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function readLayout(layoutName: string): Promise<string> {
|
|
70
|
+
const layoutPath = join(LAYOUTS_DIR, `${layoutName}.html`);
|
|
71
|
+
return await readFile(layoutPath, "utf-8");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function resolveLayout(frontmatter: Frontmatter): Promise<string> {
|
|
75
|
+
const layoutName = frontmatter.layout || frontmatter.extends || "page";
|
|
76
|
+
let template = await readLayout(layoutName);
|
|
77
|
+
|
|
78
|
+
const extendsMatch = template.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
79
|
+
if (extendsMatch) {
|
|
80
|
+
const parentLayout = extendsMatch[1].match(/extends:\s*(\w+)/);
|
|
81
|
+
if (parentLayout) {
|
|
82
|
+
const parentTemplate = await resolveLayout({ extends: parentLayout[1] } as Frontmatter);
|
|
83
|
+
const childContent = extendsMatch[2];
|
|
84
|
+
return parentTemplate.replace(/\{\{\s*content\s*\|\s*safe\s*\}\}/g, childContent);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return template;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function applyTemplate(template: string, page: Page, collection?: CollectionItem[]): string {
|
|
92
|
+
let result = template;
|
|
93
|
+
|
|
94
|
+
result = result.replace(/\{\{\s*title\s*\}\}/g, page.frontmatter.title || "");
|
|
95
|
+
result = result.replace(/\{\{\s*date\s*\}\}/g, page.frontmatter.date || "");
|
|
96
|
+
result = result.replace(/\{\{\s*description\s*\}\}/g, page.frontmatter.description || "");
|
|
97
|
+
result = result.replace(/\{\{\s*url\s*\}\}/g, page.url);
|
|
98
|
+
|
|
99
|
+
if (collection) {
|
|
100
|
+
const collectionHtml = collection
|
|
101
|
+
.map(
|
|
102
|
+
(item) =>
|
|
103
|
+
`<li><a href="${item.url}">${item.title}</a>${item.date ? ` - <time>${item.date}</time>` : ""}</li>`
|
|
104
|
+
)
|
|
105
|
+
.join("\n");
|
|
106
|
+
result = result.replace(/\{\{\s*collection\s*\}\}/g, collectionHtml);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function getCollectionForPath(filePath: string): Promise<CollectionItem[] | undefined> {
|
|
113
|
+
const dir = dirname(filePath);
|
|
114
|
+
if (dir === "." || dir === CONTENT_DIR) return undefined;
|
|
115
|
+
|
|
116
|
+
const contentDir = join(CONTENT_DIR, dir);
|
|
117
|
+
if (!await exists(contentDir)) return undefined;
|
|
118
|
+
|
|
119
|
+
const glob = new Glob("*.md");
|
|
120
|
+
const files = Array.from(glob.scanSync({ cwd: contentDir, absolute: true }));
|
|
121
|
+
|
|
122
|
+
const items: CollectionItem[] = [];
|
|
123
|
+
for (const mdFile of files) {
|
|
124
|
+
if (mdFile.endsWith("/index.md")) continue;
|
|
125
|
+
|
|
126
|
+
const content = await readFile(mdFile, "utf-8");
|
|
127
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
128
|
+
if (frontmatter.draft) continue;
|
|
129
|
+
|
|
130
|
+
const relativePath = relative(CONTENT_DIR, mdFile).replace(/\.md$/, "");
|
|
131
|
+
const url = relativePath === "index" ? "/" : `/${relativePath}`;
|
|
132
|
+
|
|
133
|
+
items.push({
|
|
134
|
+
url,
|
|
135
|
+
title: (frontmatter.title as string) || "Untitled",
|
|
136
|
+
date: (frontmatter.date as string) || "",
|
|
137
|
+
description: (frontmatter.description as string) || "",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
items.sort((a, b) => (b.date || "").localeCompare(a.date || ""));
|
|
142
|
+
|
|
143
|
+
return items;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function buildPage(filePath: string, includeDrafts: boolean): Promise<Page | null> {
|
|
147
|
+
const content = await readFile(filePath, "utf-8");
|
|
148
|
+
const { frontmatter, content: mdContent } = parseFrontmatter(content);
|
|
149
|
+
|
|
150
|
+
if (!includeDrafts && frontmatter.draft) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const html = renderMarkdown(mdContent);
|
|
155
|
+
|
|
156
|
+
const relativePath = relative(CONTENT_DIR, filePath).replace(/\.md$/, "");
|
|
157
|
+
let url = relativePath === "index" ? "/" : `/${relativePath}`;
|
|
158
|
+
if (url.endsWith("/index")) {
|
|
159
|
+
url = url.slice(0, -6) || "/";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
url,
|
|
164
|
+
filePath,
|
|
165
|
+
frontmatter,
|
|
166
|
+
content: mdContent,
|
|
167
|
+
html,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function build(includeDrafts = false): Promise<void> {
|
|
172
|
+
console.log(`Building${includeDrafts ? " (with drafts)" : ""}...`);
|
|
173
|
+
|
|
174
|
+
if (await exists(DIST_DIR)) {
|
|
175
|
+
const { rmSync } = await import("fs");
|
|
176
|
+
rmSync(DIST_DIR, { recursive: true });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await mkdir(DIST_DIR, { recursive: true });
|
|
180
|
+
|
|
181
|
+
const glob = new Glob("**/*.md");
|
|
182
|
+
const files = Array.from(glob.scanSync({ cwd: CONTENT_DIR, absolute: true }));
|
|
183
|
+
|
|
184
|
+
const pages: Page[] = [];
|
|
185
|
+
for (const file of files) {
|
|
186
|
+
const page = await buildPage(file, includeDrafts);
|
|
187
|
+
if (page) {
|
|
188
|
+
pages.push(page);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const page of pages) {
|
|
193
|
+
let template = await resolveLayout(page.frontmatter);
|
|
194
|
+
template = template.replace(/\{\{\s*content\s*\|\s*safe\s*\}\}/g, page.html);
|
|
195
|
+
|
|
196
|
+
let collection: CollectionItem[] | undefined;
|
|
197
|
+
if (page.filePath.includes("/index.md")) {
|
|
198
|
+
const dir = dirname(page.filePath);
|
|
199
|
+
const contentDir = relative(CONTENT_DIR, dir);
|
|
200
|
+
if (contentDir && contentDir !== ".") {
|
|
201
|
+
const allPagesInDir = pages.filter((p) => {
|
|
202
|
+
const pDir = dirname(p.filePath);
|
|
203
|
+
return relative(CONTENT_DIR, pDir) === contentDir && !p.filePath.endsWith("/index.md");
|
|
204
|
+
});
|
|
205
|
+
collection = allPagesInDir
|
|
206
|
+
.map((p) => ({
|
|
207
|
+
url: p.url,
|
|
208
|
+
title: p.frontmatter.title || "Untitled",
|
|
209
|
+
date: p.frontmatter.date || "",
|
|
210
|
+
description: p.frontmatter.description || "",
|
|
211
|
+
}))
|
|
212
|
+
.sort((a, b) => (b.date || "").localeCompare(a.date || ""));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const outputHtml = applyTemplate(template, page, collection);
|
|
217
|
+
|
|
218
|
+
const outputPath = join(DIST_DIR, page.url === "/" ? "index.html" : `${page.url}/index.html`);
|
|
219
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
220
|
+
await writeFile(outputPath, outputHtml);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const assetGlob = new Glob("**/*");
|
|
224
|
+
const assets = Array.from(assetGlob.scanSync({ cwd: CONTENT_DIR, absolute: true }));
|
|
225
|
+
for (const asset of assets) {
|
|
226
|
+
if (asset.endsWith(".md")) continue;
|
|
227
|
+
const relPath = relative(CONTENT_DIR, asset);
|
|
228
|
+
const destPath = join(DIST_DIR, relPath);
|
|
229
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
230
|
+
await copyFile(asset, destPath);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (await exists(PUBLIC_DIR)) {
|
|
234
|
+
const publicFiles = Array.from(assetGlob.scanSync({ cwd: PUBLIC_DIR, absolute: true }));
|
|
235
|
+
for (const file of publicFiles) {
|
|
236
|
+
const relPath = relative(PUBLIC_DIR, file);
|
|
237
|
+
const destPath = join(DIST_DIR, relPath);
|
|
238
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
239
|
+
await copyFile(file, destPath);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (await exists(join(CONTENT_DIR, "404.md"))) {
|
|
244
|
+
const page404 = await buildPage(join(CONTENT_DIR, "404.md"), includeDrafts);
|
|
245
|
+
if (page404) {
|
|
246
|
+
let template = await resolveLayout(page404.frontmatter);
|
|
247
|
+
template = template.replace(/\{\{\s*content\s*\|\s*safe\s*\}\}/g, page404.html);
|
|
248
|
+
const outputHtml = applyTemplate(template, page404);
|
|
249
|
+
await writeFile(join(DIST_DIR, "404.html"), outputHtml);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log(`Built ${pages.length} pages to ${DIST_DIR}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function dev(): Promise<void> {
|
|
257
|
+
console.log("Starting dev server...");
|
|
258
|
+
|
|
259
|
+
const port = 3000;
|
|
260
|
+
const server = Bun.serve({
|
|
261
|
+
port,
|
|
262
|
+
async fetch(req) {
|
|
263
|
+
const url = new URL(req.url);
|
|
264
|
+
let path = url.pathname;
|
|
265
|
+
|
|
266
|
+
if (path === "/") {
|
|
267
|
+
path = "/index";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const filePath = join(DIST_DIR, `${path}.html`);
|
|
271
|
+
const indexPath = join(DIST_DIR, path, "index.html");
|
|
272
|
+
|
|
273
|
+
let file: any;
|
|
274
|
+
if (await exists(filePath)) {
|
|
275
|
+
file = Bun.file(filePath);
|
|
276
|
+
} else if (await exists(indexPath)) {
|
|
277
|
+
file = Bun.file(indexPath);
|
|
278
|
+
} else if (await exists(join(DIST_DIR, "404.html"))) {
|
|
279
|
+
return new Response(Bun.file(join(DIST_DIR, "404.html")), {
|
|
280
|
+
headers: { "Content-Type": "text/html" },
|
|
281
|
+
status: 404,
|
|
282
|
+
});
|
|
283
|
+
} else {
|
|
284
|
+
return new Response("Not Found", { status: 404 });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return new Response(file, {
|
|
288
|
+
headers: { "Content-Type": "text/html" },
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
console.log(`Dev server running at http://localhost:${server.port}`);
|
|
294
|
+
|
|
295
|
+
const watcher = Bun.watch([CONTENT_DIR, LAYOUTS_DIR, PUBLIC_DIR]);
|
|
296
|
+
for await (const event of watcher) {
|
|
297
|
+
console.log(`[${event.kind}] ${event.path} - rebuilding...`);
|
|
298
|
+
await build(false);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function preview(): Promise<void> {
|
|
303
|
+
const port = 3000;
|
|
304
|
+
console.log(`Serving ${DIST_DIR} at http://localhost:${port}`);
|
|
305
|
+
|
|
306
|
+
Bun.serve({
|
|
307
|
+
port,
|
|
308
|
+
async fetch(req) {
|
|
309
|
+
const url = new URL(req.url);
|
|
310
|
+
let path = url.pathname;
|
|
311
|
+
|
|
312
|
+
if (path === "/") {
|
|
313
|
+
path = "/index";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const filePath = join(DIST_DIR, `${path}.html`);
|
|
317
|
+
const indexPath = join(DIST_DIR, path, "index.html");
|
|
318
|
+
const directPath = join(DIST_DIR, path);
|
|
319
|
+
|
|
320
|
+
let file: any;
|
|
321
|
+
if (await exists(filePath)) {
|
|
322
|
+
file = Bun.file(filePath);
|
|
323
|
+
} else if (await exists(indexPath)) {
|
|
324
|
+
file = Bun.file(indexPath);
|
|
325
|
+
} else if (await exists(directPath) && (await import("fs")).statSync(directPath).isDirectory()) {
|
|
326
|
+
file = Bun.file(join(directPath, "index.html"));
|
|
327
|
+
} else if (await exists(join(DIST_DIR, "404.html"))) {
|
|
328
|
+
return new Response(Bun.file(join(DIST_DIR, "404.html")), {
|
|
329
|
+
headers: { "Content-Type": "text/html" },
|
|
330
|
+
status: 404,
|
|
331
|
+
});
|
|
332
|
+
} else {
|
|
333
|
+
return new Response("Not Found", { status: 404 });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return new Response(file);
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function init(): Promise<void> {
|
|
342
|
+
const root = process.cwd();
|
|
343
|
+
|
|
344
|
+
const dirs = ["content/posts", "src/layouts", "public"];
|
|
345
|
+
for (const dir of dirs) {
|
|
346
|
+
await mkdir(join(root, dir), { recursive: true });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const baseLayout = `<!DOCTYPE html>
|
|
350
|
+
<html lang="en">
|
|
351
|
+
<head>
|
|
352
|
+
<meta charset="UTF-8">
|
|
353
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
354
|
+
<title>{{ title }}</title>
|
|
355
|
+
<meta name="description" content="{{ description }}">
|
|
356
|
+
<link rel="stylesheet" href="/style.css">
|
|
357
|
+
</head>
|
|
358
|
+
<body>
|
|
359
|
+
<header>
|
|
360
|
+
<nav>
|
|
361
|
+
<a href="/">Home</a>
|
|
362
|
+
</nav>
|
|
363
|
+
</header>
|
|
364
|
+
<main>
|
|
365
|
+
{{ content | safe }}
|
|
366
|
+
</main>
|
|
367
|
+
<footer>
|
|
368
|
+
<p>Built with BunPress</p>
|
|
369
|
+
</footer>
|
|
370
|
+
</body>
|
|
371
|
+
</html>`;
|
|
372
|
+
|
|
373
|
+
const pageLayout = `---
|
|
374
|
+
extends: base.html
|
|
375
|
+
---
|
|
376
|
+
<article class="page">
|
|
377
|
+
<h1>{{ title }}</h1>
|
|
378
|
+
{{ content | safe }}
|
|
379
|
+
</article>`;
|
|
380
|
+
|
|
381
|
+
const indexMd = `---
|
|
382
|
+
title: Welcome
|
|
383
|
+
description: Welcome to my site
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
# Welcome
|
|
387
|
+
|
|
388
|
+
This is your new BunPress site. Start editing \`content/index.md\` to get started!
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
const packageJson = {
|
|
392
|
+
name: "my-site",
|
|
393
|
+
version: "1.0.0",
|
|
394
|
+
type: "module",
|
|
395
|
+
scripts: {
|
|
396
|
+
build: "buntastic build",
|
|
397
|
+
"build:drafts": "buntastic build --drafts",
|
|
398
|
+
dev: "buntastic dev",
|
|
399
|
+
preview: "buntastic preview",
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const styleCss = `* {
|
|
404
|
+
margin: 0;
|
|
405
|
+
padding: 0;
|
|
406
|
+
box-sizing: border-box;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
body {
|
|
410
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
411
|
+
line-height: 1.6;
|
|
412
|
+
max-width: 800px;
|
|
413
|
+
margin: 0 auto;
|
|
414
|
+
padding: 2rem;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
a { color: #0066cc; }
|
|
418
|
+
h1, h2, h3 { margin: 1.5rem 0 1rem; }
|
|
419
|
+
main { min-height: 60vh; }
|
|
420
|
+
footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid #eee; }
|
|
421
|
+
`;
|
|
422
|
+
|
|
423
|
+
await writeFile(join(root, "src/layouts/base.html"), baseLayout);
|
|
424
|
+
await writeFile(join(root, "src/layouts/page.html"), pageLayout);
|
|
425
|
+
await writeFile(join(root, "content/index.md"), indexMd);
|
|
426
|
+
await writeFile(join(root, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
427
|
+
await writeFile(join(root, "public/style.css"), styleCss);
|
|
428
|
+
|
|
429
|
+
console.log("Initialized BunPress project!");
|
|
430
|
+
console.log("Run 'buntastic dev' to start the dev server.");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const args = process.argv.slice(2);
|
|
434
|
+
const command = args[0];
|
|
435
|
+
|
|
436
|
+
if (command === "init") {
|
|
437
|
+
init();
|
|
438
|
+
} else if (command === "build") {
|
|
439
|
+
const drafts = args.includes("--drafts");
|
|
440
|
+
build(drafts);
|
|
441
|
+
} else if (command === "dev") {
|
|
442
|
+
dev();
|
|
443
|
+
} else if (command === "preview") {
|
|
444
|
+
preview();
|
|
445
|
+
} else {
|
|
446
|
+
console.log("Usage:");
|
|
447
|
+
console.log(" buntastic init - Initialize a new project");
|
|
448
|
+
console.log(" buntastic build - Build for production");
|
|
449
|
+
console.log(" buntastic build --drafts - Build with drafts");
|
|
450
|
+
console.log(" buntastic dev - Development mode");
|
|
451
|
+
console.log(" buntastic preview - Serve dist folder");
|
|
452
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ title }}</title>
|
|
7
|
+
<meta name="description" content="{{ description }}">
|
|
8
|
+
<link rel="stylesheet" href="/style.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<header>
|
|
12
|
+
<nav>
|
|
13
|
+
<a href="/">Home</a>
|
|
14
|
+
<a href="/posts">Posts</a>
|
|
15
|
+
<a href="/about">About</a>
|
|
16
|
+
</nav>
|
|
17
|
+
</header>
|
|
18
|
+
<main>
|
|
19
|
+
{{ content | safe }}
|
|
20
|
+
</main>
|
|
21
|
+
<footer>
|
|
22
|
+
<p>Built with BunPress</p>
|
|
23
|
+
</footer>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|