bunki 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +418 -14
- package/dist/cli/commands/images-push.d.ts +1 -0
- package/dist/cli.js +76 -50
- package/dist/index.js +71 -46
- package/dist/types.d.ts +3 -1
- package/dist/utils/s3-uploader.d.ts +7 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://coveralls.io/github/kahwee/bunki?branch=main)
|
|
5
5
|
[](https://badge.fury.io/js/bunki)
|
|
6
6
|
|
|
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,
|
|
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.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -124,22 +124,411 @@ CSS is processed automatically during `bunki generate`.
|
|
|
124
124
|
|
|
125
125
|
## Image Management
|
|
126
126
|
|
|
127
|
-
|
|
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:**
|
|
132
|
+
|
|
133
|
+
- **Images:** JPG, JPEG, PNG, GIF, WebP, SVG
|
|
134
|
+
- **Video:** MP4
|
|
135
|
+
|
|
136
|
+
### Directory Structure
|
|
137
|
+
|
|
138
|
+
Organize images by year and post slug:
|
|
139
|
+
|
|
140
|
+
```
|
|
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
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The directory structure is preserved when uploading to cloud storage.
|
|
161
|
+
|
|
162
|
+
### Configuration
|
|
163
|
+
|
|
164
|
+
Add S3/R2 configuration to `bunki.config.ts`:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { SiteConfig } from "bunki";
|
|
168
|
+
|
|
169
|
+
export default (): SiteConfig => ({
|
|
170
|
+
title: "My Blog",
|
|
171
|
+
// ... other config
|
|
172
|
+
|
|
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
|
+
},
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Environment Variables
|
|
186
|
+
|
|
187
|
+
Set these in your `.env` file or export them in your shell:
|
|
188
|
+
|
|
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"
|
|
195
|
+
|
|
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"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Basic Usage
|
|
205
|
+
|
|
206
|
+
Upload all images:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
bunki images:push
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
This command:
|
|
213
|
+
|
|
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
|
|
218
|
+
|
|
219
|
+
### Command Options
|
|
220
|
+
|
|
221
|
+
#### `--images <dir>`
|
|
222
|
+
|
|
223
|
+
Specify a custom images directory (default: `./images`)
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
bunki images:push --images ./assets/images
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### `--domain <domain>`
|
|
230
|
+
|
|
231
|
+
Set a custom domain for bucket identification (optional)
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
bunki images:push --domain my-blog
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### `--output-json <file>`
|
|
238
|
+
|
|
239
|
+
Export a JSON mapping of filenames to their public URLs
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
bunki images:push --output-json image-urls.json
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
This creates a JSON file with the structure:
|
|
246
|
+
|
|
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"
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### `--min-year <year>`
|
|
255
|
+
|
|
256
|
+
Upload only images from the specified year onwards
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Upload only 2023 and 2024 images (skip 2021, 2022)
|
|
260
|
+
bunki images:push --min-year 2023
|
|
261
|
+
|
|
262
|
+
# Upload only 2024 and newer images
|
|
263
|
+
bunki images:push --min-year 2024
|
|
264
|
+
|
|
265
|
+
# Upload from 2022 onwards (all images in this example)
|
|
266
|
+
bunki images:push --min-year 2022
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
This is useful for:
|
|
270
|
+
|
|
271
|
+
- Incremental uploads (upload only new images)
|
|
272
|
+
- Testing uploads for specific years
|
|
273
|
+
- Managing large image collections across multiple uploads
|
|
274
|
+
|
|
275
|
+
### Complete Examples
|
|
276
|
+
|
|
277
|
+
#### Cloudflare R2 Setup
|
|
278
|
+
|
|
279
|
+
1. **Create R2 bucket and API token** in Cloudflare dashboard
|
|
280
|
+
|
|
281
|
+
2. **Set environment variables:**
|
|
282
|
+
|
|
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"
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
3. **Upload images:**
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
bunki images:push --output-json image-urls.json
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### AWS S3 Setup
|
|
299
|
+
|
|
300
|
+
1. **Create S3 bucket and IAM user** in AWS Console
|
|
301
|
+
|
|
302
|
+
2. **Set environment variables:**
|
|
303
|
+
|
|
304
|
+
```bash
|
|
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"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
3. **Upload images:**
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
bunki images:push
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Incremental Upload (Year-Based)
|
|
319
|
+
|
|
320
|
+
If you have thousands of images and want to upload them incrementally:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
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
|
|
328
|
+
|
|
329
|
+
# Finally, upload 2025 images
|
|
330
|
+
bunki images:push --min-year 2025
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Using Uploaded Images in Markdown
|
|
334
|
+
|
|
335
|
+
After uploading, reference images in your Markdown posts:
|
|
336
|
+
|
|
337
|
+
```markdown
|
|
338
|
+
---
|
|
339
|
+
title: "Paris Trip"
|
|
340
|
+
date: 2024-06-15T10:00:00
|
|
341
|
+
tags: [travel, france]
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
# My Trip to Paris
|
|
345
|
+
|
|
346
|
+

|
|
347
|
+
|
|
348
|
+

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

|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Using Uploaded Videos in Markdown
|
|
358
|
+
|
|
359
|
+
Upload MP4 videos alongside your images and embed them in your posts:
|
|
360
|
+
|
|
361
|
+
```markdown
|
|
362
|
+
---
|
|
363
|
+
title: "Travel Vlog"
|
|
364
|
+
date: 2024-06-15T10:00:00
|
|
365
|
+
tags: [travel, video]
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
# My Paris Adventure
|
|
369
|
+
|
|
370
|
+
Watch my trip to Paris:
|
|
371
|
+
|
|
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>
|
|
376
|
+
|
|
377
|
+
## Behind the Scenes
|
|
378
|
+
|
|
379
|
+
Check out the making of the vlog:
|
|
380
|
+
|
|
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>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Video Upload Example:**
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
# Upload all images and videos (including MP4 files)
|
|
391
|
+
bunki images:push
|
|
392
|
+
|
|
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
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Video File Organization:**
|
|
401
|
+
|
|
402
|
+
Keep videos organized the same way as images for consistency:
|
|
403
|
+
|
|
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
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Video Tips:**
|
|
420
|
+
|
|
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`
|
|
424
|
+
|
|
425
|
+
2. **Format & Codec**:
|
|
426
|
+
- Use H.264 video codec for best compatibility
|
|
427
|
+
- Use AAC audio codec
|
|
428
|
+
- Container: MP4 (.mp4 extension)
|
|
429
|
+
|
|
430
|
+
3. **Video Dimensions**:
|
|
431
|
+
- Keep 16:9 aspect ratio for web
|
|
432
|
+
- Common resolutions: 640x360, 1280x720, 1920x1080
|
|
433
|
+
|
|
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
|
|
440
|
+
|
|
441
|
+
Test the upload process without actually uploading:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
# Preview what would be uploaded (no actual upload)
|
|
445
|
+
BUNKI_DRY_RUN=true bunki images:push
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
This shows:
|
|
449
|
+
|
|
450
|
+
- Which images would be uploaded
|
|
451
|
+
- The directory structure that would be created
|
|
452
|
+
- Generated public URLs
|
|
453
|
+
|
|
454
|
+
### Troubleshooting
|
|
455
|
+
|
|
456
|
+
#### "Missing S3 configuration"
|
|
457
|
+
|
|
458
|
+
Ensure all required environment variables are set. Check `bunki.config.ts` and your `.env` file.
|
|
459
|
+
|
|
460
|
+
#### "No image files found"
|
|
461
|
+
|
|
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`)
|
|
465
|
+
|
|
466
|
+
#### "Unauthorized" or "Access Denied"
|
|
467
|
+
|
|
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
|
|
471
|
+
|
|
472
|
+
#### "Invalid bucket name"
|
|
473
|
+
|
|
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
|
|
477
|
+
|
|
478
|
+
### Advanced Configuration
|
|
479
|
+
|
|
480
|
+
#### Custom Domain per Bucket
|
|
481
|
+
|
|
482
|
+
If you have multiple S3 buckets with different custom domains:
|
|
128
483
|
|
|
129
484
|
```bash
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
485
|
+
export S3_CUSTOM_DOMAIN_MY_BUCKET="cdn1.example.com"
|
|
486
|
+
export S3_CUSTOM_DOMAIN_BACKUP_BUCKET="cdn2.example.com"
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
The bucket name is converted to uppercase and hyphens to underscores for the environment variable name.
|
|
490
|
+
|
|
491
|
+
#### Direct CDN URLs
|
|
492
|
+
|
|
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
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Or via environment variable:
|
|
137
504
|
|
|
138
|
-
|
|
139
|
-
|
|
505
|
+
```bash
|
|
506
|
+
export S3_PUBLIC_URL="https://img.example.com"
|
|
140
507
|
```
|
|
141
508
|
|
|
142
|
-
|
|
509
|
+
### Performance Tips
|
|
510
|
+
|
|
511
|
+
1. **Use year-based filtering** for large image collections:
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
bunki images:push --min-year 2024 # Only newest images
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
2. **Organize by post slug** for better directory structure:
|
|
518
|
+
|
|
519
|
+
```
|
|
520
|
+
images/2024/post-title/image.jpg
|
|
521
|
+
images/2024/post-title/photo.jpg
|
|
522
|
+
```
|
|
523
|
+
|
|
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
|
|
143
532
|
|
|
144
533
|
## CLI Commands
|
|
145
534
|
|
|
@@ -177,7 +566,8 @@ dist/
|
|
|
177
566
|
- **Performance**: Static files, optional gzip, optimized output
|
|
178
567
|
- **Templating**: Nunjucks with custom filters and macros
|
|
179
568
|
- **Styling**: Built-in PostCSS support for modern CSS frameworks
|
|
180
|
-
- **
|
|
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
|
|
181
571
|
- **SEO**: Automatic RSS feeds, sitemaps, meta tags
|
|
182
572
|
- **Pagination**: Configurable posts per page
|
|
183
573
|
- **Archives**: Year-based and tag-based organization
|
|
@@ -216,7 +606,21 @@ bunki/
|
|
|
216
606
|
|
|
217
607
|
## Changelog
|
|
218
608
|
|
|
219
|
-
### v0.
|
|
609
|
+
### v0.7.0 (Current)
|
|
610
|
+
|
|
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
|
|
616
|
+
|
|
617
|
+
### v0.6.1
|
|
618
|
+
|
|
619
|
+
- Version bump and welcome date stabilization
|
|
620
|
+
- Test formatting improvements
|
|
621
|
+
- Code style consistency updates
|
|
622
|
+
|
|
623
|
+
### v0.5.3
|
|
220
624
|
|
|
221
625
|
- Modularized CLI commands with dependency injection
|
|
222
626
|
- Enhanced test coverage (130+ tests, 539+ assertions)
|
|
@@ -9,6 +9,7 @@ export declare function handleImagesPushCommand(options: {
|
|
|
9
9
|
domain?: string;
|
|
10
10
|
images: string;
|
|
11
11
|
outputJson?: string;
|
|
12
|
+
minYear?: string;
|
|
12
13
|
}, deps?: ImagesPushDeps): Promise<void>;
|
|
13
14
|
export declare function registerImagesPushCommand(program: Command): Command;
|
|
14
15
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1596,7 +1596,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1596
1596
|
return arg.length > 1 && arg[0] === "-";
|
|
1597
1597
|
}
|
|
1598
1598
|
const negativeNumberArg = (arg) => {
|
|
1599
|
-
if (
|
|
1599
|
+
if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg))
|
|
1600
1600
|
return false;
|
|
1601
1601
|
return !this._getCommandAndAncestors().some((cmd) => cmd.options.map((opt) => opt.short).some((short) => /^-\d$/.test(short)));
|
|
1602
1602
|
};
|
|
@@ -28255,7 +28255,14 @@ async function processCSS(options2) {
|
|
|
28255
28255
|
}
|
|
28256
28256
|
function runPostCSS(inputPath, outputPath, configPath, projectRoot, verbose) {
|
|
28257
28257
|
return new Promise((resolve, reject) => {
|
|
28258
|
-
const args = [
|
|
28258
|
+
const args = [
|
|
28259
|
+
"postcss",
|
|
28260
|
+
inputPath,
|
|
28261
|
+
"-o",
|
|
28262
|
+
outputPath,
|
|
28263
|
+
"--config",
|
|
28264
|
+
configPath
|
|
28265
|
+
];
|
|
28259
28266
|
const postcss = spawn("bunx", args, {
|
|
28260
28267
|
stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
28261
28268
|
cwd: projectRoot
|
|
@@ -33595,52 +33602,59 @@ class S3Uploader {
|
|
|
33595
33602
|
}
|
|
33596
33603
|
}
|
|
33597
33604
|
}
|
|
33598
|
-
async
|
|
33605
|
+
async executeWithConcurrency(tasks, concurrency) {
|
|
33606
|
+
const results = [];
|
|
33607
|
+
const executing = [];
|
|
33608
|
+
for (const task of tasks) {
|
|
33609
|
+
const promise = task().then((result) => {
|
|
33610
|
+
results.push(result);
|
|
33611
|
+
const index = executing.indexOf(promise);
|
|
33612
|
+
if (index > -1)
|
|
33613
|
+
executing.splice(index, 1);
|
|
33614
|
+
}).catch((error) => {
|
|
33615
|
+
const index = executing.indexOf(promise);
|
|
33616
|
+
if (index > -1)
|
|
33617
|
+
executing.splice(index, 1);
|
|
33618
|
+
});
|
|
33619
|
+
executing.push(promise);
|
|
33620
|
+
if (executing.length >= concurrency) {
|
|
33621
|
+
await Promise.race(executing);
|
|
33622
|
+
}
|
|
33623
|
+
}
|
|
33624
|
+
await Promise.all(executing);
|
|
33625
|
+
return results;
|
|
33626
|
+
}
|
|
33627
|
+
async uploadImages(imagesDir, minYear) {
|
|
33599
33628
|
console.log(`[S3] Uploading all images from ${imagesDir} to bucket ${this.s3Config.bucket}...`);
|
|
33629
|
+
if (minYear) {
|
|
33630
|
+
console.log(`[S3] Filtering images from year ${minYear} onwards`);
|
|
33631
|
+
}
|
|
33600
33632
|
const imageUrls = {};
|
|
33601
33633
|
try {
|
|
33602
|
-
console.log(`[S3] Checking if directory exists: ${imagesDir}`);
|
|
33603
|
-
try {
|
|
33604
|
-
const glob2 = new Bun.Glob("**/*");
|
|
33605
|
-
let hasContent = false;
|
|
33606
|
-
for await (const file of glob2.scan({
|
|
33607
|
-
cwd: imagesDir,
|
|
33608
|
-
absolute: false
|
|
33609
|
-
})) {
|
|
33610
|
-
hasContent = true;
|
|
33611
|
-
break;
|
|
33612
|
-
}
|
|
33613
|
-
if (!hasContent) {
|
|
33614
|
-
console.warn(`Directory exists but is empty: ${imagesDir}`);
|
|
33615
|
-
}
|
|
33616
|
-
console.log(`[S3] Directory exists and is accessible`);
|
|
33617
|
-
} catch (err) {
|
|
33618
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
33619
|
-
console.warn(`No images directory found at ${imagesDir}, skipping image upload. Error: ${errorMessage}`);
|
|
33620
|
-
return imageUrls;
|
|
33621
|
-
}
|
|
33622
33634
|
const glob = new Bun.Glob("**/*.{jpg,jpeg,png,gif,webp,svg}");
|
|
33623
33635
|
const files = [];
|
|
33624
33636
|
console.log(`[S3] Scanning directory ${imagesDir} for image files...`);
|
|
33625
33637
|
try {
|
|
33626
|
-
const
|
|
33627
|
-
const allFiles = [];
|
|
33628
|
-
for await (const file of dirGlob.scan({
|
|
33638
|
+
for await (const file of glob.scan({
|
|
33629
33639
|
cwd: imagesDir,
|
|
33630
33640
|
absolute: false
|
|
33631
33641
|
})) {
|
|
33632
|
-
|
|
33642
|
+
if (minYear) {
|
|
33643
|
+
const yearMatch = file.match(/^(\d{4})\//);
|
|
33644
|
+
if (yearMatch) {
|
|
33645
|
+
const fileYear = parseInt(yearMatch[1], 10);
|
|
33646
|
+
if (fileYear >= minYear) {
|
|
33647
|
+
files.push(file);
|
|
33648
|
+
}
|
|
33649
|
+
}
|
|
33650
|
+
} else {
|
|
33651
|
+
files.push(file);
|
|
33652
|
+
}
|
|
33633
33653
|
}
|
|
33634
|
-
console.log(`[S3] Files in directory (including subdirs): ${allFiles.length > 0 ? allFiles.slice(0, 10).join(", ") + (allFiles.length > 10 ? "..." : "") : "none"}`);
|
|
33635
33654
|
} catch (err) {
|
|
33636
|
-
|
|
33637
|
-
|
|
33638
|
-
|
|
33639
|
-
cwd: imagesDir,
|
|
33640
|
-
absolute: false
|
|
33641
|
-
})) {
|
|
33642
|
-
console.log(`[S3] Found image file: ${file}`);
|
|
33643
|
-
files.push(file);
|
|
33655
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
33656
|
+
console.warn(`Error scanning images directory: ${errorMessage}`);
|
|
33657
|
+
return imageUrls;
|
|
33644
33658
|
}
|
|
33645
33659
|
const imageFiles = files;
|
|
33646
33660
|
if (imageFiles.length === 0) {
|
|
@@ -33648,27 +33662,35 @@ class S3Uploader {
|
|
|
33648
33662
|
return imageUrls;
|
|
33649
33663
|
}
|
|
33650
33664
|
console.log(`Found ${imageFiles.length} images to upload`);
|
|
33651
|
-
|
|
33665
|
+
console.log(`[S3] Processing with 10 concurrent uploads...`);
|
|
33666
|
+
const concurrencyLimit = 10;
|
|
33667
|
+
let uploadedCount = 0;
|
|
33668
|
+
let failedCount = 0;
|
|
33669
|
+
const uploadTasks = imageFiles.map((imageFile) => async () => {
|
|
33652
33670
|
try {
|
|
33653
33671
|
const imagePath = path7.join(imagesDir, imageFile);
|
|
33654
33672
|
const filename = path7.basename(imagePath);
|
|
33655
|
-
console.log(`[S3] Uploading image ${imagePath} to S3 bucket ${this.s3Config.bucket}/${imageFile}...`);
|
|
33656
33673
|
const file = Bun.file(imagePath);
|
|
33657
33674
|
const contentType = file.type;
|
|
33658
|
-
if (process.env.BUNKI_DRY_RUN === "true") {
|
|
33659
|
-
console.log(`[S3] Dry run: would upload ${imageFile} with content type ${contentType}`);
|
|
33660
|
-
} else {
|
|
33675
|
+
if (process.env.BUNKI_DRY_RUN === "true") {} else {
|
|
33661
33676
|
const s3File = this.client.file(imageFile);
|
|
33662
33677
|
await s3File.write(file);
|
|
33663
33678
|
}
|
|
33664
33679
|
const imageUrl = this.getPublicUrl(imageFile);
|
|
33665
|
-
console.log(`[S3] Image uploaded to ${imageUrl}`);
|
|
33666
33680
|
imageUrls[imageFile] = imageUrl;
|
|
33681
|
+
uploadedCount++;
|
|
33682
|
+
if (uploadedCount % 10 === 0) {
|
|
33683
|
+
console.log(`[S3] Progress: ${uploadedCount}/${imageFiles.length} images uploaded`);
|
|
33684
|
+
}
|
|
33685
|
+
return { success: true, file: imageFile };
|
|
33667
33686
|
} catch (error) {
|
|
33668
|
-
|
|
33687
|
+
failedCount++;
|
|
33688
|
+
console.error(`[S3] Error uploading ${imageFile}:`, error);
|
|
33689
|
+
return { success: false, file: imageFile };
|
|
33669
33690
|
}
|
|
33670
|
-
}
|
|
33671
|
-
|
|
33691
|
+
});
|
|
33692
|
+
await this.executeWithConcurrency(uploadTasks, concurrencyLimit);
|
|
33693
|
+
console.log(`[S3] Upload complete: ${uploadedCount} succeeded, ${failedCount} failed out of ${imageFiles.length} images`);
|
|
33672
33694
|
return imageUrls;
|
|
33673
33695
|
} catch (error) {
|
|
33674
33696
|
console.error(`Error uploading images:`, error);
|
|
@@ -33715,8 +33737,11 @@ async function uploadImages(options2 = {}) {
|
|
|
33715
33737
|
};
|
|
33716
33738
|
}
|
|
33717
33739
|
console.log(`Uploading images from ${imagesDir} to bucket ${s3Config.bucket}`);
|
|
33740
|
+
if (options2.minYear) {
|
|
33741
|
+
console.log(`Filtering images from year ${options2.minYear} onwards`);
|
|
33742
|
+
}
|
|
33718
33743
|
const uploader = createUploader(s3Config);
|
|
33719
|
-
const imageUrlMap = await uploader.uploadImages(imagesDir);
|
|
33744
|
+
const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear);
|
|
33720
33745
|
if (options2.outputJson) {
|
|
33721
33746
|
const outputFile = path8.resolve(options2.outputJson);
|
|
33722
33747
|
await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
|
|
@@ -33734,7 +33759,7 @@ Image upload completed successfully!`);
|
|
|
33734
33759
|
return imageUrlMap;
|
|
33735
33760
|
} catch (error) {
|
|
33736
33761
|
console.error("Error uploading images:", error);
|
|
33737
|
-
|
|
33762
|
+
throw error;
|
|
33738
33763
|
}
|
|
33739
33764
|
}
|
|
33740
33765
|
|
|
@@ -33749,7 +33774,8 @@ async function handleImagesPushCommand(options2, deps = defaultDeps3) {
|
|
|
33749
33774
|
await deps.uploadImages({
|
|
33750
33775
|
domain: options2.domain,
|
|
33751
33776
|
images: options2.images,
|
|
33752
|
-
outputJson: options2.outputJson
|
|
33777
|
+
outputJson: options2.outputJson,
|
|
33778
|
+
minYear: options2.minYear ? parseInt(options2.minYear, 10) : undefined
|
|
33753
33779
|
});
|
|
33754
33780
|
} catch (error) {
|
|
33755
33781
|
deps.logger.error("Error uploading images:", error);
|
|
@@ -33757,7 +33783,7 @@ async function handleImagesPushCommand(options2, deps = defaultDeps3) {
|
|
|
33757
33783
|
}
|
|
33758
33784
|
}
|
|
33759
33785
|
function registerImagesPushCommand(program2) {
|
|
33760
|
-
return program2.command("images:push").description("Upload images to S3-compatible storage").option("-d, --domain <domain>", "Domain name for bucket identification (defaults to domain in bunki.config.ts)").option("-i, --images <dir>", "Images directory path", DEFAULT_IMAGES_DIR).option("--output-json <file>", "Output URL mapping to JSON file").action(async (options2) => {
|
|
33786
|
+
return program2.command("images:push").description("Upload images to S3-compatible storage").option("-d, --domain <domain>", "Domain name for bucket identification (defaults to domain in bunki.config.ts)").option("-i, --images <dir>", "Images directory path", DEFAULT_IMAGES_DIR).option("--output-json <file>", "Output URL mapping to JSON file").option("--min-year <year>", "Only upload images from the specified year onwards (e.g., 2023 uploads 2023, 2024, etc.)").action(async (options2) => {
|
|
33761
33787
|
await handleImagesPushCommand(options2);
|
|
33762
33788
|
});
|
|
33763
33789
|
}
|
|
@@ -34234,7 +34260,7 @@ function getDefaultCss() {
|
|
|
34234
34260
|
function getSamplePost() {
|
|
34235
34261
|
return `---
|
|
34236
34262
|
title: Welcome to Bunki
|
|
34237
|
-
date:
|
|
34263
|
+
date: 2025-01-15T12:00:00Z
|
|
34238
34264
|
tags: [getting-started, bunki]
|
|
34239
34265
|
---
|
|
34240
34266
|
|
package/dist/index.js
CHANGED
|
@@ -30815,7 +30815,14 @@ async function processCSS(options2) {
|
|
|
30815
30815
|
}
|
|
30816
30816
|
function runPostCSS(inputPath, outputPath, configPath, projectRoot, verbose) {
|
|
30817
30817
|
return new Promise((resolve, reject) => {
|
|
30818
|
-
const args = [
|
|
30818
|
+
const args = [
|
|
30819
|
+
"postcss",
|
|
30820
|
+
inputPath,
|
|
30821
|
+
"-o",
|
|
30822
|
+
outputPath,
|
|
30823
|
+
"--config",
|
|
30824
|
+
configPath
|
|
30825
|
+
];
|
|
30819
30826
|
const postcss = spawn("bunx", args, {
|
|
30820
30827
|
stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
30821
30828
|
cwd: projectRoot
|
|
@@ -31502,52 +31509,59 @@ class S3Uploader {
|
|
|
31502
31509
|
}
|
|
31503
31510
|
}
|
|
31504
31511
|
}
|
|
31505
|
-
async
|
|
31512
|
+
async executeWithConcurrency(tasks, concurrency) {
|
|
31513
|
+
const results = [];
|
|
31514
|
+
const executing = [];
|
|
31515
|
+
for (const task of tasks) {
|
|
31516
|
+
const promise = task().then((result) => {
|
|
31517
|
+
results.push(result);
|
|
31518
|
+
const index = executing.indexOf(promise);
|
|
31519
|
+
if (index > -1)
|
|
31520
|
+
executing.splice(index, 1);
|
|
31521
|
+
}).catch((error) => {
|
|
31522
|
+
const index = executing.indexOf(promise);
|
|
31523
|
+
if (index > -1)
|
|
31524
|
+
executing.splice(index, 1);
|
|
31525
|
+
});
|
|
31526
|
+
executing.push(promise);
|
|
31527
|
+
if (executing.length >= concurrency) {
|
|
31528
|
+
await Promise.race(executing);
|
|
31529
|
+
}
|
|
31530
|
+
}
|
|
31531
|
+
await Promise.all(executing);
|
|
31532
|
+
return results;
|
|
31533
|
+
}
|
|
31534
|
+
async uploadImages(imagesDir, minYear) {
|
|
31506
31535
|
console.log(`[S3] Uploading all images from ${imagesDir} to bucket ${this.s3Config.bucket}...`);
|
|
31536
|
+
if (minYear) {
|
|
31537
|
+
console.log(`[S3] Filtering images from year ${minYear} onwards`);
|
|
31538
|
+
}
|
|
31507
31539
|
const imageUrls = {};
|
|
31508
31540
|
try {
|
|
31509
|
-
console.log(`[S3] Checking if directory exists: ${imagesDir}`);
|
|
31510
|
-
try {
|
|
31511
|
-
const glob2 = new Bun.Glob("**/*");
|
|
31512
|
-
let hasContent = false;
|
|
31513
|
-
for await (const file of glob2.scan({
|
|
31514
|
-
cwd: imagesDir,
|
|
31515
|
-
absolute: false
|
|
31516
|
-
})) {
|
|
31517
|
-
hasContent = true;
|
|
31518
|
-
break;
|
|
31519
|
-
}
|
|
31520
|
-
if (!hasContent) {
|
|
31521
|
-
console.warn(`Directory exists but is empty: ${imagesDir}`);
|
|
31522
|
-
}
|
|
31523
|
-
console.log(`[S3] Directory exists and is accessible`);
|
|
31524
|
-
} catch (err) {
|
|
31525
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
31526
|
-
console.warn(`No images directory found at ${imagesDir}, skipping image upload. Error: ${errorMessage}`);
|
|
31527
|
-
return imageUrls;
|
|
31528
|
-
}
|
|
31529
31541
|
const glob = new Bun.Glob("**/*.{jpg,jpeg,png,gif,webp,svg}");
|
|
31530
31542
|
const files = [];
|
|
31531
31543
|
console.log(`[S3] Scanning directory ${imagesDir} for image files...`);
|
|
31532
31544
|
try {
|
|
31533
|
-
const
|
|
31534
|
-
const allFiles = [];
|
|
31535
|
-
for await (const file of dirGlob.scan({
|
|
31545
|
+
for await (const file of glob.scan({
|
|
31536
31546
|
cwd: imagesDir,
|
|
31537
31547
|
absolute: false
|
|
31538
31548
|
})) {
|
|
31539
|
-
|
|
31549
|
+
if (minYear) {
|
|
31550
|
+
const yearMatch = file.match(/^(\d{4})\//);
|
|
31551
|
+
if (yearMatch) {
|
|
31552
|
+
const fileYear = parseInt(yearMatch[1], 10);
|
|
31553
|
+
if (fileYear >= minYear) {
|
|
31554
|
+
files.push(file);
|
|
31555
|
+
}
|
|
31556
|
+
}
|
|
31557
|
+
} else {
|
|
31558
|
+
files.push(file);
|
|
31559
|
+
}
|
|
31540
31560
|
}
|
|
31541
|
-
console.log(`[S3] Files in directory (including subdirs): ${allFiles.length > 0 ? allFiles.slice(0, 10).join(", ") + (allFiles.length > 10 ? "..." : "") : "none"}`);
|
|
31542
31561
|
} catch (err) {
|
|
31543
|
-
|
|
31544
|
-
|
|
31545
|
-
|
|
31546
|
-
cwd: imagesDir,
|
|
31547
|
-
absolute: false
|
|
31548
|
-
})) {
|
|
31549
|
-
console.log(`[S3] Found image file: ${file}`);
|
|
31550
|
-
files.push(file);
|
|
31562
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
31563
|
+
console.warn(`Error scanning images directory: ${errorMessage}`);
|
|
31564
|
+
return imageUrls;
|
|
31551
31565
|
}
|
|
31552
31566
|
const imageFiles = files;
|
|
31553
31567
|
if (imageFiles.length === 0) {
|
|
@@ -31555,27 +31569,35 @@ class S3Uploader {
|
|
|
31555
31569
|
return imageUrls;
|
|
31556
31570
|
}
|
|
31557
31571
|
console.log(`Found ${imageFiles.length} images to upload`);
|
|
31558
|
-
|
|
31572
|
+
console.log(`[S3] Processing with 10 concurrent uploads...`);
|
|
31573
|
+
const concurrencyLimit = 10;
|
|
31574
|
+
let uploadedCount = 0;
|
|
31575
|
+
let failedCount = 0;
|
|
31576
|
+
const uploadTasks = imageFiles.map((imageFile) => async () => {
|
|
31559
31577
|
try {
|
|
31560
31578
|
const imagePath = path6.join(imagesDir, imageFile);
|
|
31561
31579
|
const filename = path6.basename(imagePath);
|
|
31562
|
-
console.log(`[S3] Uploading image ${imagePath} to S3 bucket ${this.s3Config.bucket}/${imageFile}...`);
|
|
31563
31580
|
const file = Bun.file(imagePath);
|
|
31564
31581
|
const contentType = file.type;
|
|
31565
|
-
if (process.env.BUNKI_DRY_RUN === "true") {
|
|
31566
|
-
console.log(`[S3] Dry run: would upload ${imageFile} with content type ${contentType}`);
|
|
31567
|
-
} else {
|
|
31582
|
+
if (process.env.BUNKI_DRY_RUN === "true") {} else {
|
|
31568
31583
|
const s3File = this.client.file(imageFile);
|
|
31569
31584
|
await s3File.write(file);
|
|
31570
31585
|
}
|
|
31571
31586
|
const imageUrl = this.getPublicUrl(imageFile);
|
|
31572
|
-
console.log(`[S3] Image uploaded to ${imageUrl}`);
|
|
31573
31587
|
imageUrls[imageFile] = imageUrl;
|
|
31588
|
+
uploadedCount++;
|
|
31589
|
+
if (uploadedCount % 10 === 0) {
|
|
31590
|
+
console.log(`[S3] Progress: ${uploadedCount}/${imageFiles.length} images uploaded`);
|
|
31591
|
+
}
|
|
31592
|
+
return { success: true, file: imageFile };
|
|
31574
31593
|
} catch (error) {
|
|
31575
|
-
|
|
31594
|
+
failedCount++;
|
|
31595
|
+
console.error(`[S3] Error uploading ${imageFile}:`, error);
|
|
31596
|
+
return { success: false, file: imageFile };
|
|
31576
31597
|
}
|
|
31577
|
-
}
|
|
31578
|
-
|
|
31598
|
+
});
|
|
31599
|
+
await this.executeWithConcurrency(uploadTasks, concurrencyLimit);
|
|
31600
|
+
console.log(`[S3] Upload complete: ${uploadedCount} succeeded, ${failedCount} failed out of ${imageFiles.length} images`);
|
|
31579
31601
|
return imageUrls;
|
|
31580
31602
|
} catch (error) {
|
|
31581
31603
|
console.error(`Error uploading images:`, error);
|
|
@@ -31622,8 +31644,11 @@ async function uploadImages(options2 = {}) {
|
|
|
31622
31644
|
};
|
|
31623
31645
|
}
|
|
31624
31646
|
console.log(`Uploading images from ${imagesDir} to bucket ${s3Config.bucket}`);
|
|
31647
|
+
if (options2.minYear) {
|
|
31648
|
+
console.log(`Filtering images from year ${options2.minYear} onwards`);
|
|
31649
|
+
}
|
|
31625
31650
|
const uploader = createUploader(s3Config);
|
|
31626
|
-
const imageUrlMap = await uploader.uploadImages(imagesDir);
|
|
31651
|
+
const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear);
|
|
31627
31652
|
if (options2.outputJson) {
|
|
31628
31653
|
const outputFile = path7.resolve(options2.outputJson);
|
|
31629
31654
|
await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
|
|
@@ -31641,7 +31666,7 @@ Image upload completed successfully!`);
|
|
|
31641
31666
|
return imageUrlMap;
|
|
31642
31667
|
} catch (error) {
|
|
31643
31668
|
console.error("Error uploading images:", error);
|
|
31644
|
-
|
|
31669
|
+
throw error;
|
|
31645
31670
|
}
|
|
31646
31671
|
}
|
|
31647
31672
|
export {
|
package/dist/types.d.ts
CHANGED
|
@@ -146,9 +146,10 @@ export interface ImageUploader {
|
|
|
146
146
|
/**
|
|
147
147
|
* Upload all images from a directory
|
|
148
148
|
* @param imagesDir Directory containing images to upload
|
|
149
|
+
* @param minYear Optional minimum year to filter (e.g., 2023 uploads 2023, 2024, etc.)
|
|
149
150
|
* @returns Record of image filenames to their public URLs
|
|
150
151
|
*/
|
|
151
|
-
uploadImages(imagesDir: string): Promise<Record<string, string>>;
|
|
152
|
+
uploadImages(imagesDir: string, minYear?: number): Promise<Record<string, string>>;
|
|
152
153
|
}
|
|
153
154
|
/**
|
|
154
155
|
* S3 configuration type
|
|
@@ -169,4 +170,5 @@ export interface ImageUploadOptions {
|
|
|
169
170
|
domain?: string;
|
|
170
171
|
images?: string;
|
|
171
172
|
outputJson?: string;
|
|
173
|
+
minYear?: number;
|
|
172
174
|
}
|
|
@@ -13,7 +13,13 @@ export declare class S3Uploader implements Uploader, ImageUploader {
|
|
|
13
13
|
* @returns The public URL for the file
|
|
14
14
|
*/
|
|
15
15
|
private getPublicUrl;
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Execute async tasks with concurrency limit
|
|
18
|
+
* @param tasks Array of task functions that return promises
|
|
19
|
+
* @param concurrency Maximum number of concurrent tasks
|
|
20
|
+
*/
|
|
21
|
+
private executeWithConcurrency;
|
|
22
|
+
uploadImages(imagesDir: string, minYear?: number): Promise<Record<string, string>>;
|
|
17
23
|
}
|
|
18
24
|
/**
|
|
19
25
|
* Create an S3 uploader
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "An opinionated static site generator built with Bun featuring PostCSS integration and modern web development workflows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"homepage": "https://github.com/kahwee/bunki#readme",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"commander": "^14.0.
|
|
50
|
+
"commander": "^14.0.2",
|
|
51
51
|
"gray-matter": "^4.0.3",
|
|
52
52
|
"highlight.js": "^11.11.1",
|
|
53
53
|
"marked": "^16.4.1",
|
|
@@ -59,15 +59,15 @@
|
|
|
59
59
|
"slugify": "^1.6.6"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@tailwindcss/postcss": "^4.1.
|
|
62
|
+
"@tailwindcss/postcss": "^4.1.16",
|
|
63
63
|
"@types/nunjucks": "^3.2.6",
|
|
64
64
|
"@types/sanitize-html": "^2.16.0",
|
|
65
|
-
"autoprefixer": "^10.4.
|
|
66
|
-
"bun-types": "^1.3.
|
|
65
|
+
"autoprefixer": "^10.4.21",
|
|
66
|
+
"bun-types": "^1.3.1",
|
|
67
67
|
"husky": "^9.1.7",
|
|
68
|
-
"lint-staged": "^16.2.
|
|
68
|
+
"lint-staged": "^16.2.6",
|
|
69
69
|
"prettier": "^3.6.2",
|
|
70
|
-
"tailwindcss": "^4.1.
|
|
70
|
+
"tailwindcss": "^4.1.16",
|
|
71
71
|
"typescript": "^5.9.3"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|