bunki 0.6.0 → 0.7.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 +430 -406
- package/dist/cli/commands/images-push.d.ts +1 -0
- package/dist/cli.js +132 -124
- package/dist/index.js +96 -91
- package/dist/types.d.ts +3 -1
- package/dist/utils/css-processor.d.ts +2 -1
- package/dist/utils/file-utils.d.ts +140 -0
- package/dist/utils/s3-uploader.d.ts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,45 +1,60 @@
|
|
|
1
1
|
# Bunki
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/kahwee/bunki/actions/workflows/ci.yml)
|
|
4
|
+
[](https://coveralls.io/github/kahwee/bunki?branch=main)
|
|
5
|
+
[](https://badge.fury.io/js/bunki)
|
|
4
6
|
|
|
5
|
-
Fast static site generator for blogs
|
|
7
|
+
Fast static site generator for blogs and documentation built with Bun. Supports Markdown + frontmatter, tags, year-based archives, pagination, RSS feeds, sitemaps, secure HTML sanitization, syntax highlighting, PostCSS pipelines, media uploads (images & videos to S3/R2), incremental uploads with year filtering, and Nunjucks templating.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
11
|
+
Requires **Bun v1.3.0+** (recommended) or Node.js v18+
|
|
12
|
+
|
|
9
13
|
```bash
|
|
10
|
-
|
|
11
|
-
bun install -g bunki
|
|
12
|
-
npm i bunki # Node (>=18)
|
|
13
|
-
```
|
|
14
|
+
# Install globally with Bun
|
|
15
|
+
bun install -g bunki
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
# Or with npm
|
|
18
|
+
npm install -g bunki
|
|
19
|
+
|
|
20
|
+
# Or in your project
|
|
21
|
+
bun install bunki
|
|
22
|
+
```
|
|
16
23
|
|
|
17
24
|
## Quick Start
|
|
18
25
|
|
|
19
26
|
```bash
|
|
20
|
-
bunki init
|
|
21
|
-
bunki new "
|
|
22
|
-
bunki generate
|
|
23
|
-
bunki serve
|
|
27
|
+
bunki init # Create new site
|
|
28
|
+
bunki new "My First Post" --tags web,notes # Add content
|
|
29
|
+
bunki generate # Build static site
|
|
30
|
+
bunki serve --port 3000 # Preview locally
|
|
24
31
|
```
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
This creates a fully functional site with Markdown content, responsive templates, and all assets in `dist/`.
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Create `bunki.config.ts` in your project root:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
29
40
|
import { SiteConfig } from "bunki";
|
|
41
|
+
|
|
30
42
|
export default (): SiteConfig => ({
|
|
31
43
|
title: "My Blog",
|
|
32
|
-
description: "
|
|
44
|
+
description: "My thoughts and ideas",
|
|
33
45
|
baseUrl: "https://example.com",
|
|
34
46
|
domain: "example.com",
|
|
47
|
+
|
|
48
|
+
// Optional: PostCSS/Tailwind CSS support
|
|
35
49
|
css: {
|
|
36
50
|
input: "templates/styles/main.css",
|
|
37
51
|
output: "css/style.css",
|
|
38
52
|
postcssConfig: "postcss.config.js",
|
|
39
53
|
enabled: true,
|
|
40
|
-
},
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Optional: Image upload to Cloudflare R2 or S3
|
|
41
57
|
s3: {
|
|
42
|
-
// optional image upload
|
|
43
58
|
accessKeyId: process.env.R2_ACCESS_KEY_ID || "",
|
|
44
59
|
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || "",
|
|
45
60
|
bucket: process.env.R2_BUCKET || "",
|
|
@@ -50,37 +65,54 @@ export default (): SiteConfig => ({
|
|
|
50
65
|
});
|
|
51
66
|
```
|
|
52
67
|
|
|
53
|
-
## Frontmatter
|
|
68
|
+
## Content & Frontmatter
|
|
69
|
+
|
|
70
|
+
Create Markdown files in `content/YYYY/` (e.g., `content/2025/my-post.md`):
|
|
54
71
|
|
|
55
72
|
```markdown
|
|
56
73
|
---
|
|
57
74
|
title: "Post Title"
|
|
58
75
|
date: 2025-01-15T09:00:00-07:00
|
|
59
76
|
tags: [web, performance]
|
|
60
|
-
excerpt: Optional summary
|
|
77
|
+
excerpt: "Optional summary for listings"
|
|
61
78
|
---
|
|
79
|
+
|
|
80
|
+
# Post Title
|
|
81
|
+
|
|
82
|
+
Your content here with **markdown** support.
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
<video controls width="640" height="360">
|
|
87
|
+
<source src="video.mp4" type="video/mp4">
|
|
88
|
+
Your browser does not support HTML5 video.
|
|
89
|
+
</video>
|
|
62
90
|
```
|
|
63
91
|
|
|
64
|
-
Optional tag descriptions
|
|
92
|
+
Optional: Define tag descriptions in `src/tags.toml`:
|
|
65
93
|
|
|
66
94
|
```toml
|
|
67
|
-
performance = "
|
|
68
|
-
web = "
|
|
95
|
+
performance = "Performance optimization and speed"
|
|
96
|
+
web = "Web development and technology"
|
|
69
97
|
```
|
|
70
98
|
|
|
71
|
-
## CSS
|
|
99
|
+
## CSS & Tailwind
|
|
100
|
+
|
|
101
|
+
To use Tailwind CSS:
|
|
72
102
|
|
|
73
103
|
```bash
|
|
74
104
|
bun add -D tailwindcss @tailwindcss/postcss @tailwindcss/typography
|
|
75
105
|
```
|
|
76
106
|
|
|
77
|
-
`postcss.config.js`:
|
|
107
|
+
Create `postcss.config.js`:
|
|
78
108
|
|
|
79
|
-
```
|
|
80
|
-
module.exports = {
|
|
109
|
+
```javascript
|
|
110
|
+
module.exports = {
|
|
111
|
+
plugins: [require("@tailwindcss/postcss")],
|
|
112
|
+
};
|
|
81
113
|
```
|
|
82
114
|
|
|
83
|
-
`templates/styles/main.css`:
|
|
115
|
+
Create `templates/styles/main.css`:
|
|
84
116
|
|
|
85
117
|
```css
|
|
86
118
|
@tailwind base;
|
|
@@ -88,541 +120,533 @@ module.exports = { plugins: [require("@tailwindcss/postcss")] };
|
|
|
88
120
|
@tailwind utilities;
|
|
89
121
|
```
|
|
90
122
|
|
|
91
|
-
|
|
123
|
+
CSS is processed automatically during `bunki generate`.
|
|
124
|
+
|
|
125
|
+
## Image Management
|
|
126
|
+
|
|
127
|
+
### Overview
|
|
128
|
+
|
|
129
|
+
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.
|
|
130
|
+
|
|
131
|
+
**Supported formats:**
|
|
92
132
|
|
|
93
|
-
|
|
133
|
+
- **Images:** JPG, JPEG, PNG, GIF, WebP, SVG
|
|
134
|
+
- **Video:** MP4
|
|
94
135
|
|
|
95
|
-
|
|
136
|
+
### Directory Structure
|
|
137
|
+
|
|
138
|
+
Organize images by year and post slug:
|
|
96
139
|
|
|
97
140
|
```
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
141
|
+
images/
|
|
142
|
+
├── 2023/
|
|
143
|
+
│ ├── post-slug-1/
|
|
144
|
+
│ │ ├── image-1.jpg
|
|
145
|
+
│ │ └── image-2.png
|
|
146
|
+
│ └── post-slug-2/
|
|
147
|
+
│ └── photo.webp
|
|
148
|
+
├── 2024/
|
|
149
|
+
│ └── travel-guide/
|
|
150
|
+
│ ├── paris-1.jpg
|
|
151
|
+
│ ├── london-2.jpg
|
|
152
|
+
│ ├── tokyo-3.png
|
|
153
|
+
│ └── travel-vlog.mp4
|
|
154
|
+
└── 2025/
|
|
155
|
+
└── new-post/
|
|
156
|
+
├── screenshot.jpg
|
|
157
|
+
└── demo-video.mp4
|
|
103
158
|
```
|
|
104
159
|
|
|
105
|
-
|
|
160
|
+
The directory structure is preserved when uploading to cloud storage.
|
|
106
161
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
162
|
+
### Configuration
|
|
163
|
+
|
|
164
|
+
Add S3/R2 configuration to `bunki.config.ts`:
|
|
110
165
|
|
|
111
|
-
|
|
166
|
+
```typescript
|
|
167
|
+
import { SiteConfig } from "bunki";
|
|
168
|
+
|
|
169
|
+
export default (): SiteConfig => ({
|
|
170
|
+
title: "My Blog",
|
|
171
|
+
// ... other config
|
|
112
172
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
173
|
+
// Image upload configuration
|
|
174
|
+
s3: {
|
|
175
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
|
|
176
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
|
|
177
|
+
bucket: process.env.S3_BUCKET || "",
|
|
178
|
+
endpoint: process.env.S3_ENDPOINT, // Optional: for R2, etc.
|
|
179
|
+
region: process.env.S3_REGION || "auto",
|
|
180
|
+
publicUrl: process.env.S3_PUBLIC_URL || "",
|
|
181
|
+
},
|
|
121
182
|
});
|
|
122
|
-
await gen.initialize();
|
|
123
|
-
await gen.generate();
|
|
124
183
|
```
|
|
125
184
|
|
|
126
|
-
|
|
185
|
+
### Environment Variables
|
|
127
186
|
|
|
128
|
-
|
|
129
|
-
init | new | generate | serve | images:push | css
|
|
130
|
-
```
|
|
187
|
+
Set these in your `.env` file or export them in your shell:
|
|
131
188
|
|
|
132
|
-
|
|
189
|
+
```bash
|
|
190
|
+
# Required
|
|
191
|
+
export S3_ACCESS_KEY_ID="your-access-key"
|
|
192
|
+
export S3_SECRET_ACCESS_KEY="your-secret-key"
|
|
193
|
+
export S3_BUCKET="your-bucket-name"
|
|
194
|
+
export S3_PUBLIC_URL="https://cdn.example.com"
|
|
133
195
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
tags/... tag pages
|
|
141
|
-
css/style.css (if enabled)
|
|
196
|
+
# Optional (for Cloudflare R2 or custom endpoints)
|
|
197
|
+
export S3_ENDPOINT="https://r2.cloudflarestorage.com"
|
|
198
|
+
export S3_REGION="auto"
|
|
199
|
+
|
|
200
|
+
# Optional (custom domain per bucket)
|
|
201
|
+
export S3_CUSTOM_DOMAIN_YOUR_BUCKET="cdn.example.com"
|
|
142
202
|
```
|
|
143
203
|
|
|
144
|
-
|
|
204
|
+
### Basic Usage
|
|
145
205
|
|
|
146
|
-
|
|
206
|
+
Upload all images:
|
|
147
207
|
|
|
148
|
-
|
|
208
|
+
```bash
|
|
209
|
+
bunki images:push
|
|
210
|
+
```
|
|
149
211
|
|
|
150
|
-
|
|
212
|
+
This command:
|
|
151
213
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- Minor CSS command robustness and exit handling
|
|
214
|
+
1. Scans the `images/` directory recursively
|
|
215
|
+
2. Uploads all supported image formats
|
|
216
|
+
3. Preserves the directory structure (year/slug/filename)
|
|
217
|
+
4. Generates public URLs for each image
|
|
157
218
|
|
|
158
|
-
|
|
219
|
+
### Command Options
|
|
159
220
|
|
|
160
|
-
|
|
161
|
-
- Export map + sideEffects=false for tree-shaking
|
|
162
|
-
- Prepack build cleanup
|
|
221
|
+
#### `--images <dir>`
|
|
163
222
|
|
|
164
|
-
|
|
223
|
+
Specify a custom images directory (default: `./images`)
|
|
165
224
|
|
|
166
225
|
```bash
|
|
167
|
-
|
|
168
|
-
bun run build
|
|
169
|
-
bun test
|
|
226
|
+
bunki images:push --images ./assets/images
|
|
170
227
|
```
|
|
171
228
|
|
|
172
|
-
|
|
229
|
+
#### `--domain <domain>`
|
|
173
230
|
|
|
174
|
-
|
|
231
|
+
Set a custom domain for bucket identification (optional)
|
|
175
232
|
|
|
176
|
-
|
|
233
|
+
```bash
|
|
234
|
+
bunki images:push --domain my-blog
|
|
235
|
+
```
|
|
177
236
|
|
|
178
|
-
|
|
237
|
+
#### `--output-json <file>`
|
|
179
238
|
|
|
180
|
-
|
|
239
|
+
Export a JSON mapping of filenames to their public URLs
|
|
181
240
|
|
|
182
|
-
|
|
241
|
+
```bash
|
|
242
|
+
bunki images:push --output-json image-urls.json
|
|
243
|
+
```
|
|
183
244
|
|
|
184
|
-
|
|
245
|
+
This creates a JSON file with the structure:
|
|
185
246
|
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
description: "A blog built with Bunki",
|
|
191
|
-
baseUrl: "https://example.com",
|
|
192
|
-
domain: "blog",
|
|
193
|
-
// CSS processing configuration
|
|
194
|
-
css: {
|
|
195
|
-
input: "templates/styles/main.css", // Input CSS file
|
|
196
|
-
output: "css/style.css", // Output path in dist
|
|
197
|
-
postcssConfig: "postcss.config.js", // PostCSS config file
|
|
198
|
-
enabled: true, // Enable CSS processing
|
|
199
|
-
watch: false, // Watch for changes (dev mode)
|
|
200
|
-
},
|
|
201
|
-
// ... other config
|
|
202
|
-
};
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"2023/post-slug/image.jpg": "https://cdn.example.com/2023/post-slug/image.jpg",
|
|
250
|
+
"2024/travel/paris.jpg": "https://cdn.example.com/2024/travel/paris.jpg"
|
|
203
251
|
}
|
|
204
252
|
```
|
|
205
253
|
|
|
206
|
-
|
|
254
|
+
#### `--min-year <year>`
|
|
255
|
+
|
|
256
|
+
Upload only images from the specified year onwards
|
|
207
257
|
|
|
208
258
|
```bash
|
|
209
|
-
#
|
|
210
|
-
bunki
|
|
259
|
+
# Upload only 2023 and 2024 images (skip 2021, 2022)
|
|
260
|
+
bunki images:push --min-year 2023
|
|
211
261
|
|
|
212
|
-
#
|
|
213
|
-
bunki
|
|
262
|
+
# Upload only 2024 and newer images
|
|
263
|
+
bunki images:push --min-year 2024
|
|
214
264
|
|
|
215
|
-
#
|
|
216
|
-
bunki
|
|
265
|
+
# Upload from 2022 onwards (all images in this example)
|
|
266
|
+
bunki images:push --min-year 2022
|
|
217
267
|
```
|
|
218
268
|
|
|
219
|
-
|
|
269
|
+
This is useful for:
|
|
220
270
|
|
|
221
|
-
|
|
271
|
+
- Incremental uploads (upload only new images)
|
|
272
|
+
- Testing uploads for specific years
|
|
273
|
+
- Managing large image collections across multiple uploads
|
|
222
274
|
|
|
223
|
-
|
|
224
|
-
bun add -D tailwindcss @tailwindcss/postcss @tailwindcss/typography
|
|
225
|
-
```
|
|
275
|
+
### Complete Examples
|
|
226
276
|
|
|
227
|
-
|
|
277
|
+
#### Cloudflare R2 Setup
|
|
228
278
|
|
|
229
|
-
|
|
230
|
-
module.exports = {
|
|
231
|
-
plugins: [require("@tailwindcss/postcss")],
|
|
232
|
-
};
|
|
233
|
-
```
|
|
279
|
+
1. **Create R2 bucket and API token** in Cloudflare dashboard
|
|
234
280
|
|
|
235
|
-
|
|
281
|
+
2. **Set environment variables:**
|
|
236
282
|
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
};
|
|
283
|
+
```bash
|
|
284
|
+
export S3_ACCESS_KEY_ID="your-r2-api-token-id"
|
|
285
|
+
export S3_SECRET_ACCESS_KEY="your-r2-api-token-secret"
|
|
286
|
+
export S3_BUCKET="my-blog-images"
|
|
287
|
+
export S3_ENDPOINT="https://r2.cloudflarestorage.com"
|
|
288
|
+
export S3_REGION="auto"
|
|
289
|
+
export S3_PUBLIC_URL="https://cdn.example.com"
|
|
245
290
|
```
|
|
246
291
|
|
|
247
|
-
|
|
292
|
+
3. **Upload images:**
|
|
248
293
|
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
@tailwind components;
|
|
252
|
-
@tailwind utilities;
|
|
294
|
+
```bash
|
|
295
|
+
bunki images:push --output-json image-urls.json
|
|
253
296
|
```
|
|
254
297
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
## 📦 Installation
|
|
298
|
+
#### AWS S3 Setup
|
|
258
299
|
|
|
259
|
-
|
|
300
|
+
1. **Create S3 bucket and IAM user** in AWS Console
|
|
260
301
|
|
|
261
|
-
|
|
302
|
+
2. **Set environment variables:**
|
|
262
303
|
|
|
263
304
|
```bash
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
305
|
+
export S3_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
|
|
306
|
+
export S3_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
307
|
+
export S3_BUCKET="my-blog-bucket"
|
|
308
|
+
export S3_REGION="us-east-1"
|
|
309
|
+
export S3_PUBLIC_URL="https://my-blog-bucket.s3.amazonaws.com"
|
|
269
310
|
```
|
|
270
311
|
|
|
271
|
-
|
|
312
|
+
3. **Upload images:**
|
|
272
313
|
|
|
273
314
|
```bash
|
|
274
|
-
|
|
275
|
-
bun install -g bunki
|
|
276
|
-
|
|
277
|
-
# Or with npm
|
|
278
|
-
npm install -g bunki
|
|
279
|
-
|
|
280
|
-
# Or install locally in your project
|
|
281
|
-
bun install bunki
|
|
282
|
-
# or
|
|
283
|
-
npm install bunki
|
|
284
|
-
|
|
285
|
-
# Or from GitHub for development
|
|
286
|
-
git clone git@github.com:kahwee/bunki.git
|
|
287
|
-
cd bunki
|
|
288
|
-
bun install
|
|
289
|
-
bun run build
|
|
290
|
-
bun link
|
|
315
|
+
bunki images:push
|
|
291
316
|
```
|
|
292
317
|
|
|
293
|
-
|
|
318
|
+
#### Incremental Upload (Year-Based)
|
|
294
319
|
|
|
295
|
-
|
|
320
|
+
If you have thousands of images and want to upload them incrementally:
|
|
296
321
|
|
|
297
322
|
```bash
|
|
298
|
-
#
|
|
299
|
-
bunki
|
|
323
|
+
# First, upload all 2023 images
|
|
324
|
+
bunki images:push --min-year 2023 --max-year 2023
|
|
325
|
+
|
|
326
|
+
# Next, upload 2024 images
|
|
327
|
+
bunki images:push --min-year 2024 --max-year 2024
|
|
300
328
|
|
|
301
|
-
#
|
|
302
|
-
|
|
303
|
-
# - content/ (for markdown posts)
|
|
304
|
-
# - templates/ (Nunjucks templates)
|
|
305
|
-
# - public/ (static assets)
|
|
329
|
+
# Finally, upload 2025 images
|
|
330
|
+
bunki images:push --min-year 2025
|
|
306
331
|
```
|
|
307
332
|
|
|
308
|
-
###
|
|
333
|
+
### Using Uploaded Images in Markdown
|
|
309
334
|
|
|
310
|
-
|
|
335
|
+
After uploading, reference images in your Markdown posts:
|
|
311
336
|
|
|
312
|
-
|
|
337
|
+
```markdown
|
|
313
338
|
---
|
|
314
|
-
title:
|
|
315
|
-
date:
|
|
316
|
-
tags: [
|
|
339
|
+
title: "Paris Trip"
|
|
340
|
+
date: 2024-06-15T10:00:00
|
|
341
|
+
tags: [travel, france]
|
|
317
342
|
---
|
|
318
343
|
|
|
319
|
-
#
|
|
344
|
+
# My Trip to Paris
|
|
320
345
|
|
|
321
|
-
|
|
346
|
+

|
|
322
347
|
|
|
323
|
-
|
|
324
|
-
|
|
348
|
+

|
|
349
|
+
|
|
350
|
+
## Evening Stroll
|
|
351
|
+
|
|
352
|
+
The Parisian streets at night are magical.
|
|
353
|
+
|
|
354
|
+

|
|
325
355
|
```
|
|
326
356
|
|
|
327
|
-
|
|
357
|
+
### Using Uploaded Videos in Markdown
|
|
328
358
|
|
|
329
|
-
|
|
359
|
+
Upload MP4 videos alongside your images and embed them in your posts:
|
|
330
360
|
|
|
331
|
-
|
|
361
|
+
```markdown
|
|
362
|
+
---
|
|
363
|
+
title: "Travel Vlog"
|
|
364
|
+
date: 2024-06-15T10:00:00
|
|
365
|
+
tags: [travel, video]
|
|
366
|
+
---
|
|
332
367
|
|
|
333
|
-
|
|
334
|
-
<source src="video.mp4" type="video/mp4">
|
|
335
|
-
<source src="video.webm" type="video/webm">
|
|
336
|
-
Your browser does not support the video tag.
|
|
337
|
-
</video>
|
|
338
|
-
````
|
|
368
|
+
# My Paris Adventure
|
|
339
369
|
|
|
340
|
-
|
|
370
|
+
Watch my trip to Paris:
|
|
341
371
|
|
|
342
|
-
|
|
372
|
+
<video controls width="640" height="360">
|
|
373
|
+
<source src="https://cdn.example.com/2024/paris-trip/travel-vlog.mp4" type="video/mp4">
|
|
374
|
+
Your browser does not support HTML5 video.
|
|
375
|
+
</video>
|
|
343
376
|
|
|
344
|
-
|
|
345
|
-
bunki new "Your Post Title" --tags "web-development, javascript"
|
|
346
|
-
````
|
|
377
|
+
## Behind the Scenes
|
|
347
378
|
|
|
348
|
-
|
|
379
|
+
Check out the making of the vlog:
|
|
349
380
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
381
|
+
<video controls width="640" height="360">
|
|
382
|
+
<source src="https://cdn.example.com/2024/paris-trip/behind-scenes.mp4" type="video/mp4">
|
|
383
|
+
Your browser does not support HTML5 video.
|
|
384
|
+
</video>
|
|
353
385
|
```
|
|
354
386
|
|
|
355
|
-
|
|
387
|
+
**Video Upload Example:**
|
|
356
388
|
|
|
357
389
|
```bash
|
|
358
|
-
#
|
|
359
|
-
bunki
|
|
390
|
+
# Upload all images and videos (including MP4 files)
|
|
391
|
+
bunki images:push
|
|
360
392
|
|
|
361
|
-
#
|
|
362
|
-
bunki
|
|
393
|
+
# Upload only 2024 videos and images
|
|
394
|
+
bunki images:push --min-year 2024
|
|
395
|
+
|
|
396
|
+
# Preview what would be uploaded without actually uploading
|
|
397
|
+
BUNKI_DRY_RUN=true bunki images:push --min-year 2024
|
|
363
398
|
```
|
|
364
399
|
|
|
365
|
-
|
|
400
|
+
**Video File Organization:**
|
|
366
401
|
|
|
367
|
-
|
|
402
|
+
Keep videos organized the same way as images for consistency:
|
|
368
403
|
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// CSS processing configuration
|
|
384
|
-
css: {
|
|
385
|
-
input: "templates/styles/main.css",
|
|
386
|
-
output: "css/style.css",
|
|
387
|
-
postcssConfig: "postcss.config.js",
|
|
388
|
-
enabled: true,
|
|
389
|
-
watch: false,
|
|
390
|
-
},
|
|
391
|
-
|
|
392
|
-
// Cloud storage configuration for images
|
|
393
|
-
publicUrl: process.env.R2_PUBLIC_URL,
|
|
394
|
-
s3: {
|
|
395
|
-
accessKeyId: process.env.R2_ACCESS_KEY_ID || "",
|
|
396
|
-
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || "",
|
|
397
|
-
bucket: process.env.R2_BUCKET || "",
|
|
398
|
-
endpoint: process.env.R2_ENDPOINT,
|
|
399
|
-
region: process.env.R2_REGION || "auto",
|
|
400
|
-
},
|
|
401
|
-
};
|
|
402
|
-
}
|
|
404
|
+
```
|
|
405
|
+
images/
|
|
406
|
+
├── 2024/
|
|
407
|
+
│ └── travel-vlog/
|
|
408
|
+
│ ├── intro.mp4
|
|
409
|
+
│ ├── highlights.mp4
|
|
410
|
+
│ ├── thumbnail.jpg
|
|
411
|
+
│ └── poster.jpg
|
|
412
|
+
└── 2025/
|
|
413
|
+
└── tutorial/
|
|
414
|
+
├── part-1.mp4
|
|
415
|
+
├── part-2.mp4
|
|
416
|
+
└── preview.jpg
|
|
403
417
|
```
|
|
404
418
|
|
|
405
|
-
|
|
419
|
+
**Video Tips:**
|
|
406
420
|
|
|
407
|
-
|
|
421
|
+
1. **File Size**: Keep MP4 files optimized (under 50MB recommended)
|
|
422
|
+
- Use tools like FFmpeg to compress before uploading
|
|
423
|
+
- Example: `ffmpeg -i input.mp4 -crf 28 output.mp4`
|
|
408
424
|
|
|
409
|
-
|
|
425
|
+
2. **Format & Codec**:
|
|
426
|
+
- Use H.264 video codec for best compatibility
|
|
427
|
+
- Use AAC audio codec
|
|
428
|
+
- Container: MP4 (.mp4 extension)
|
|
410
429
|
|
|
411
|
-
|
|
430
|
+
3. **Video Dimensions**:
|
|
431
|
+
- Keep 16:9 aspect ratio for web
|
|
432
|
+
- Common resolutions: 640x360, 1280x720, 1920x1080
|
|
412
433
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
```
|
|
434
|
+
4. **Hosting**:
|
|
435
|
+
- MP4s benefit from CDN caching via S3/R2
|
|
436
|
+
- Cloudflare R2 provides excellent video delivery
|
|
437
|
+
- AWS S3 with CloudFront for additional acceleration
|
|
438
|
+
|
|
439
|
+
### Dry Run Mode
|
|
420
440
|
|
|
421
|
-
|
|
441
|
+
Test the upload process without actually uploading:
|
|
422
442
|
|
|
423
|
-
```
|
|
424
|
-
|
|
443
|
+
```bash
|
|
444
|
+
# Preview what would be uploaded (no actual upload)
|
|
445
|
+
BUNKI_DRY_RUN=true bunki images:push
|
|
425
446
|
```
|
|
426
447
|
|
|
427
|
-
|
|
448
|
+
This shows:
|
|
428
449
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
450
|
+
- Which images would be uploaded
|
|
451
|
+
- The directory structure that would be created
|
|
452
|
+
- Generated public URLs
|
|
432
453
|
|
|
433
|
-
|
|
434
|
-
bunki images:push --images path/to/images
|
|
454
|
+
### Troubleshooting
|
|
435
455
|
|
|
436
|
-
|
|
437
|
-
bunki images:push --output-json image-urls.json
|
|
456
|
+
#### "Missing S3 configuration"
|
|
438
457
|
|
|
439
|
-
|
|
440
|
-
bunki images:push --domain example.com
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
Supported formats: **JPG**, **PNG**, **GIF**, **WebP**, **SVG**
|
|
444
|
-
|
|
445
|
-
## 📁 Directory Structure
|
|
446
|
-
|
|
447
|
-
```
|
|
448
|
-
my-blog/
|
|
449
|
-
├── bunki.config.ts # Site configuration
|
|
450
|
-
├── postcss.config.js # PostCSS configuration (optional)
|
|
451
|
-
├── tailwind.config.js # Tailwind config (if using Tailwind)
|
|
452
|
-
├── .env # Environment variables
|
|
453
|
-
├── content/ # Markdown content
|
|
454
|
-
│ └── 2025/ # Year-based organization
|
|
455
|
-
│ ├── my-first-post.md
|
|
456
|
-
│ └── another-post.md
|
|
457
|
-
├── templates/ # Nunjucks templates
|
|
458
|
-
│ ├── base.njk # Base layout
|
|
459
|
-
│ ├── index.njk # Homepage
|
|
460
|
-
│ ├── post.njk # Post template
|
|
461
|
-
│ ├── tag.njk # Tag page
|
|
462
|
-
│ ├── tags.njk # Tags index
|
|
463
|
-
│ ├── archive.njk # Year archive
|
|
464
|
-
│ └── styles/ # CSS source files
|
|
465
|
-
│ └── main.css # Main stylesheet
|
|
466
|
-
├── images/ # Local images (uploaded to cloud)
|
|
467
|
-
├── public/ # Static assets (copied to dist)
|
|
468
|
-
├── src/ # Tag descriptions
|
|
469
|
-
│ └── tags.toml # Tag metadata
|
|
470
|
-
└── dist/ # Generated site (output)
|
|
471
|
-
├── index.html
|
|
472
|
-
├── css/
|
|
473
|
-
│ └── style.css # Processed CSS
|
|
474
|
-
├── 2025/
|
|
475
|
-
│ └── my-first-post/
|
|
476
|
-
│ └── index.html
|
|
477
|
-
├── tags/
|
|
478
|
-
├── feed.xml # RSS feed
|
|
479
|
-
└── sitemap.xml # Sitemap
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
## 🚨 CLI Commands
|
|
458
|
+
Ensure all required environment variables are set. Check `bunki.config.ts` and your `.env` file.
|
|
483
459
|
|
|
484
|
-
|
|
485
|
-
Usage: bunki [options] [command]
|
|
460
|
+
#### "No image files found"
|
|
486
461
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
generate [options] Generate static site from content
|
|
491
|
-
css [options] Process CSS using PostCSS
|
|
492
|
-
serve [options] Start local development server
|
|
493
|
-
images:push [options] Upload images to cloud storage
|
|
494
|
-
help [command] Display help for command
|
|
462
|
+
- Verify images exist in `images/` directory
|
|
463
|
+
- Check that files have supported extensions (.jpg, .png, .gif, .webp, .svg)
|
|
464
|
+
- Ensure the directory structure is correct (e.g., `images/2024/post-slug/image.jpg`)
|
|
495
465
|
|
|
496
|
-
|
|
497
|
-
-V, --version Display version number
|
|
498
|
-
-h, --help Display help for command
|
|
499
|
-
```
|
|
466
|
+
#### "Unauthorized" or "Access Denied"
|
|
500
467
|
|
|
501
|
-
|
|
468
|
+
- Verify S3 credentials (access key and secret key)
|
|
469
|
+
- Check that the IAM user/API token has S3 permissions
|
|
470
|
+
- Confirm the bucket name is correct
|
|
502
471
|
|
|
503
|
-
|
|
504
|
-
# Initialize new site
|
|
505
|
-
bunki init --config custom.config.ts
|
|
472
|
+
#### "Invalid bucket name"
|
|
506
473
|
|
|
507
|
-
|
|
508
|
-
|
|
474
|
+
- S3 bucket names must be globally unique
|
|
475
|
+
- Use only lowercase letters, numbers, and hyphens
|
|
476
|
+
- Bucket names must be 3-63 characters long
|
|
509
477
|
|
|
510
|
-
|
|
511
|
-
bunki generate --config bunki.config.ts --output dist
|
|
478
|
+
### Advanced Configuration
|
|
512
479
|
|
|
513
|
-
|
|
514
|
-
bunki css --watch # Watch for changes
|
|
515
|
-
bunki css --output dist # Custom output directory
|
|
480
|
+
#### Custom Domain per Bucket
|
|
516
481
|
|
|
517
|
-
|
|
518
|
-
bunki serve --port 3000 # Custom port
|
|
519
|
-
bunki serve --output dist # Serve from custom directory
|
|
482
|
+
If you have multiple S3 buckets with different custom domains:
|
|
520
483
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
484
|
+
```bash
|
|
485
|
+
export S3_CUSTOM_DOMAIN_MY_BUCKET="cdn1.example.com"
|
|
486
|
+
export S3_CUSTOM_DOMAIN_BACKUP_BUCKET="cdn2.example.com"
|
|
524
487
|
```
|
|
525
488
|
|
|
526
|
-
|
|
489
|
+
The bucket name is converted to uppercase and hyphens to underscores for the environment variable name.
|
|
527
490
|
|
|
528
|
-
|
|
491
|
+
#### Direct CDN URLs
|
|
529
492
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
493
|
+
Configure public URLs with custom domains:
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
// bunki.config.ts
|
|
497
|
+
s3: {
|
|
498
|
+
// ... other config
|
|
499
|
+
publicUrl: "https://img.example.com",
|
|
500
|
+
}
|
|
534
501
|
```
|
|
535
502
|
|
|
536
|
-
|
|
503
|
+
Or via environment variable:
|
|
537
504
|
|
|
538
505
|
```bash
|
|
539
|
-
|
|
540
|
-
|
|
506
|
+
export S3_PUBLIC_URL="https://img.example.com"
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Performance Tips
|
|
541
510
|
|
|
542
|
-
|
|
543
|
-
bun run dev
|
|
511
|
+
1. **Use year-based filtering** for large image collections:
|
|
544
512
|
|
|
545
|
-
|
|
546
|
-
|
|
513
|
+
```bash
|
|
514
|
+
bunki images:push --min-year 2024 # Only newest images
|
|
515
|
+
```
|
|
547
516
|
|
|
548
|
-
|
|
549
|
-
bun test:coverage
|
|
517
|
+
2. **Organize by post slug** for better directory structure:
|
|
550
518
|
|
|
551
|
-
|
|
552
|
-
|
|
519
|
+
```
|
|
520
|
+
images/2024/post-title/image.jpg
|
|
521
|
+
images/2024/post-title/photo.jpg
|
|
522
|
+
```
|
|
553
523
|
|
|
554
|
-
|
|
555
|
-
|
|
524
|
+
3. **Compress images before uploading** to save storage:
|
|
525
|
+
- Use tools like `imagemin` or built-in OS utilities
|
|
526
|
+
- Aim for 500KB or smaller per image
|
|
527
|
+
|
|
528
|
+
4. **Use modern formats** (WebP) for better compression:
|
|
529
|
+
- JPG/PNG for screenshots
|
|
530
|
+
- WebP for photos
|
|
531
|
+
- SVG for icons/graphics
|
|
532
|
+
|
|
533
|
+
## CLI Commands
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
bunki init [--config FILE] # Initialize new site
|
|
537
|
+
bunki new <TITLE> [--tags TAG1,TAG2] # Create new post
|
|
538
|
+
bunki generate [--config FILE] # Build static site
|
|
539
|
+
bunki serve [--port 3000] # Start dev server
|
|
540
|
+
bunki css [--watch] # Process CSS
|
|
541
|
+
bunki images:push [--domain DOMAIN] # Upload images to cloud
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Output Structure
|
|
545
|
+
|
|
546
|
+
```
|
|
547
|
+
dist/
|
|
548
|
+
├── index.html # Homepage
|
|
549
|
+
├── feed.xml # RSS feed
|
|
550
|
+
├── sitemap.xml # XML sitemap
|
|
551
|
+
├── css/style.css # Processed stylesheet
|
|
552
|
+
├── 2025/
|
|
553
|
+
│ └── my-post/
|
|
554
|
+
│ └── index.html # Post page
|
|
555
|
+
├── tags/
|
|
556
|
+
│ └── web/
|
|
557
|
+
│ └── index.html # Tag page
|
|
558
|
+
└── page/
|
|
559
|
+
└── 2/index.html # Paginated content
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Features
|
|
563
|
+
|
|
564
|
+
- **Markdown Processing**: Frontmatter extraction, code highlighting, HTML sanitization
|
|
565
|
+
- **Security**: XSS protection, sanitized HTML, link hardening
|
|
566
|
+
- **Performance**: Static files, optional gzip, optimized output
|
|
567
|
+
- **Templating**: Nunjucks with custom filters and macros
|
|
568
|
+
- **Styling**: Built-in PostCSS support for modern CSS frameworks
|
|
569
|
+
- **Media Management**: Direct S3/R2 uploads for images and MP4 videos with URL mapping
|
|
570
|
+
- **Incremental Uploads**: Year-based filtering (`--min-year`) for large media collections
|
|
571
|
+
- **SEO**: Automatic RSS feeds, sitemaps, meta tags
|
|
572
|
+
- **Pagination**: Configurable posts per page
|
|
573
|
+
- **Archives**: Year-based and tag-based organization
|
|
574
|
+
|
|
575
|
+
## Development
|
|
556
576
|
|
|
557
|
-
|
|
558
|
-
|
|
577
|
+
```bash
|
|
578
|
+
git clone git@github.com:kahwee/bunki.git
|
|
579
|
+
cd bunki
|
|
580
|
+
bun install
|
|
559
581
|
|
|
560
|
-
|
|
561
|
-
bun
|
|
582
|
+
bun run build # Build distribution
|
|
583
|
+
bun test # Run test suite
|
|
584
|
+
bun test:coverage # Test coverage report
|
|
585
|
+
bun run typecheck # TypeScript validation
|
|
586
|
+
bun run format # Prettier formatting
|
|
562
587
|
```
|
|
563
588
|
|
|
564
|
-
|
|
589
|
+
## Project Structure
|
|
565
590
|
|
|
566
591
|
```
|
|
567
592
|
bunki/
|
|
568
593
|
├── src/
|
|
569
|
-
│ ├── cli.ts
|
|
570
|
-
│ ├── config.ts
|
|
571
|
-
│ ├── site-generator.ts
|
|
572
|
-
│ ├── server.ts
|
|
573
|
-
│ ├── parser.ts
|
|
574
|
-
│ ├── types.ts
|
|
575
|
-
│ └── utils/
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
│ └── s3-uploader.ts # S3 API client
|
|
581
|
-
├── test/ # Test files
|
|
582
|
-
├── fixtures/ # Test fixtures
|
|
583
|
-
└── dist/ # Built output
|
|
594
|
+
│ ├── cli.ts # CLI interface
|
|
595
|
+
│ ├── config.ts # Configuration management
|
|
596
|
+
│ ├── site-generator.ts # Core generation logic
|
|
597
|
+
│ ├── server.ts # Development server
|
|
598
|
+
│ ├── parser.ts # Markdown parsing
|
|
599
|
+
│ ├── types.ts # TypeScript types
|
|
600
|
+
│ └── utils/ # Utility modules
|
|
601
|
+
├── test/ # Test suite (mirrors src/)
|
|
602
|
+
├── templates/ # Example templates
|
|
603
|
+
├── fixtures/ # Test fixtures
|
|
604
|
+
└── dist/ # Built output
|
|
584
605
|
```
|
|
585
606
|
|
|
586
|
-
##
|
|
607
|
+
## Changelog
|
|
587
608
|
|
|
588
|
-
### v0.
|
|
609
|
+
### v0.7.0 (Current)
|
|
589
610
|
|
|
590
|
-
-
|
|
591
|
-
-
|
|
592
|
-
-
|
|
593
|
-
-
|
|
594
|
-
-
|
|
595
|
-
- 🐛 **FIXED**: Directory existence validation for development server
|
|
596
|
-
- 📚 **DOCS**: Comprehensive PostCSS integration guide
|
|
611
|
+
- **Media uploads**: Added MP4 video support alongside image uploads
|
|
612
|
+
- **Incremental uploads**: Year-based filtering with `--min-year` option
|
|
613
|
+
- **Enhanced documentation**: Comprehensive video upload guide with examples
|
|
614
|
+
- **Test coverage**: Added 10+ tests for image/video uploader functionality
|
|
615
|
+
- **Fixed timestamps**: Stable dates in test fixtures to prevent flipping
|
|
597
616
|
|
|
598
|
-
### v0.
|
|
617
|
+
### v0.6.1
|
|
599
618
|
|
|
600
|
-
-
|
|
601
|
-
-
|
|
602
|
-
-
|
|
619
|
+
- Version bump and welcome date stabilization
|
|
620
|
+
- Test formatting improvements
|
|
621
|
+
- Code style consistency updates
|
|
603
622
|
|
|
604
|
-
|
|
623
|
+
### v0.5.3
|
|
605
624
|
|
|
606
|
-
|
|
625
|
+
- Modularized CLI commands with dependency injection
|
|
626
|
+
- Enhanced test coverage (130+ tests, 539+ assertions)
|
|
627
|
+
- Fixed CLI entry point detection (Bun.main compatibility)
|
|
628
|
+
- Added comprehensive server tests using Bun.serve()
|
|
629
|
+
- Improved CSS processor with fallback support
|
|
607
630
|
|
|
608
|
-
###
|
|
631
|
+
### v0.3.0
|
|
609
632
|
|
|
610
|
-
-
|
|
611
|
-
-
|
|
612
|
-
-
|
|
613
|
-
-
|
|
614
|
-
- 🎨 Template improvements
|
|
633
|
+
- PostCSS integration with CSS processing command
|
|
634
|
+
- Framework-agnostic CSS support (Tailwind, etc.)
|
|
635
|
+
- CSS watch mode for development
|
|
636
|
+
- Better error handling and recovery
|
|
615
637
|
|
|
616
|
-
##
|
|
638
|
+
## Contributing
|
|
617
639
|
|
|
618
|
-
|
|
640
|
+
Contributions welcome! Areas for improvement:
|
|
619
641
|
|
|
620
|
-
|
|
642
|
+
- Bug fixes and error handling
|
|
643
|
+
- Documentation and examples
|
|
644
|
+
- Test coverage expansion
|
|
645
|
+
- Performance optimizations
|
|
646
|
+
- New features and plugins
|
|
621
647
|
|
|
622
|
-
##
|
|
648
|
+
## License
|
|
623
649
|
|
|
624
|
-
|
|
625
|
-
- 🐛 [Issues](https://github.com/kahwee/bunki/issues)
|
|
626
|
-
- 💬 [Discussions](https://github.com/kahwee/bunki/discussions)
|
|
650
|
+
MIT © [KahWee Teng](https://github.com/kahwee)
|
|
627
651
|
|
|
628
|
-
Built with
|
|
652
|
+
Built with [Bun](https://bun.sh)
|