bunki 0.21.0 → 0.22.0
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 +142 -1342
- package/dist/cli/commands/validate-media.d.ts +0 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +7130 -12572
- package/dist/cli.js.map +196 -0
- package/dist/config.d.ts +12 -1
- package/dist/constants.d.ts +2 -0
- package/dist/generators/assets.d.ts +3 -3
- package/dist/generators/pages.d.ts +12 -8
- package/dist/index.d.ts +4 -3
- package/dist/index.js +30013 -30
- package/dist/index.js.map +180 -0
- package/dist/parser.d.ts +2 -0
- package/dist/site-generator.d.ts +2 -6
- package/dist/site-model.d.ts +3 -0
- package/dist/types.d.ts +10 -4
- package/dist/utils/build-cache.d.ts +2 -0
- package/dist/utils/concurrency.d.ts +5 -0
- package/dist/utils/css-processor.d.ts +4 -1
- package/dist/utils/date-utils.d.ts +1 -1
- package/dist/utils/file-utils.d.ts +7 -12
- package/dist/utils/markdown/constants.d.ts +5 -5
- package/dist/utils/s3-uploader.d.ts +1 -7
- package/dist/utils/template-engine.d.ts +1 -1
- package/dist/utils/xml-builder.d.ts +1 -0
- package/docs/assets/bunki-banner.svg +17 -0
- package/docs/migration.md +9 -0
- package/package.json +32 -34
- package/dist/fsevents-hj42pnne.node +0 -0
package/README.md
CHANGED
|
@@ -4,1409 +4,209 @@
|
|
|
4
4
|
[](https://coveralls.io/github/kahwee/bunki?branch=main)
|
|
5
5
|
[](https://badge.fury.io/js/bunki)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Bunki turns Markdown into a blog, a collection of notes, or a documentation site. Built with [Bun](https://bun.sh), it pairs editable Nunjucks templates with static HTML you can host wherever you like.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+

|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
[Get started](#quick-start) · [Read the reference](docs/reference.md) · [Migration guide](docs/migration.md) · [Contribute](CONTRIBUTING.md)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
bun
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Requires Bun 1.4.4 or newer. Run `bun upgrade` to update an existing Bun installation.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
```bash
|
|
18
|
+
mkdir my-blog
|
|
19
|
+
cd my-blog
|
|
20
|
+
bun init -y
|
|
21
|
+
bun add bunki
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
bunx bunki init
|
|
24
|
+
bunx bunki new "My First Post" --tags web,notes
|
|
25
|
+
bunx bunki generate
|
|
26
|
+
bunx bunki serve --port 3000
|
|
22
27
|
```
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
Open <http://localhost:3000>. Edit `bunki.config.ts` for your site details, `content/` for posts, and `templates/` for layout. Files in `public/` are copied into the generated site in `dist/`, which you can deploy to a static host.
|
|
25
30
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
bunki
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
```text
|
|
32
|
+
my-blog/
|
|
33
|
+
├── bunki.config.ts Site settings
|
|
34
|
+
├── content/ Your Markdown posts
|
|
35
|
+
├── templates/ Layouts and styles
|
|
36
|
+
├── public/ Files to copy as-is
|
|
37
|
+
└── dist/ Generated site
|
|
31
38
|
```
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
For a global CLI, use `bun add -g bunki` and run `bunki` in place of `bunx bunki`.
|
|
34
41
|
|
|
35
42
|
## Configuration
|
|
36
43
|
|
|
37
|
-
Create `bunki.config.ts` in your project root:
|
|
38
|
-
|
|
39
44
|
```typescript
|
|
40
|
-
import {
|
|
45
|
+
import { defineConfig } from "bunki";
|
|
41
46
|
|
|
42
|
-
export default (
|
|
43
|
-
title: "
|
|
44
|
-
description: "
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
title: "Field Notes",
|
|
49
|
+
description: "Things I build, places I go, and what I learn.",
|
|
45
50
|
baseUrl: "https://example.com",
|
|
46
51
|
domain: "example.com",
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
input: "templates/styles/main.css",
|
|
51
|
-
output: "css/style.css",
|
|
52
|
-
postcssConfig: "postcss.config.js",
|
|
53
|
-
enabled: true,
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
// Optional: Image upload to Cloudflare R2 or S3
|
|
57
|
-
s3: {
|
|
58
|
-
accessKeyId: process.env.R2_ACCESS_KEY_ID || "",
|
|
59
|
-
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || "",
|
|
60
|
-
bucket: process.env.R2_BUCKET || "",
|
|
61
|
-
endpoint: process.env.R2_ENDPOINT,
|
|
62
|
-
region: process.env.R2_REGION || "auto",
|
|
63
|
-
publicUrl: process.env.R2_PUBLIC_URL || "",
|
|
64
|
-
},
|
|
52
|
+
authorName: "Your Name",
|
|
53
|
+
authorEmail: "you@example.com",
|
|
54
|
+
rssLanguage: "en-US",
|
|
65
55
|
});
|
|
66
56
|
```
|
|
67
57
|
|
|
68
|
-
|
|
58
|
+
See [configuration types](src/types.ts) for available options and [configuration defaults](src/config.ts) for loading behavior.
|
|
69
59
|
|
|
70
|
-
|
|
60
|
+
The [reference](docs/reference.md) covers path overrides, template data, validation, and cache limitations.
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
content/2025/my-post.md
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Option 2: Directory with README** (Obsidian-friendly)
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
content/2025/my-post/README.md
|
|
82
|
-
content/2025/my-post/image.jpg
|
|
83
|
-
```
|
|
62
|
+
## Write a post
|
|
84
63
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
> [!WARNING]
|
|
88
|
-
> You cannot have both patterns for the same slug. Bunki will throw a validation error if both `content/2025/my-post.md` AND `content/2025/my-post/README.md` exist.
|
|
89
|
-
|
|
90
|
-
Example frontmatter:
|
|
64
|
+
Posts live under `content/`. Use either `content/2026/my-post.md` or `content/2026/my-post/README.md`, but avoid both for the same slug.
|
|
91
65
|
|
|
92
66
|
```markdown
|
|
93
67
|
---
|
|
94
|
-
title: "
|
|
95
|
-
date:
|
|
96
|
-
tags: [web-development,
|
|
97
|
-
excerpt: "
|
|
68
|
+
title: "A place for small discoveries"
|
|
69
|
+
date: 2026-09-11T09:00:00-07:00
|
|
70
|
+
tags: [web-development, notes]
|
|
71
|
+
excerpt: "Notes worth keeping, one post at a time."
|
|
98
72
|
---
|
|
99
73
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Your content here with **markdown** support.
|
|
103
|
-
|
|
104
|
-

|
|
74
|
+
Today I started keeping a few **field notes**.
|
|
105
75
|
|
|
106
|
-
|
|
107
|
-
<source src="video.mp4" type="video/mp4">
|
|
108
|
-
Your browser does not support HTML5 video.
|
|
109
|
-
</video>
|
|
76
|
+

|
|
110
77
|
```
|
|
111
78
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
> [!IMPORTANT]
|
|
115
|
-
> Tags must use hyphens instead of spaces: `web-development` NOT `"web development"`
|
|
116
|
-
|
|
117
|
-
Tags with spaces will fail validation. Use hyphenated slugs:
|
|
118
|
-
|
|
119
|
-
- ✅ `tags: [new-york-city, travel, family-friendly]`
|
|
120
|
-
- ❌ `tags: ["new york city", "travel", "family friendly"]`
|
|
79
|
+
Saved as `my-post.md`, the example generates `/2026/my-post/`. The URL year comes from the frontmatter date in Pacific time, not the directory name. Use timezone-aware dates and hyphenated tag slugs such as `web-development`.
|
|
121
80
|
|
|
122
|
-
Optional
|
|
81
|
+
Relative Markdown links such as `../2025/earlier-post.md` become site links such as `/2025/earlier-post/`. Optional tag descriptions go in `src/tags.toml`:
|
|
123
82
|
|
|
124
83
|
```toml
|
|
125
|
-
|
|
126
|
-
web-development = "Web development and technology"
|
|
127
|
-
new-york-city = "New York City travel guides"
|
|
84
|
+
web-development = "Notes on building for the web"
|
|
128
85
|
```
|
|
129
86
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
Bunki automatically converts relative markdown links to absolute URLs during build time. This lets you write cross-references using familiar file paths:
|
|
133
|
-
|
|
134
|
-
**In your markdown:**
|
|
135
|
-
|
|
136
|
-
```markdown
|
|
137
|
-
Check out [my earlier post](../2023/introduction.md) for context.
|
|
138
|
-
|
|
139
|
-
See also [related article](../../2020/old-post.md).
|
|
140
|
-
```
|
|
87
|
+
For business or location metadata, add `business` to frontmatter:
|
|
141
88
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
```html
|
|
145
|
-
<a href="/2023/introduction/">my earlier post</a>
|
|
146
|
-
<a href="/2020/old-post/">related article</a>
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
This feature works with:
|
|
150
|
-
|
|
151
|
-
- `../YEAR/slug.md` - Single level up
|
|
152
|
-
- `../../YEAR/slug.md` - Multiple levels up
|
|
153
|
-
- Any number of `../` sequences
|
|
154
|
-
|
|
155
|
-
The links are automatically converted to absolute URLs (`/YEAR/slug/`) that match your site's URL structure.
|
|
156
|
-
|
|
157
|
-
### Business Location Data
|
|
158
|
-
|
|
159
|
-
Add structured business/location data with automatic validation:
|
|
160
|
-
|
|
161
|
-
```markdown
|
|
162
|
-
---
|
|
163
|
-
title: "Restaurant Review"
|
|
164
|
-
date: 2025-01-15T09:00:00-07:00
|
|
165
|
-
tags: [food, review]
|
|
89
|
+
```yaml
|
|
166
90
|
business:
|
|
167
91
|
- type: Restaurant
|
|
168
|
-
name: "
|
|
169
|
-
address: "123 Main St, San Francisco, CA
|
|
92
|
+
name: "Example Cafe"
|
|
93
|
+
address: "123 Main St, San Francisco, CA"
|
|
170
94
|
lat: 37.7749
|
|
171
95
|
lng: -122.4194
|
|
172
|
-
---
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
**Required fields:** `type`, `name`, `lat`, `lng`
|
|
176
|
-
**Optional fields:** `address`, `cuisine`, `priceRange`, `telephone`, `url`, `openingHours`
|
|
177
|
-
|
|
178
|
-
The validator enforces:
|
|
179
|
-
|
|
180
|
-
- Use `business:` (not deprecated `location:`)
|
|
181
|
-
- Use `lat:`/`lng:` (not deprecated `latitude:`/`longitude:`)
|
|
182
|
-
- All required fields must be present
|
|
183
|
-
|
|
184
|
-
Validation runs automatically during `bunki generate` and `bunki validate`.
|
|
185
|
-
|
|
186
|
-
## CSS & Tailwind
|
|
187
|
-
|
|
188
|
-
To use Tailwind CSS:
|
|
189
|
-
|
|
190
|
-
```bash
|
|
191
|
-
bun add -D tailwindcss @tailwindcss/postcss @tailwindcss/typography
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Create `postcss.config.js`:
|
|
195
|
-
|
|
196
|
-
```javascript
|
|
197
|
-
module.exports = {
|
|
198
|
-
plugins: [require("@tailwindcss/postcss")],
|
|
199
|
-
};
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
Create `templates/styles/main.css`:
|
|
203
|
-
|
|
204
|
-
```css
|
|
205
|
-
@tailwind base;
|
|
206
|
-
@tailwind components;
|
|
207
|
-
@tailwind utilities;
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
CSS is processed automatically during `bunki generate`.
|
|
211
|
-
|
|
212
|
-
## JSON-LD Structured Data for SEO
|
|
213
|
-
|
|
214
|
-
Bunki automatically generates [JSON-LD](https://json-ld.org/) structured data markup for enhanced SEO and search engine visibility. JSON-LD (JavaScript Object Notation for Linked Data) is Google's recommended format for structured data.
|
|
215
|
-
|
|
216
|
-
### What is JSON-LD?
|
|
217
|
-
|
|
218
|
-
JSON-LD helps search engines better understand your content by providing explicit, structured information about your pages. This can lead to:
|
|
219
|
-
|
|
220
|
-
- **Rich snippets** in search results (article previews, star ratings, etc.)
|
|
221
|
-
- **Better content indexing** and understanding by search engines
|
|
222
|
-
- **Improved click-through rates** from search results
|
|
223
|
-
- **Knowledge graph integration** with Google, Bing, and other search engines
|
|
224
|
-
|
|
225
|
-
### Automatic Schema Generation
|
|
226
|
-
|
|
227
|
-
Bunki automatically generates appropriate schemas for different page types:
|
|
228
|
-
|
|
229
|
-
#### Blog Posts (BlogPosting Schema)
|
|
230
|
-
|
|
231
|
-
Every blog post includes comprehensive `BlogPosting` schema with:
|
|
232
|
-
|
|
233
|
-
- Headline and description
|
|
234
|
-
- Publication and modification dates
|
|
235
|
-
- Author information
|
|
236
|
-
- Publisher details
|
|
237
|
-
- Article keywords (from tags)
|
|
238
|
-
- Word count
|
|
239
|
-
- Featured image (automatically extracted)
|
|
240
|
-
- Language information
|
|
241
|
-
|
|
242
|
-
Example output in your HTML:
|
|
243
|
-
|
|
244
|
-
```html
|
|
245
|
-
<script type="application/ld+json">
|
|
246
|
-
{
|
|
247
|
-
"@context": "https://schema.org",
|
|
248
|
-
"@type": "BlogPosting",
|
|
249
|
-
"headline": "Getting Started with Bun",
|
|
250
|
-
"description": "Learn how to get started with Bun, the fast JavaScript runtime.",
|
|
251
|
-
"url": "https://example.com/2025/getting-started-with-bun/",
|
|
252
|
-
"datePublished": "2025-01-15T10:30:00.000Z",
|
|
253
|
-
"dateModified": "2025-01-15T10:30:00.000Z",
|
|
254
|
-
"author": {
|
|
255
|
-
"@type": "Person",
|
|
256
|
-
"name": "John Doe",
|
|
257
|
-
"email": "john@example.com"
|
|
258
|
-
},
|
|
259
|
-
"publisher": {
|
|
260
|
-
"@type": "Organization",
|
|
261
|
-
"name": "My Blog",
|
|
262
|
-
"url": "https://example.com"
|
|
263
|
-
},
|
|
264
|
-
"keywords": "bun, javascript, performance",
|
|
265
|
-
"image": "https://example.com/images/bun-logo.png"
|
|
266
|
-
}
|
|
267
|
-
</script>
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
#### Homepage (WebSite & Organization Schemas)
|
|
271
|
-
|
|
272
|
-
The homepage includes dual schemas:
|
|
273
|
-
|
|
274
|
-
1. **WebSite Schema**: Defines the website entity
|
|
275
|
-
2. **Organization Schema**: Defines the publisher/organization
|
|
276
|
-
|
|
277
|
-
```html
|
|
278
|
-
<script type="application/ld+json">
|
|
279
|
-
{
|
|
280
|
-
"@context": "https://schema.org",
|
|
281
|
-
"@type": "WebSite",
|
|
282
|
-
"name": "My Blog",
|
|
283
|
-
"url": "https://example.com",
|
|
284
|
-
"description": "My thoughts and ideas",
|
|
285
|
-
"potentialAction": {
|
|
286
|
-
"@type": "SearchAction",
|
|
287
|
-
"target": {
|
|
288
|
-
"@type": "EntryPoint",
|
|
289
|
-
"urlTemplate": "https://example.com/search?q={search_term_string}"
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
</script>
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
#### Breadcrumbs (BreadcrumbList Schema)
|
|
297
|
-
|
|
298
|
-
All pages include breadcrumb navigation for better site hierarchy understanding:
|
|
299
|
-
|
|
300
|
-
```html
|
|
301
|
-
<script type="application/ld+json">
|
|
302
|
-
{
|
|
303
|
-
"@context": "https://schema.org",
|
|
304
|
-
"@type": "BreadcrumbList",
|
|
305
|
-
"itemListElement": [
|
|
306
|
-
{
|
|
307
|
-
"@type": "ListItem",
|
|
308
|
-
"position": 1,
|
|
309
|
-
"name": "Home",
|
|
310
|
-
"item": "https://example.com"
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
"@type": "ListItem",
|
|
314
|
-
"position": 2,
|
|
315
|
-
"name": "Getting Started with Bun",
|
|
316
|
-
"item": "https://example.com/2025/getting-started-with-bun/"
|
|
317
|
-
}
|
|
318
|
-
]
|
|
319
|
-
}
|
|
320
|
-
</script>
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Configuration for SEO
|
|
324
|
-
|
|
325
|
-
Enhance your JSON-LD output by providing complete author and site information in `bunki.config.ts`:
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
import { SiteConfig } from "bunki";
|
|
329
|
-
|
|
330
|
-
export default (): SiteConfig => ({
|
|
331
|
-
title: "My Blog",
|
|
332
|
-
description: "My thoughts and ideas on web development",
|
|
333
|
-
baseUrl: "https://example.com",
|
|
334
|
-
domain: "example.com",
|
|
335
|
-
|
|
336
|
-
// Author information (used in BlogPosting schema)
|
|
337
|
-
authorName: "John Doe",
|
|
338
|
-
authorEmail: "john@example.com",
|
|
339
|
-
|
|
340
|
-
// RSS/SEO configuration
|
|
341
|
-
rssLanguage: "en-US", // Language code for content
|
|
342
|
-
copyright: "Copyright © 2025 My Blog",
|
|
343
|
-
|
|
344
|
-
// ... other config
|
|
345
|
-
});
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Testing Your JSON-LD
|
|
349
|
-
|
|
350
|
-
You can validate your structured data using these tools:
|
|
351
|
-
|
|
352
|
-
1. **[Google Rich Results Test](https://search.google.com/test/rich-results)** - Test how Google sees your structured data
|
|
353
|
-
2. **[Schema.org Validator](https://validator.schema.org/)** - Validate JSON-LD syntax
|
|
354
|
-
3. **[Structured Data Linter](http://linter.structured-data.org/)** - Check for errors and warnings
|
|
355
|
-
|
|
356
|
-
### Supported Schema Types
|
|
357
|
-
|
|
358
|
-
Bunki currently supports these Schema.org types:
|
|
359
|
-
|
|
360
|
-
- **BlogPosting** - Individual blog posts and articles
|
|
361
|
-
- **WebSite** - Homepage and site-wide metadata
|
|
362
|
-
- **Organization** - Publisher/organization information
|
|
363
|
-
- **Person** - Author information
|
|
364
|
-
- **BreadcrumbList** - Navigation breadcrumbs
|
|
365
|
-
|
|
366
|
-
### How It Works
|
|
367
|
-
|
|
368
|
-
JSON-LD generation is completely automatic:
|
|
369
|
-
|
|
370
|
-
1. **Post Creation**: When you write a post with frontmatter, Bunki extracts metadata
|
|
371
|
-
2. **Site Generation**: During `bunki generate`, appropriate schemas are created
|
|
372
|
-
3. **Template Injection**: JSON-LD scripts are automatically injected into `<head>`
|
|
373
|
-
4. **Image Extraction**: The first image in your post content is automatically used as the featured image
|
|
374
|
-
|
|
375
|
-
No manual configuration needed - just run `bunki generate` and your site will have complete structured data!
|
|
376
|
-
|
|
377
|
-
### Best Practices
|
|
378
|
-
|
|
379
|
-
To maximize SEO benefits:
|
|
380
|
-
|
|
381
|
-
1. **Use descriptive titles** - Your post title becomes the schema headline
|
|
382
|
-
2. **Write good excerpts** - These become schema descriptions
|
|
383
|
-
3. **Include images** - First image in content is used as featured image
|
|
384
|
-
4. **Tag your posts** - Tags become schema keywords
|
|
385
|
-
5. **Set author info** - Complete `authorName` and `authorEmail` in config
|
|
386
|
-
6. **Use ISO 8601 dates** - Format: `2025-01-15T10:30:00-07:00`
|
|
387
|
-
|
|
388
|
-
### Further Reading
|
|
389
|
-
|
|
390
|
-
- [Schema.org Documentation](https://schema.org/)
|
|
391
|
-
- [Google Search Central - Structured Data](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data)
|
|
392
|
-
- [JSON-LD Official Spec](https://json-ld.org/)
|
|
393
|
-
|
|
394
|
-
## Template Fragments
|
|
395
|
-
|
|
396
|
-
Bunki ships reusable Nunjucks macros called **fragments**. They're registered as a second template search path alongside your site's `templates/` directory, so you can import them from any template without any configuration.
|
|
397
|
-
|
|
398
|
-
### Available fragments
|
|
399
|
-
|
|
400
|
-
| File | Macros | Purpose |
|
|
401
|
-
|------|--------|---------|
|
|
402
|
-
| `og-image.njk` | `og_image(post, site)`, `twitter_image(post, site)` | Open Graph and Twitter Card image meta tags |
|
|
403
|
-
| `json-ld.njk` | `blog_posting_schema(post, site)`, `local_business_schema(post, site)` | Schema.org structured data scripts |
|
|
404
|
-
| `share-buttons.njk` | `share_buttons(post, site)` | X / Facebook / LinkedIn / Email share button row (Tailwind) |
|
|
405
|
-
| `pagination.njk` | `pagination_nav(pagination)` | Previous / Next pagination nav (Tailwind) |
|
|
406
|
-
|
|
407
|
-
### Using fragments
|
|
408
|
-
|
|
409
|
-
Import macros at the top of any template with `{% from %}`:
|
|
410
|
-
|
|
411
|
-
```nunjucks
|
|
412
|
-
{# post.njk #}
|
|
413
|
-
{% from "og-image.njk" import og_image, twitter_image %}
|
|
414
|
-
{% from "json-ld.njk" import blog_posting_schema %}
|
|
415
|
-
|
|
416
|
-
{% block og_image %}{{ og_image(post, site) }}{% endblock %}
|
|
417
|
-
{% block twitter_image %}{{ twitter_image(post, site) }}{% endblock %}
|
|
418
|
-
|
|
419
|
-
{% block head %}
|
|
420
|
-
{{ blog_posting_schema(post, site) }}
|
|
421
|
-
{% endblock %}
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
```nunjucks
|
|
425
|
-
{# index.njk, tag.njk, archive.njk #}
|
|
426
|
-
{% from "pagination.njk" import pagination_nav %}
|
|
427
|
-
|
|
428
|
-
{{ pagination_nav(pagination) }}
|
|
429
96
|
```
|
|
430
97
|
|
|
431
|
-
|
|
98
|
+
Use `business`, `lat`, and `lng`; the older `location`, `latitude`, and `longitude` fields fail validation. Check content with `bunx bunki validate` and media with `bunx bunki validate:media`.
|
|
432
99
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
Fragments are standard Nunjucks macro files. You can place your own in your site's `templates/` directory and import them the same way — site templates take priority over built-in fragments if names collide.
|
|
436
|
-
|
|
437
|
-
```nunjucks
|
|
438
|
-
{# templates/my-macros.njk #}
|
|
439
|
-
{% macro breadcrumbs(post, site) %}
|
|
440
|
-
<nav aria-label="Breadcrumb">
|
|
441
|
-
<a href="/">Home</a> /
|
|
442
|
-
<span>{{ post.title }}</span>
|
|
443
|
-
</nav>
|
|
444
|
-
{% endmacro %}
|
|
445
|
-
```
|
|
100
|
+
## Templates and uploads
|
|
446
101
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
{{ breadcrumbs(post, site) }}
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### Template variables reference
|
|
454
|
-
|
|
455
|
-
| Variable | Available in | Description |
|
|
456
|
-
|----------|-------------|-------------|
|
|
457
|
-
| `site` | All templates | Site config: `title`, `description`, `baseUrl`, `author` |
|
|
458
|
-
| `post` | `post.njk` | Post data: `title`, `excerpt`, `html`, `url`, `date`, `tags`, `tagSlugs`, `image`, `business` |
|
|
459
|
-
| `posts` | `index.njk`, `archive.njk` | Array of post objects |
|
|
460
|
-
| `pagination` | `index.njk`, `tag.njk`, `archive.njk` | `currentPage`, `totalPages`, `hasPrevPage`, `hasNextPage`, `prevPage`, `nextPage`, `pagePath` |
|
|
461
|
-
| `tag` | `tag.njk` | Tag object: `name`, `slug`, `description`, `posts` |
|
|
462
|
-
| `tags` | All templates | Array of tag objects: `name`, `slug`, `count`, `description` |
|
|
463
|
-
| `year` | `archive.njk` | Year string, e.g. `"2025"` |
|
|
464
|
-
|
|
465
|
-
## Image Management
|
|
466
|
-
|
|
467
|
-
### Overview
|
|
468
|
-
|
|
469
|
-
The `images:push` command uploads local media (images and videos) to Cloudflare R2, AWS S3, or any S3-compatible storage provider. Media files are organized by year in the `images/` directory and uploaded with their full directory structure preserved.
|
|
470
|
-
|
|
471
|
-
**Supported formats:**
|
|
472
|
-
|
|
473
|
-
- **Images:** JPG, JPEG, PNG, GIF, WebP, SVG
|
|
474
|
-
- **Video:** MP4
|
|
475
|
-
|
|
476
|
-
### Directory Structure
|
|
477
|
-
|
|
478
|
-
Organize images by year and post slug:
|
|
479
|
-
|
|
480
|
-
```
|
|
481
|
-
images/
|
|
482
|
-
├── 2023/
|
|
483
|
-
│ ├── post-slug-1/
|
|
484
|
-
│ │ ├── image-1.jpg
|
|
485
|
-
│ │ └── image-2.png
|
|
486
|
-
│ └── post-slug-2/
|
|
487
|
-
│ └── photo.webp
|
|
488
|
-
├── 2024/
|
|
489
|
-
│ └── travel-guide/
|
|
490
|
-
│ ├── paris-1.jpg
|
|
491
|
-
│ ├── london-2.jpg
|
|
492
|
-
│ ├── tokyo-3.png
|
|
493
|
-
│ └── travel-vlog.mp4
|
|
494
|
-
└── 2025/
|
|
495
|
-
└── new-post/
|
|
496
|
-
├── screenshot.jpg
|
|
497
|
-
└── demo-video.mp4
|
|
498
|
-
```
|
|
102
|
+
Edit Nunjucks templates and CSS for your layout. The [reference](docs/reference.md)
|
|
103
|
+
covers template data, PostCSS, S3-compatible uploads, and their configuration.
|
|
104
|
+
Uploads run separately from site generation.
|
|
499
105
|
|
|
500
|
-
|
|
106
|
+
## Commands
|
|
501
107
|
|
|
502
|
-
|
|
108
|
+
Run `bunx bunki <command> --help` for all options.
|
|
503
109
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
|
|
516
|
-
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
|
|
517
|
-
bucket: process.env.S3_BUCKET || "",
|
|
518
|
-
endpoint: process.env.S3_ENDPOINT, // Optional: for R2, etc.
|
|
519
|
-
region: process.env.S3_REGION || "auto",
|
|
520
|
-
publicUrl: process.env.S3_PUBLIC_URL || "",
|
|
521
|
-
},
|
|
522
|
-
});
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
### Environment Variables
|
|
526
|
-
|
|
527
|
-
Set these in your `.env` file or export them in your shell:
|
|
528
|
-
|
|
529
|
-
```bash
|
|
530
|
-
# Required
|
|
531
|
-
export S3_ACCESS_KEY_ID="your-access-key"
|
|
532
|
-
export S3_SECRET_ACCESS_KEY="your-secret-key"
|
|
533
|
-
export S3_BUCKET="your-bucket-name"
|
|
534
|
-
export S3_PUBLIC_URL="https://cdn.example.com"
|
|
535
|
-
|
|
536
|
-
# Optional (for Cloudflare R2 or custom endpoints)
|
|
537
|
-
export S3_ENDPOINT="https://r2.cloudflarestorage.com"
|
|
538
|
-
export S3_REGION="auto"
|
|
539
|
-
|
|
540
|
-
# Optional (custom domain per bucket)
|
|
541
|
-
export S3_CUSTOM_DOMAIN_YOUR_BUCKET="cdn.example.com"
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### Basic Usage
|
|
545
|
-
|
|
546
|
-
Upload all images:
|
|
547
|
-
|
|
548
|
-
```bash
|
|
549
|
-
bunki images:push
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
This command:
|
|
553
|
-
|
|
554
|
-
1. Scans the `images/` directory recursively
|
|
555
|
-
2. Uploads all supported image formats
|
|
556
|
-
3. Preserves the directory structure (year/slug/filename)
|
|
557
|
-
4. Generates public URLs for each image
|
|
558
|
-
|
|
559
|
-
### Command Options
|
|
560
|
-
|
|
561
|
-
#### `--images <dir>`
|
|
562
|
-
|
|
563
|
-
Specify a custom images directory (default: `./images`)
|
|
564
|
-
|
|
565
|
-
```bash
|
|
566
|
-
bunki images:push --images ./assets/images
|
|
567
|
-
```
|
|
110
|
+
| Command | Purpose |
|
|
111
|
+
| ------------------------------ | --------------------------------------------------- |
|
|
112
|
+
| `init` | Create configuration, templates, and sample content |
|
|
113
|
+
| `new <title> --tags web,notes` | Create a Markdown post |
|
|
114
|
+
| `generate` | Build the site in `dist/` |
|
|
115
|
+
| `generate --incremental` | Reuse cached posts and unchanged CSS |
|
|
116
|
+
| `serve --port 3000` | Preview generated files locally |
|
|
117
|
+
| `css --watch` | Process CSS and watch for changes |
|
|
118
|
+
| `validate` | Check Markdown and frontmatter |
|
|
119
|
+
| `validate:media` | Check media files |
|
|
120
|
+
| `images:push` | Upload media to S3-compatible storage |
|
|
568
121
|
|
|
569
|
-
|
|
122
|
+
`generate` accepts `--config`, `--content`, `--output`, and `--templates` to override paths.
|
|
570
123
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
```bash
|
|
574
|
-
bunki images:push --domain my-blog
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
#### `--output-json <file>`
|
|
578
|
-
|
|
579
|
-
Export a JSON mapping of filenames to their public URLs
|
|
580
|
-
|
|
581
|
-
```bash
|
|
582
|
-
bunki images:push --output-json image-urls.json
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
This creates a JSON file with the structure:
|
|
586
|
-
|
|
587
|
-
```json
|
|
588
|
-
{
|
|
589
|
-
"2023/post-slug/image.jpg": "https://cdn.example.com/2023/post-slug/image.jpg",
|
|
590
|
-
"2024/travel/paris.jpg": "https://cdn.example.com/2024/travel/paris.jpg"
|
|
591
|
-
}
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
#### `--min-year <year>`
|
|
595
|
-
|
|
596
|
-
Upload only images from the specified year onwards
|
|
597
|
-
|
|
598
|
-
```bash
|
|
599
|
-
# Upload only 2023 and 2024 images (skip 2021, 2022)
|
|
600
|
-
bunki images:push --min-year 2023
|
|
601
|
-
|
|
602
|
-
# Upload only 2024 and newer images
|
|
603
|
-
bunki images:push --min-year 2024
|
|
604
|
-
|
|
605
|
-
# Upload from 2022 onwards (all images in this example)
|
|
606
|
-
bunki images:push --min-year 2022
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
This is useful for:
|
|
610
|
-
|
|
611
|
-
- Incremental uploads (upload only new images)
|
|
612
|
-
- Testing uploads for specific years
|
|
613
|
-
- Managing large image collections across multiple uploads
|
|
614
|
-
|
|
615
|
-
#### `--content-assets`
|
|
616
|
-
|
|
617
|
-
Upload images that are co-located with markdown files instead of the top-level `assets/` directory.
|
|
618
|
-
|
|
619
|
-
When content is organized with images living alongside the markdown:
|
|
620
|
-
|
|
621
|
-
```
|
|
622
|
-
content/
|
|
623
|
-
├── 2024/
|
|
624
|
-
│ └── _assets/
|
|
625
|
-
│ ├── paris-cafe.webp
|
|
626
|
-
│ └── eiffel-tower.webp
|
|
627
|
-
└── 2025/
|
|
628
|
-
└── _assets/
|
|
629
|
-
└── tokyo-ramen.webp
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
Run:
|
|
633
|
-
|
|
634
|
-
```bash
|
|
635
|
-
bunki images:push --content-assets
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
This uploads each file with the S3 key `{year}/{filename}` — the `_assets/` segment is stripped. For example, `content/2024/_assets/paris-cafe.webp` becomes key `2024/paris-cafe.webp`, accessible at `https://cdn.example.com/2024/paris-cafe.webp`.
|
|
639
|
-
|
|
640
|
-
> [!IMPORTANT]
|
|
641
|
-
> Always use CDN URLs in your markdown, not relative `_assets/` paths. Relative paths cause the image files to be bundled into your Cloudflare Workers deployment instead of served from R2.
|
|
642
|
-
|
|
643
|
-
```markdown
|
|
644
|
-
<!-- ❌ Causes image to be bundled into Workers -->
|
|
645
|
-

|
|
646
|
-
|
|
647
|
-
<!-- ✅ Served from R2 CDN -->
|
|
648
|
-

|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
#### `--content-assets-dir <dir>`
|
|
652
|
-
|
|
653
|
-
Override the assets subdirectory name. Defaults to `_assets` (or `contentAssets.assetsDir` in `bunki.config.ts`).
|
|
654
|
-
|
|
655
|
-
```bash
|
|
656
|
-
# Use _images instead of _assets
|
|
657
|
-
bunki images:push --content-assets --content-assets-dir _images
|
|
658
|
-
|
|
659
|
-
# Use any custom name
|
|
660
|
-
bunki images:push --content-assets --content-assets-dir media
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
### Complete Examples
|
|
664
|
-
|
|
665
|
-
#### Cloudflare R2 Setup
|
|
666
|
-
|
|
667
|
-
1. **Create R2 bucket and API token** in Cloudflare dashboard
|
|
668
|
-
|
|
669
|
-
2. **Set environment variables:**
|
|
670
|
-
|
|
671
|
-
```bash
|
|
672
|
-
export S3_ACCESS_KEY_ID="your-r2-api-token-id"
|
|
673
|
-
export S3_SECRET_ACCESS_KEY="your-r2-api-token-secret"
|
|
674
|
-
export S3_BUCKET="my-blog-images"
|
|
675
|
-
export S3_ENDPOINT="https://r2.cloudflarestorage.com"
|
|
676
|
-
export S3_REGION="auto"
|
|
677
|
-
export S3_PUBLIC_URL="https://cdn.example.com"
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
3. **Upload images:**
|
|
681
|
-
|
|
682
|
-
```bash
|
|
683
|
-
bunki images:push --output-json image-urls.json
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
#### AWS S3 Setup
|
|
687
|
-
|
|
688
|
-
1. **Create S3 bucket and IAM user** in AWS Console
|
|
689
|
-
|
|
690
|
-
2. **Set environment variables:**
|
|
691
|
-
|
|
692
|
-
```bash
|
|
693
|
-
export S3_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
|
|
694
|
-
export S3_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
695
|
-
export S3_BUCKET="my-blog-bucket"
|
|
696
|
-
export S3_REGION="us-east-1"
|
|
697
|
-
export S3_PUBLIC_URL="https://my-blog-bucket.s3.amazonaws.com"
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
3. **Upload images:**
|
|
701
|
-
|
|
702
|
-
```bash
|
|
703
|
-
bunki images:push
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
#### Incremental Upload (Year-Based)
|
|
707
|
-
|
|
708
|
-
If you have thousands of images and want to upload them incrementally:
|
|
709
|
-
|
|
710
|
-
```bash
|
|
711
|
-
# First, upload all 2023 images
|
|
712
|
-
bunki images:push --min-year 2023 --max-year 2023
|
|
713
|
-
|
|
714
|
-
# Next, upload 2024 images
|
|
715
|
-
bunki images:push --min-year 2024 --max-year 2024
|
|
716
|
-
|
|
717
|
-
# Finally, upload 2025 images
|
|
718
|
-
bunki images:push --min-year 2025
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
### Using Uploaded Images in Markdown
|
|
722
|
-
|
|
723
|
-
After uploading, reference images in your Markdown posts:
|
|
724
|
-
|
|
725
|
-
```markdown
|
|
726
|
-
---
|
|
727
|
-
title: "Paris Trip"
|
|
728
|
-
date: 2024-06-15T10:00:00
|
|
729
|
-
tags: [travel, france]
|
|
730
|
-
---
|
|
731
|
-
|
|
732
|
-
# My Trip to Paris
|
|
733
|
-
|
|
734
|
-

|
|
735
|
-
|
|
736
|
-

|
|
737
|
-
|
|
738
|
-
## Evening Stroll
|
|
739
|
-
|
|
740
|
-
The Parisian streets at night are magical.
|
|
741
|
-
|
|
742
|
-

|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
### Using Uploaded Videos in Markdown
|
|
746
|
-
|
|
747
|
-
Upload MP4 videos alongside your images and embed them in your posts:
|
|
748
|
-
|
|
749
|
-
```markdown
|
|
750
|
-
---
|
|
751
|
-
title: "Travel Vlog"
|
|
752
|
-
date: 2024-06-15T10:00:00
|
|
753
|
-
tags: [travel, video]
|
|
754
|
-
---
|
|
755
|
-
|
|
756
|
-
# My Paris Adventure
|
|
757
|
-
|
|
758
|
-
Watch my trip to Paris:
|
|
759
|
-
|
|
760
|
-
<video controls width="640" height="360">
|
|
761
|
-
<source src="https://cdn.example.com/2024/paris-trip/travel-vlog.mp4" type="video/mp4">
|
|
762
|
-
Your browser does not support HTML5 video.
|
|
763
|
-
</video>
|
|
764
|
-
|
|
765
|
-
## Behind the Scenes
|
|
766
|
-
|
|
767
|
-
Check out the making of the vlog:
|
|
768
|
-
|
|
769
|
-
<video controls width="640" height="360">
|
|
770
|
-
<source src="https://cdn.example.com/2024/paris-trip/behind-scenes.mp4" type="video/mp4">
|
|
771
|
-
Your browser does not support HTML5 video.
|
|
772
|
-
</video>
|
|
773
|
-
```
|
|
774
|
-
|
|
775
|
-
**Video Upload Example:**
|
|
776
|
-
|
|
777
|
-
```bash
|
|
778
|
-
# Upload all images and videos (including MP4 files)
|
|
779
|
-
bunki images:push
|
|
780
|
-
|
|
781
|
-
# Upload only 2024 videos and images
|
|
782
|
-
bunki images:push --min-year 2024
|
|
783
|
-
|
|
784
|
-
# Preview what would be uploaded without actually uploading
|
|
785
|
-
BUNKI_DRY_RUN=true bunki images:push --min-year 2024
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
**Video File Organization:**
|
|
789
|
-
|
|
790
|
-
Keep videos organized the same way as images for consistency:
|
|
791
|
-
|
|
792
|
-
```
|
|
793
|
-
images/
|
|
794
|
-
├── 2024/
|
|
795
|
-
│ └── travel-vlog/
|
|
796
|
-
│ ├── intro.mp4
|
|
797
|
-
│ ├── highlights.mp4
|
|
798
|
-
│ ├── thumbnail.jpg
|
|
799
|
-
│ └── poster.jpg
|
|
800
|
-
└── 2025/
|
|
801
|
-
└── tutorial/
|
|
802
|
-
├── part-1.mp4
|
|
803
|
-
├── part-2.mp4
|
|
804
|
-
└── preview.jpg
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
**Video Tips:**
|
|
808
|
-
|
|
809
|
-
1. **File Size**: Keep MP4 files optimized (under 50MB recommended)
|
|
810
|
-
- Use tools like FFmpeg to compress before uploading
|
|
811
|
-
- Example: `ffmpeg -i input.mp4 -crf 28 output.mp4`
|
|
812
|
-
|
|
813
|
-
2. **Format & Codec**:
|
|
814
|
-
- Use H.264 video codec for best compatibility
|
|
815
|
-
- Use AAC audio codec
|
|
816
|
-
- Container: MP4 (.mp4 extension)
|
|
817
|
-
|
|
818
|
-
3. **Video Dimensions**:
|
|
819
|
-
- Keep 16:9 aspect ratio for web
|
|
820
|
-
- Common resolutions: 640x360, 1280x720, 1920x1080
|
|
821
|
-
|
|
822
|
-
4. **Hosting**:
|
|
823
|
-
- MP4s benefit from CDN caching via S3/R2
|
|
824
|
-
- Cloudflare R2 provides excellent video delivery
|
|
825
|
-
- AWS S3 with CloudFront for additional acceleration
|
|
826
|
-
|
|
827
|
-
### Dry Run Mode
|
|
828
|
-
|
|
829
|
-
Test the upload process without actually uploading:
|
|
830
|
-
|
|
831
|
-
```bash
|
|
832
|
-
# Preview what would be uploaded (no actual upload)
|
|
833
|
-
BUNKI_DRY_RUN=true bunki images:push
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
This shows:
|
|
837
|
-
|
|
838
|
-
- Which images would be uploaded
|
|
839
|
-
- The directory structure that would be created
|
|
840
|
-
- Generated public URLs
|
|
841
|
-
|
|
842
|
-
### Troubleshooting
|
|
843
|
-
|
|
844
|
-
#### "Missing S3 configuration"
|
|
845
|
-
|
|
846
|
-
Ensure all required environment variables are set. Check `bunki.config.ts` and your `.env` file.
|
|
847
|
-
|
|
848
|
-
#### "No image files found"
|
|
849
|
-
|
|
850
|
-
- Verify images exist in `images/` directory
|
|
851
|
-
- Check that files have supported extensions (.jpg, .png, .gif, .webp, .svg)
|
|
852
|
-
- Ensure the directory structure is correct (e.g., `images/2024/post-slug/image.jpg`)
|
|
853
|
-
|
|
854
|
-
#### "Unauthorized" or "Access Denied"
|
|
855
|
-
|
|
856
|
-
- Verify S3 credentials (access key and secret key)
|
|
857
|
-
- Check that the IAM user/API token has S3 permissions
|
|
858
|
-
- Confirm the bucket name is correct
|
|
859
|
-
|
|
860
|
-
#### "Invalid bucket name"
|
|
861
|
-
|
|
862
|
-
- S3 bucket names must be globally unique
|
|
863
|
-
- Use only lowercase letters, numbers, and hyphens
|
|
864
|
-
- Bucket names must be 3-63 characters long
|
|
865
|
-
|
|
866
|
-
### Advanced Configuration
|
|
867
|
-
|
|
868
|
-
#### Content Assets Configuration
|
|
869
|
-
|
|
870
|
-
Configure co-located content assets in `bunki.config.ts`:
|
|
871
|
-
|
|
872
|
-
```typescript
|
|
873
|
-
import { SiteConfig } from "bunki";
|
|
874
|
-
|
|
875
|
-
export default (): SiteConfig => ({
|
|
876
|
-
title: "My Blog",
|
|
877
|
-
baseUrl: "https://example.com",
|
|
878
|
-
domain: "example.com",
|
|
879
|
-
|
|
880
|
-
// Default S3 config (used by bunki images:push)
|
|
881
|
-
s3: {
|
|
882
|
-
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
|
|
883
|
-
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
|
|
884
|
-
bucket: "my-site-assets",
|
|
885
|
-
endpoint: process.env.S3_ENDPOINT,
|
|
886
|
-
region: "auto",
|
|
887
|
-
publicUrl: "https://assets.example.com",
|
|
888
|
-
},
|
|
889
|
-
|
|
890
|
-
// Content assets: images stored alongside markdown in content/{year}/_images/
|
|
891
|
-
contentAssets: {
|
|
892
|
-
// Directory name within content/{year}/ (default: "_assets")
|
|
893
|
-
assetsDir: "_images",
|
|
894
|
-
|
|
895
|
-
// Optional: use a separate R2 bucket for content assets
|
|
896
|
-
s3: {
|
|
897
|
-
accessKeyId: process.env.IMG_ACCESS_KEY_ID || "",
|
|
898
|
-
secretAccessKey: process.env.IMG_SECRET_ACCESS_KEY || "",
|
|
899
|
-
bucket: "my-blog-images",
|
|
900
|
-
endpoint: process.env.S3_ENDPOINT,
|
|
901
|
-
region: "auto",
|
|
902
|
-
publicUrl: "https://img.example.com",
|
|
903
|
-
},
|
|
904
|
-
},
|
|
905
|
-
});
|
|
906
|
-
```
|
|
907
|
-
|
|
908
|
-
Then upload content assets:
|
|
909
|
-
|
|
910
|
-
```bash
|
|
911
|
-
# Uses contentAssets.assetsDir and contentAssets.s3 from config
|
|
912
|
-
bunki images:push --content-assets
|
|
913
|
-
|
|
914
|
-
# Override the directory name at the CLI level
|
|
915
|
-
bunki images:push --content-assets --content-assets-dir _media
|
|
916
|
-
```
|
|
917
|
-
|
|
918
|
-
#### Custom Domain per Bucket
|
|
919
|
-
|
|
920
|
-
If you have multiple S3 buckets with different custom domains:
|
|
921
|
-
|
|
922
|
-
```bash
|
|
923
|
-
export S3_CUSTOM_DOMAIN_MY_BUCKET="cdn1.example.com"
|
|
924
|
-
export S3_CUSTOM_DOMAIN_BACKUP_BUCKET="cdn2.example.com"
|
|
925
|
-
```
|
|
926
|
-
|
|
927
|
-
The bucket name is converted to uppercase and hyphens to underscores for the environment variable name.
|
|
928
|
-
|
|
929
|
-
#### Direct CDN URLs
|
|
930
|
-
|
|
931
|
-
Configure public URLs with custom domains:
|
|
932
|
-
|
|
933
|
-
```typescript
|
|
934
|
-
// bunki.config.ts
|
|
935
|
-
s3: {
|
|
936
|
-
// ... other config
|
|
937
|
-
publicUrl: "https://img.example.com",
|
|
938
|
-
}
|
|
939
|
-
```
|
|
940
|
-
|
|
941
|
-
Or via environment variable:
|
|
942
|
-
|
|
943
|
-
```bash
|
|
944
|
-
export S3_PUBLIC_URL="https://img.example.com"
|
|
945
|
-
```
|
|
946
|
-
|
|
947
|
-
### Performance Tips
|
|
948
|
-
|
|
949
|
-
1. **Use year-based filtering** for large image collections:
|
|
950
|
-
|
|
951
|
-
```bash
|
|
952
|
-
bunki images:push --min-year 2024 # Only newest images
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
2. **Organize by post slug** for better directory structure:
|
|
956
|
-
|
|
957
|
-
```
|
|
958
|
-
images/2024/post-title/image.jpg
|
|
959
|
-
images/2024/post-title/photo.jpg
|
|
960
|
-
```
|
|
961
|
-
|
|
962
|
-
3. **Compress images before uploading** to save storage:
|
|
963
|
-
- Use tools like `imagemin` or built-in OS utilities
|
|
964
|
-
- Aim for 500KB or smaller per image
|
|
965
|
-
|
|
966
|
-
4. **Use modern formats** (WebP) for better compression:
|
|
967
|
-
- JPG/PNG for screenshots
|
|
968
|
-
- WebP for photos
|
|
969
|
-
- SVG for icons/graphics
|
|
970
|
-
|
|
971
|
-
## Incremental Builds
|
|
972
|
-
|
|
973
|
-
Bunki supports incremental builds for significantly faster rebuild times during development. When enabled, only changed content is reprocessed while unchanged files are loaded from cache.
|
|
974
|
-
|
|
975
|
-
### Performance Impact
|
|
976
|
-
|
|
977
|
-
**Large site example (455 posts):**
|
|
978
|
-
|
|
979
|
-
- Full build: 3,128ms
|
|
980
|
-
- Incremental build (no changes): 985ms (**3.2x faster**)
|
|
981
|
-
|
|
982
|
-
**Speedup breakdown:**
|
|
983
|
-
|
|
984
|
-
- Markdown parsing: 1,202ms → 55ms (**22x faster**)
|
|
985
|
-
- CSS processing: 1,024ms → 1ms (**1024x faster**)
|
|
986
|
-
- Overall: **68% faster builds**
|
|
987
|
-
|
|
988
|
-
### Usage
|
|
989
|
-
|
|
990
|
-
```bash
|
|
991
|
-
# Enable incremental builds
|
|
992
|
-
bunki generate --incremental
|
|
993
|
-
|
|
994
|
-
# First run (creates cache)
|
|
995
|
-
# Config changed, full rebuild required
|
|
996
|
-
# Total: 3,128ms (same as normal build)
|
|
997
|
-
|
|
998
|
-
# Subsequent runs (no changes)
|
|
999
|
-
# No content changes detected, using cached posts
|
|
1000
|
-
# ✨ Loaded 455 posts from cache (0ms parsing)
|
|
1001
|
-
# ⏭️ Skipping CSS (unchanged)
|
|
1002
|
-
# Total: 985ms (3.2x faster!)
|
|
1003
|
-
|
|
1004
|
-
# When one file changes
|
|
1005
|
-
# 📦 Incremental build: 1/456 files changed (~2730ms saved)
|
|
1006
|
-
# Parsed: 1 new/changed, loaded: 455 from cache
|
|
1007
|
-
# Total: ~1,000ms
|
|
1008
|
-
```
|
|
1009
|
-
|
|
1010
|
-
### How It Works
|
|
1011
|
-
|
|
1012
|
-
1. **First build** creates `.bunki-cache.json` with:
|
|
1013
|
-
- File hashes and modification times
|
|
1014
|
-
- Parsed post data (title, content, metadata)
|
|
1015
|
-
- CSS file checksums
|
|
1016
|
-
- Config file hash
|
|
1017
|
-
|
|
1018
|
-
2. **Subsequent builds** detect changes by comparing:
|
|
1019
|
-
- Config file hash (triggers full rebuild if changed)
|
|
1020
|
-
- Markdown file hashes/mtimes
|
|
1021
|
-
- CSS file hashes
|
|
1022
|
-
|
|
1023
|
-
3. **Selective processing**:
|
|
1024
|
-
- Only parse changed markdown files
|
|
1025
|
-
- Load unchanged posts from cache
|
|
1026
|
-
- Skip CSS if unchanged
|
|
1027
|
-
- Regenerate all pages (currently not selective)
|
|
1028
|
-
|
|
1029
|
-
### Cache Management
|
|
1030
|
-
|
|
1031
|
-
The cache is stored in `.bunki-cache.json` at your project root:
|
|
1032
|
-
|
|
1033
|
-
```bash
|
|
1034
|
-
# View cache status
|
|
1035
|
-
cat .bunki-cache.json | jq '.version, .configHash'
|
|
1036
|
-
|
|
1037
|
-
# Clear cache (force full rebuild)
|
|
1038
|
-
rm .bunki-cache.json
|
|
1039
|
-
|
|
1040
|
-
# Exclude from version control
|
|
1041
|
-
echo ".bunki-cache.json" >> .gitignore
|
|
1042
|
-
```
|
|
1043
|
-
|
|
1044
|
-
### When to Use
|
|
1045
|
-
|
|
1046
|
-
**Recommended for:**
|
|
1047
|
-
|
|
1048
|
-
- Large sites (100+ posts)
|
|
1049
|
-
- Development workflow with frequent rebuilds
|
|
1050
|
-
- Sites with slow CSS processing (Tailwind, PostCSS)
|
|
1051
|
-
|
|
1052
|
-
**Not needed for:**
|
|
1053
|
-
|
|
1054
|
-
- Small sites (<50 posts) - already fast enough
|
|
1055
|
-
- CI/CD builds - prefer clean full builds
|
|
1056
|
-
- Production deployments - always use full builds
|
|
1057
|
-
|
|
1058
|
-
### Cache Format
|
|
1059
|
-
|
|
1060
|
-
Version 2.0.0 cache structure:
|
|
1061
|
-
|
|
1062
|
-
```json
|
|
1063
|
-
{
|
|
1064
|
-
"version": "2.0.0",
|
|
1065
|
-
"configHash": "abc123",
|
|
1066
|
-
"files": {
|
|
1067
|
-
"/path/to/post.md": {
|
|
1068
|
-
"hash": "def456",
|
|
1069
|
-
"mtime": 1771720766417,
|
|
1070
|
-
"post": {
|
|
1071
|
-
"title": "Post Title",
|
|
1072
|
-
"date": "2024-01-01",
|
|
1073
|
-
"content": "...",
|
|
1074
|
-
"html": "..."
|
|
1075
|
-
}
|
|
1076
|
-
},
|
|
1077
|
-
"/path/to/main.css": {
|
|
1078
|
-
"hash": "ghi789",
|
|
1079
|
-
"mtime": 1771720800000
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
```
|
|
1084
|
-
|
|
1085
|
-
### Future Optimizations
|
|
1086
|
-
|
|
1087
|
-
Current implementation (v0.18.0) optimizes parsing and CSS processing. Future versions may add:
|
|
1088
|
-
|
|
1089
|
-
- Selective page regeneration (only rebuild changed posts)
|
|
1090
|
-
- Incremental sitemap/RSS updates
|
|
1091
|
-
- Smart index page regeneration
|
|
1092
|
-
|
|
1093
|
-
## CLI Commands
|
|
1094
|
-
|
|
1095
|
-
```bash
|
|
1096
|
-
bunki init [--config FILE] # Initialize new site
|
|
1097
|
-
bunki new <TITLE> [--tags TAG1,TAG2] # Create new post
|
|
1098
|
-
bunki generate [--config FILE] # Build static site (full)
|
|
1099
|
-
bunki generate --incremental # Build with caching (3x faster)
|
|
1100
|
-
bunki validate [--config FILE] # Validate frontmatter
|
|
1101
|
-
bunki serve [--port 3000] # Start dev server
|
|
1102
|
-
bunki css [--watch] # Process CSS
|
|
1103
|
-
bunki images:push [--domain DOMAIN] # Upload images to cloud
|
|
1104
|
-
```
|
|
1105
|
-
|
|
1106
|
-
## Output Structure
|
|
1107
|
-
|
|
1108
|
-
```
|
|
1109
|
-
dist/
|
|
1110
|
-
├── index.html # Homepage
|
|
1111
|
-
├── feed.xml # RSS feed
|
|
1112
|
-
├── sitemap.xml # XML sitemap
|
|
1113
|
-
├── css/style.css # Processed stylesheet
|
|
1114
|
-
├── 2025/
|
|
1115
|
-
│ └── my-post/
|
|
1116
|
-
│ └── index.html # Post page
|
|
1117
|
-
├── tags/
|
|
1118
|
-
│ └── web/
|
|
1119
|
-
│ └── index.html # Tag page
|
|
1120
|
-
└── page/
|
|
1121
|
-
└── 2/index.html # Paginated content
|
|
1122
|
-
```
|
|
1123
|
-
|
|
1124
|
-
## Architecture
|
|
1125
|
-
|
|
1126
|
-
Bunki follows a **modular architecture** with single responsibility modules for maintainability and performance:
|
|
1127
|
-
|
|
1128
|
-
### Core Orchestrator
|
|
1129
|
-
|
|
1130
|
-
- **site-generator.ts** (282 lines) - Clean orchestrator coordinating all generation tasks
|
|
1131
|
-
- Uses dependency injection for testability
|
|
1132
|
-
- Parallel processing with Promise.all()
|
|
1133
|
-
- Minimal business logic (delegates to generators)
|
|
1134
|
-
|
|
1135
|
-
### Modular Generators
|
|
1136
|
-
|
|
1137
|
-
- **generators/feeds.ts** (285 lines) - RSS feed, sitemap, and robots.txt generation
|
|
1138
|
-
- **generators/pages.ts** (357 lines) - HTML page generation with batched processing
|
|
1139
|
-
- **generators/assets.ts** (115 lines) - CSS processing and static file copying
|
|
1140
|
-
|
|
1141
|
-
### Markdown Processing
|
|
1142
|
-
|
|
1143
|
-
- **utils/markdown/constants.ts** (71 lines) - Pre-compiled regex patterns, Schema.org types, icons
|
|
1144
|
-
- **utils/markdown/validators.ts** (139 lines) - Frontmatter and business location validation
|
|
1145
|
-
- **utils/markdown/parser.ts** (308 lines) - Markdown to HTML conversion with sanitization
|
|
1146
|
-
|
|
1147
|
-
### Reusable Utilities
|
|
1148
|
-
|
|
1149
|
-
- **utils/pagination.ts** (67 lines) - Pagination logic used across index, tags, and archives
|
|
1150
|
-
- **utils/xml-builder.ts** (117 lines) - DRY XML/RSS building utilities
|
|
1151
|
-
- **utils/markdown-utils.ts** (177 lines) - Main export file for backward compatibility
|
|
1152
|
-
|
|
1153
|
-
### Dependency Graph
|
|
1154
|
-
|
|
1155
|
-
```
|
|
1156
|
-
site-generator.ts (orchestrator)
|
|
1157
|
-
├── generators/feeds.ts → utils/xml-builder.ts
|
|
1158
|
-
├── generators/pages.ts → utils/pagination.ts
|
|
1159
|
-
├── generators/assets.ts
|
|
1160
|
-
└── utils/markdown/
|
|
1161
|
-
├── constants.ts
|
|
1162
|
-
├── validators.ts
|
|
1163
|
-
└── parser.ts
|
|
1164
|
-
```
|
|
1165
|
-
|
|
1166
|
-
### Performance Optimizations
|
|
1167
|
-
|
|
1168
|
-
1. **Parallel Processing**: Independent tasks run simultaneously with Promise.all()
|
|
1169
|
-
2. **Batched Operations**: Posts processed in batches of 10 for optimal throughput
|
|
1170
|
-
3. **Pre-compiled Patterns**: Regex compiled once at module load, not on every parse
|
|
1171
|
-
4. **O(1) Lookups**: Set-based validation instead of array.includes()
|
|
1172
|
-
5. **Zero-Copy I/O**: Bun native APIs for kernel-level file transfers
|
|
1173
|
-
6. **Content Hashing**: Bun.hash() for CSS cache busting without external dependencies
|
|
1174
|
-
|
|
1175
|
-
### Benefits
|
|
1176
|
-
|
|
1177
|
-
- ✅ **Clarity** - Easy to find and understand code
|
|
1178
|
-
- ✅ **Testability** - Each module tested in isolation (424 tests)
|
|
1179
|
-
- ✅ **Maintainability** - Changes isolated to specific modules
|
|
1180
|
-
- ✅ **Reusability** - Modules can be imported independently
|
|
1181
|
-
- ✅ **Performance** - Optimized at module level with Bun native APIs
|
|
1182
|
-
|
|
1183
|
-
## Features
|
|
1184
|
-
|
|
1185
|
-
- **Markdown Processing**: Frontmatter extraction, code highlighting, HTML sanitization
|
|
1186
|
-
- **Relative Link Conversion**: Automatic conversion of relative markdown links (`../2023/post.md`) to absolute URLs (`/2023/post/`)
|
|
1187
|
-
- **Frontmatter Validation**: Automatic validation of business location data with clear error messages
|
|
1188
|
-
- **Security**: XSS protection, sanitized HTML, link hardening
|
|
1189
|
-
- **High Performance**:
|
|
1190
|
-
- **Incremental builds** with smart caching (3.2x faster, 68% speedup)
|
|
1191
|
-
- Parallel page generation (40-60% faster builds)
|
|
1192
|
-
- Batched post processing (10x faster for 100+ posts)
|
|
1193
|
-
- Pre-compiled regex patterns (2-3x faster parsing)
|
|
1194
|
-
- O(1) Set-based validation (35x faster)
|
|
1195
|
-
- Zero-copy file operations (50% faster, lower memory)
|
|
1196
|
-
- Bun native APIs for optimal performance
|
|
1197
|
-
- **Templating**: Nunjucks with custom filters and macros
|
|
1198
|
-
- **Styling**: Built-in PostCSS support for modern CSS frameworks with content-based cache busting
|
|
1199
|
-
- **Media Management**: Direct S3/R2 uploads for images and MP4 videos with URL mapping
|
|
1200
|
-
- **Incremental Uploads**: Year-based filtering (`--min-year`) for large media collections
|
|
1201
|
-
- **SEO**: Automatic RSS feeds, sitemaps, meta tags, and JSON-LD structured data
|
|
1202
|
-
- **JSON-LD Structured Data**: Automatic Schema.org markup (BlogPosting, WebSite, Organization, BreadcrumbList)
|
|
1203
|
-
- **Pagination**: Configurable posts per page with reusable pagination utilities
|
|
1204
|
-
- **Archives**: Year-based and tag-based organization
|
|
1205
|
-
- **Modular Architecture**: Single responsibility modules with comprehensive test coverage (424 tests)
|
|
124
|
+
Incremental builds store parsed posts and file checks in `.bunki-cache.json`; HTML pages are still regenerated. Full and incremental builds use the same validation and date ordering. Config changes invalidate cached posts; content changes also rebuild CSS. Add the cache to `.gitignore`. Delete that cache file to reset it, or omit `--incremental` for a full build.
|
|
1206
125
|
|
|
1207
126
|
## Development
|
|
1208
127
|
|
|
128
|
+
Bug reports, documentation improvements, and focused contributions are welcome. Start with the [contribution guide](CONTRIBUTING.md) or [open an issue](https://github.com/kahwee/bunki/issues).
|
|
129
|
+
|
|
1209
130
|
```bash
|
|
1210
|
-
git clone
|
|
131
|
+
git clone https://github.com/kahwee/bunki.git
|
|
1211
132
|
cd bunki
|
|
1212
|
-
bun install
|
|
1213
|
-
|
|
1214
|
-
bun run
|
|
1215
|
-
bun
|
|
1216
|
-
bun
|
|
1217
|
-
bun run
|
|
1218
|
-
bun run format # Biome formatting
|
|
133
|
+
bun install --frozen-lockfile
|
|
134
|
+
bun run test
|
|
135
|
+
bun run typecheck
|
|
136
|
+
bun run format:check
|
|
137
|
+
bun run build
|
|
138
|
+
bun run benchmark -- 1000 # Measure cold, cached, and single-post-edit builds
|
|
1219
139
|
```
|
|
1220
140
|
|
|
1221
|
-
|
|
141
|
+
Source lives in `src/`, tests in `test/`, and test data in `fixtures/`. See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow and [package.json](package.json) for additional scripts.
|
|
1222
142
|
|
|
1223
|
-
|
|
1224
|
-
bunki/
|
|
1225
|
-
├── src/
|
|
1226
|
-
│ ├── cli.ts # CLI interface
|
|
1227
|
-
│ ├── config.ts # Configuration management
|
|
1228
|
-
│ ├── site-generator.ts # Orchestrator (282 lines, was 957)
|
|
1229
|
-
│ ├── server.ts # Development server
|
|
1230
|
-
│ ├── parser.ts # Markdown parsing
|
|
1231
|
-
│ ├── types.ts # TypeScript types
|
|
1232
|
-
│ ├── generators/ # Modular generation (NEW)
|
|
1233
|
-
│ │ ├── feeds.ts # RSS, sitemap, robots.txt (285 lines)
|
|
1234
|
-
│ │ ├── pages.ts # HTML generation with batching (357 lines)
|
|
1235
|
-
│ │ └── assets.ts # CSS & static file copying (115 lines)
|
|
1236
|
-
│ └── utils/ # Utility modules
|
|
1237
|
-
│ ├── markdown/ # Markdown processing (NEW)
|
|
1238
|
-
│ │ ├── constants.ts # Pre-compiled patterns (71 lines)
|
|
1239
|
-
│ │ ├── validators.ts # Frontmatter validation (139 lines)
|
|
1240
|
-
│ │ └── parser.ts # Markdown → HTML (308 lines)
|
|
1241
|
-
│ ├── pagination.ts # Pagination utilities (67 lines, NEW)
|
|
1242
|
-
│ ├── xml-builder.ts # XML/RSS builders (117 lines, NEW)
|
|
1243
|
-
│ ├── markdown-utils.ts # Main export file (177 lines, was 576)
|
|
1244
|
-
│ ├── css-processor.ts # PostCSS + Bun.hash()
|
|
1245
|
-
│ ├── file-utils.ts # Bun native file ops
|
|
1246
|
-
│ ├── date-utils.ts # Date/time utilities
|
|
1247
|
-
│ ├── json-ld.ts # JSON-LD schema generation
|
|
1248
|
-
│ ├── image-uploader.ts # Image upload logic
|
|
1249
|
-
│ └── s3-uploader.ts # S3/R2 client
|
|
1250
|
-
├── test/ # Test suite (424 tests, mirrors src/)
|
|
1251
|
-
│ ├── utils/
|
|
1252
|
-
│ │ ├── markdown/ # Modular tests (NEW)
|
|
1253
|
-
│ │ │ ├── constants.test.ts (25 tests)
|
|
1254
|
-
│ │ │ ├── validators.test.ts (21 tests)
|
|
1255
|
-
│ │ │ └── parser.test.ts (17 tests)
|
|
1256
|
-
│ │ ├── pagination.test.ts (15 tests, NEW)
|
|
1257
|
-
│ │ ├── xml-builder.test.ts (13 tests, NEW)
|
|
1258
|
-
│ │ └── ...
|
|
1259
|
-
│ ├── cli/commands/
|
|
1260
|
-
│ ├── security/
|
|
1261
|
-
│ └── ...
|
|
1262
|
-
├── templates/ # Example templates
|
|
1263
|
-
├── fixtures/ # Test fixtures
|
|
1264
|
-
└── dist/ # Built output
|
|
1265
|
-
```
|
|
1266
|
-
|
|
1267
|
-
## Changelog
|
|
1268
|
-
|
|
1269
|
-
### v0.19.0 (Current)
|
|
1270
|
-
|
|
1271
|
-
- **Template Fragments**: Built-in reusable Nunjucks macros available in all site templates
|
|
1272
|
-
- `og-image.njk` — `og_image(post, site)` and `twitter_image(post, site)` for Open Graph / Twitter Card image tags
|
|
1273
|
-
- `json-ld.njk` — `blog_posting_schema(post, site)` and `local_business_schema(post, site)` for Schema.org structured data
|
|
1274
|
-
- `share-buttons.njk` — `share_buttons(post, site)` for X / Facebook / LinkedIn / Email share buttons (Tailwind)
|
|
1275
|
-
- `pagination.njk` — `pagination_nav(pagination)` for previous/next navigation (Tailwind)
|
|
1276
|
-
- Fragments are registered as a second Nunjucks search path; site templates take priority over built-in fragments
|
|
1277
|
-
- **Improved init templates**: `bunki init` now generates templates with canonical URLs, Open Graph meta tags, Twitter Cards, RSS feed link, share buttons, and JSON-LD structured data out of the box
|
|
1278
|
-
- **Template variables reference**: Added documentation for all variables available in each template type
|
|
1279
|
-
|
|
1280
|
-
### v0.18.1
|
|
1281
|
-
|
|
1282
|
-
- **Page Generation Optimization**: Cache JSON-LD schemas and metadata during initialization
|
|
1283
|
-
- Eliminates 910 redundant operations per build (455 posts × 2)
|
|
1284
|
-
- `extractFirstImageUrl()` called once during initialization, cached in `post.image`
|
|
1285
|
-
- Word count calculated once, cached in `post.wordCount`
|
|
1286
|
-
- JSON-LD schemas pre-generated and cached in `post.jsonLd`
|
|
1287
|
-
- Removed duplicate schema generation from page and feed generators
|
|
1288
|
-
- **Deployment Optimization**: All deploy commands now use incremental builds by default
|
|
1289
|
-
- `deploy:all` now builds incrementally for faster deployments
|
|
1290
|
-
- Individual site deployments also use incremental builds
|
|
1291
|
-
- Full builds available via `build:full` when needed
|
|
1292
|
-
|
|
1293
|
-
### v0.18.0
|
|
1294
|
-
|
|
1295
|
-
- **Incremental Builds**: Smart caching for 3.2x faster development builds
|
|
1296
|
-
- File change detection using content hashing and modification times
|
|
1297
|
-
- Selective markdown parsing (only parse changed files)
|
|
1298
|
-
- CSS caching (skip processing if unchanged)
|
|
1299
|
-
- Cache format v2.0.0 stores full parsed post data
|
|
1300
|
-
- Automatic config change detection triggers full rebuilds
|
|
1301
|
-
- `.bunki-cache.json` stores file hashes, mtimes, and parsed posts
|
|
1302
|
-
- **Performance Results** (455 posts):
|
|
1303
|
-
- Full build: 3,128ms
|
|
1304
|
-
- Incremental (no changes): 985ms (68% faster)
|
|
1305
|
-
- Markdown parsing: 1,202ms → 55ms (22x faster)
|
|
1306
|
-
- CSS processing: 1,024ms → 1ms (1024x faster)
|
|
1307
|
-
- **CLI Enhancement**: New `--incremental` flag for `bunki generate`
|
|
1308
|
-
- **Code Cleanup**: Removed unused imports, reverted template extraction
|
|
1309
|
-
|
|
1310
|
-
### v0.17.0
|
|
1311
|
-
|
|
1312
|
-
- **Major Architecture Refactoring**: Modular design with single responsibility modules
|
|
1313
|
-
- Split `site-generator.ts` from 957 to 282 lines (-70%)
|
|
1314
|
-
- Split `markdown-utils.ts` from 576 to 177 lines (-69%)
|
|
1315
|
-
- Created 3 new generator modules: feeds, pages, assets
|
|
1316
|
-
- Created 3 new markdown modules: constants, validators, parser
|
|
1317
|
-
- Created 2 new utility modules: pagination, xml-builder
|
|
1318
|
-
- **Performance Improvements**:
|
|
1319
|
-
- Parallel page generation with Promise.all() (40-60% faster builds)
|
|
1320
|
-
- Batched post processing (10x faster for 100+ posts)
|
|
1321
|
-
- Pre-compiled regex patterns (2-3x faster markdown parsing)
|
|
1322
|
-
- O(1) Set-based validation (35x faster validation)
|
|
1323
|
-
- Zero-copy file operations using Bun native APIs (50% faster, lower memory)
|
|
1324
|
-
- Content-based CSS cache busting with Bun.hash()
|
|
1325
|
-
- **DRY Improvements**:
|
|
1326
|
-
- Extracted pagination utilities (saved 80+ lines)
|
|
1327
|
-
- Created XML builder utilities (saved 150+ lines)
|
|
1328
|
-
- Reusable page writing utilities (saved 40+ lines)
|
|
1329
|
-
- **Enhanced Test Coverage**:
|
|
1330
|
-
- Added 87 new tests (337 → 424 tests total)
|
|
1331
|
-
- Modular test organization mirroring source structure
|
|
1332
|
-
- 100% backward compatible with existing API
|
|
1333
|
-
- **Code Reduction**: Eliminated 1,074 lines while adding 11 focused modules
|
|
1334
|
-
|
|
1335
|
-
### v0.16.0
|
|
1336
|
-
|
|
1337
|
-
- **Relative Link Conversion**: Automatically convert relative markdown links to absolute URLs
|
|
1338
|
-
- Supports `../2023/post.md` → `/2023/post/` conversion during build time
|
|
1339
|
-
- Works with multiple parent directories (`../../`, `../../../`, etc.)
|
|
1340
|
-
- Preserves link text and formatting
|
|
1341
|
-
- Enables cleaner internal cross-references in markdown files
|
|
1342
|
-
- **Comprehensive Testing**: Added 13 new tests for relative link conversion
|
|
1343
|
-
- **Zero Configuration**: Works automatically without any setup required
|
|
143
|
+
GitHub Actions runs typechecking, formatting, coverage tests, a build, and a site-generation smoke test. Compatibility checks cover the minimum supported Bun version and the latest release.
|
|
1344
144
|
|
|
1345
|
-
|
|
145
|
+
The benchmark creates and removes a temporary site, runs each scenario three times, and reports median wall-clock times. It uses synthetic Markdown and minimal templates with CSS disabled, so it measures parsing, caching, and site generation rather than PostCSS or network uploads. The report includes the Bun version, platform,
|
|
146
|
+
HTML page count, and an SHA-256 fingerprint of final HTML paths and contents for
|
|
147
|
+
before/after output comparisons. Fingerprinting runs outside the timed builds.
|
|
148
|
+
Repeated tag names are normalized once per site model; the cache is discarded after
|
|
149
|
+
that model is created. In a local 1,000-post fixture with 21 distinct tags, this
|
|
150
|
+
reduced slug normalization calls from 2,000 to 21 and preserved all 1,404 HTML files.
|
|
151
|
+
This reduces repeated CPU work; whole-build timings also depend on parsing,
|
|
152
|
+
rendering, filesystem performance, and other workloads.
|
|
1346
153
|
|
|
1347
|
-
|
|
1348
|
-
- Enforces `business:` field (rejects deprecated `location:`)
|
|
1349
|
-
- Enforces `lat:`/`lng:` coordinates (rejects deprecated `latitude:`/`longitude:`)
|
|
1350
|
-
- Validates required fields (type, name, lat, lng)
|
|
1351
|
-
- Clear error messages with suggestions for fixes
|
|
1352
|
-
- New `bunki validate` command for standalone validation
|
|
1353
|
-
- **Enhanced Testing**: 47 tests for markdown parsing and validation
|
|
1354
|
-
- **Breaking Change**: Deprecated `location:`, `latitude:`, and `longitude:` fields now rejected
|
|
154
|
+
## Cloudflare hosting
|
|
1355
155
|
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
- BlogPosting schema for individual blog posts with author, keywords, images
|
|
1360
|
-
- WebSite schema for homepage with search action
|
|
1361
|
-
- Organization schema for publisher information
|
|
1362
|
-
- BreadcrumbList schema for navigation hierarchy
|
|
1363
|
-
- Automatic featured image extraction from post content
|
|
1364
|
-
- **Comprehensive SEO**: Complete structured data support following Google best practices
|
|
1365
|
-
- **Zero configuration**: JSON-LD automatically generated during site build
|
|
1366
|
-
- **Well documented**: Extensive README section with examples and validation tools
|
|
1367
|
-
- **Fully tested**: 60+ new tests covering all JSON-LD schema types
|
|
1368
|
-
|
|
1369
|
-
### v0.7.0
|
|
1370
|
-
|
|
1371
|
-
- **Media uploads**: Added MP4 video support alongside image uploads
|
|
1372
|
-
- **Incremental uploads**: Year-based filtering with `--min-year` option
|
|
1373
|
-
- **Enhanced documentation**: Comprehensive video upload guide with examples
|
|
1374
|
-
- **Test coverage**: Added 10+ tests for image/video uploader functionality
|
|
1375
|
-
- **Fixed timestamps**: Stable dates in test fixtures to prevent flipping
|
|
1376
|
-
|
|
1377
|
-
### v0.6.1
|
|
1378
|
-
|
|
1379
|
-
- Version bump and welcome date stabilization
|
|
1380
|
-
- Test formatting improvements
|
|
1381
|
-
- Code style consistency updates
|
|
1382
|
-
|
|
1383
|
-
### v0.5.3
|
|
1384
|
-
|
|
1385
|
-
- Modularized CLI commands with dependency injection
|
|
1386
|
-
- Enhanced test coverage (130+ tests, 539+ assertions)
|
|
1387
|
-
- Fixed CLI entry point detection (Bun.main compatibility)
|
|
1388
|
-
- Added comprehensive server tests using Bun.serve()
|
|
1389
|
-
- Improved CSS processor with fallback support
|
|
1390
|
-
|
|
1391
|
-
### v0.3.0
|
|
1392
|
-
|
|
1393
|
-
- PostCSS integration with CSS processing command
|
|
1394
|
-
- Framework-agnostic CSS support (Tailwind, etc.)
|
|
1395
|
-
- CSS watch mode for development
|
|
1396
|
-
- Better error handling and recovery
|
|
1397
|
-
|
|
1398
|
-
## Contributing
|
|
1399
|
-
|
|
1400
|
-
Contributions welcome! Areas for improvement:
|
|
1401
|
-
|
|
1402
|
-
- Bug fixes and error handling
|
|
1403
|
-
- Documentation and examples
|
|
1404
|
-
- Test coverage expansion
|
|
1405
|
-
- Performance optimizations
|
|
1406
|
-
- New features and plugins
|
|
156
|
+
Build the site with Bun, then serve the generated files with Workers Static Assets.
|
|
157
|
+
See [Cloudflare Workers and Bun](docs/cloudflare.md) for runtime compatibility,
|
|
158
|
+
deployment configuration, routing, and caching guidance.
|
|
1407
159
|
|
|
1408
160
|
## License
|
|
1409
161
|
|
|
1410
|
-
MIT © [KahWee Teng](https://github.com/kahwee)
|
|
1411
|
-
|
|
1412
|
-
|
|
162
|
+
[MIT](LICENSE) © [KahWee Teng](https://github.com/kahwee)
|
|
163
|
+
|
|
164
|
+
## CI maintenance
|
|
165
|
+
|
|
166
|
+
[GitHub Actions maintenance](.github/ACTIONS.md) covers workflows, parallel checks, action versions, and weekly updates.
|
|
167
|
+
|
|
168
|
+
Bun 1.4.4 owns formatting (`bun run format`, `bun run format:check`) and type checking
|
|
169
|
+
(`bun run typecheck`). Biome has been removed. Bun formatting is not a replacement
|
|
170
|
+
for Biome’s semantic lint rules; tests, strict type checks, and package builds remain
|
|
171
|
+
the verification gates. TypeScript remains installed for declaration generation.
|
|
172
|
+
|
|
173
|
+
`bun run check:source` runs formatting, Bun type checking, and tests in parallel.
|
|
174
|
+
`bun run check` waits for those checks before building package artifacts.
|
|
175
|
+
`bun run build` checks types before building. CI calls `build:artifacts` only after
|
|
176
|
+
a successful Bun check; TypeScript emits declarations with `--noCheck` to avoid
|
|
177
|
+
checking the same source again. Do not use `build:artifacts` as a validation command.
|
|
178
|
+
|
|
179
|
+
On the session’s Linux executor, the median of three project-check runs was
|
|
180
|
+
0.152s with Bun versus 0.700s with `bun x --no-install tsc --noEmit`.
|
|
181
|
+
These include command startup and describe this checkout, not a CI speed guarantee.
|
|
182
|
+
Declaration emission measured 0.640s with `--noCheck` versus
|
|
183
|
+
0.728s with checking; generated `.d.ts` contents matched.
|
|
184
|
+
|
|
185
|
+
Plain CSS uses Bun’s native bundler with no PostCSS subprocess. Local `@import` rules
|
|
186
|
+
are bundled and referenced assets may be embedded as data URLs. CSS is transformed,
|
|
187
|
+
so output is not byte-for-byte identical to the input. Existing project PostCSS
|
|
188
|
+
configs or an explicit `postcssConfig` continue to select PostCSS automatically.
|
|
189
|
+
Set `css.processor` to `"bun"` or `"postcss"` to select an engine explicitly.
|
|
190
|
+
|
|
191
|
+
Bun does not compile Tailwind directives or run arbitrary PostCSS plugins. Those
|
|
192
|
+
projects still need PostCSS and their own plugins; see [CSS configuration](docs/reference.md#css-configuration).
|
|
193
|
+
The repository’s unused Tailwind and Autoprefixer development setup has been removed.
|
|
194
|
+
PostCSS and its CLI remain installed for existing plugin-based sites. The PostCSS
|
|
195
|
+
path needs `bun` on PATH, including when using a compiled executable. A failed
|
|
196
|
+
PostCSS plugin stops generation so its transformation is not silently omitted;
|
|
197
|
+
plain CSS retains the raw-CSS fallback. Both engines support content hashing.
|
|
198
|
+
Existing configuration files that cannot be loaded also stop generation.
|
|
199
|
+
|
|
200
|
+
A local Bun 1.4.4 benchmark using `templates/styles/main.css` measured median CSS-phase
|
|
201
|
+
times of 10.2 ms with native Bun and 204.6 ms with the Bun-hosted PostCSS CLI and no
|
|
202
|
+
plugins (one warm-up per engine, then five alternating samples). Native CSS output
|
|
203
|
+
is transformed; this is not an output-identity comparison or a full-site speedup.
|
|
204
|
+
|
|
205
|
+
See [build performance](docs/build-performance.md) for the Bun-hosted build,
|
|
206
|
+
measured artifact timings, output verification, and benchmark scope.
|
|
207
|
+
|
|
208
|
+
To experiment locally with the same canary for source, bundled and AOT builds, run
|
|
209
|
+
`python3 scripts/benchmark-aot.py /absolute/path/to/bun 5` on Linux after installing
|
|
210
|
+
dependencies. The benchmark uses temporary synthetic fixtures and checks output,
|
|
211
|
+
config, plugin and failure behavior. AOT is experimental; the benchmark enables
|
|
212
|
+
disk-code loading for user configs and plugins and does not change the stable runtime.
|