bunki 0.6.1 → 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 +418 -14
- package/dist/cli/commands/images-push.d.ts +1 -0
- package/dist/cli.js +34 -9
- package/dist/index.js +30 -6
- package/dist/types.d.ts +3 -1
- package/dist/utils/s3-uploader.d.ts +1 -1
- package/package.json +1 -1
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
|
@@ -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,8 +33602,11 @@ class S3Uploader {
|
|
|
33595
33602
|
}
|
|
33596
33603
|
}
|
|
33597
33604
|
}
|
|
33598
|
-
async uploadImages(imagesDir) {
|
|
33605
|
+
async uploadImages(imagesDir, minYear) {
|
|
33599
33606
|
console.log(`[S3] Uploading all images from ${imagesDir} to bucket ${this.s3Config.bucket}...`);
|
|
33607
|
+
if (minYear) {
|
|
33608
|
+
console.log(`[S3] Filtering images from year ${minYear} onwards`);
|
|
33609
|
+
}
|
|
33600
33610
|
const imageUrls = {};
|
|
33601
33611
|
try {
|
|
33602
33612
|
console.log(`[S3] Checking if directory exists: ${imagesDir}`);
|
|
@@ -33639,8 +33649,19 @@ class S3Uploader {
|
|
|
33639
33649
|
cwd: imagesDir,
|
|
33640
33650
|
absolute: false
|
|
33641
33651
|
})) {
|
|
33642
|
-
|
|
33643
|
-
|
|
33652
|
+
if (minYear) {
|
|
33653
|
+
const yearMatch = file.match(/^(\d{4})\//);
|
|
33654
|
+
if (yearMatch) {
|
|
33655
|
+
const fileYear = parseInt(yearMatch[1], 10);
|
|
33656
|
+
if (fileYear >= minYear) {
|
|
33657
|
+
console.log(`[S3] Found image file: ${file}`);
|
|
33658
|
+
files.push(file);
|
|
33659
|
+
}
|
|
33660
|
+
}
|
|
33661
|
+
} else {
|
|
33662
|
+
console.log(`[S3] Found image file: ${file}`);
|
|
33663
|
+
files.push(file);
|
|
33664
|
+
}
|
|
33644
33665
|
}
|
|
33645
33666
|
const imageFiles = files;
|
|
33646
33667
|
if (imageFiles.length === 0) {
|
|
@@ -33715,8 +33736,11 @@ async function uploadImages(options2 = {}) {
|
|
|
33715
33736
|
};
|
|
33716
33737
|
}
|
|
33717
33738
|
console.log(`Uploading images from ${imagesDir} to bucket ${s3Config.bucket}`);
|
|
33739
|
+
if (options2.minYear) {
|
|
33740
|
+
console.log(`Filtering images from year ${options2.minYear} onwards`);
|
|
33741
|
+
}
|
|
33718
33742
|
const uploader = createUploader(s3Config);
|
|
33719
|
-
const imageUrlMap = await uploader.uploadImages(imagesDir);
|
|
33743
|
+
const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear);
|
|
33720
33744
|
if (options2.outputJson) {
|
|
33721
33745
|
const outputFile = path8.resolve(options2.outputJson);
|
|
33722
33746
|
await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
|
|
@@ -33734,7 +33758,7 @@ Image upload completed successfully!`);
|
|
|
33734
33758
|
return imageUrlMap;
|
|
33735
33759
|
} catch (error) {
|
|
33736
33760
|
console.error("Error uploading images:", error);
|
|
33737
|
-
|
|
33761
|
+
throw error;
|
|
33738
33762
|
}
|
|
33739
33763
|
}
|
|
33740
33764
|
|
|
@@ -33749,7 +33773,8 @@ async function handleImagesPushCommand(options2, deps = defaultDeps3) {
|
|
|
33749
33773
|
await deps.uploadImages({
|
|
33750
33774
|
domain: options2.domain,
|
|
33751
33775
|
images: options2.images,
|
|
33752
|
-
outputJson: options2.outputJson
|
|
33776
|
+
outputJson: options2.outputJson,
|
|
33777
|
+
minYear: options2.minYear ? parseInt(options2.minYear, 10) : undefined
|
|
33753
33778
|
});
|
|
33754
33779
|
} catch (error) {
|
|
33755
33780
|
deps.logger.error("Error uploading images:", error);
|
|
@@ -33757,7 +33782,7 @@ async function handleImagesPushCommand(options2, deps = defaultDeps3) {
|
|
|
33757
33782
|
}
|
|
33758
33783
|
}
|
|
33759
33784
|
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) => {
|
|
33785
|
+
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
33786
|
await handleImagesPushCommand(options2);
|
|
33762
33787
|
});
|
|
33763
33788
|
}
|
|
@@ -34234,7 +34259,7 @@ function getDefaultCss() {
|
|
|
34234
34259
|
function getSamplePost() {
|
|
34235
34260
|
return `---
|
|
34236
34261
|
title: Welcome to Bunki
|
|
34237
|
-
date:
|
|
34262
|
+
date: 2025-01-15T12:00:00Z
|
|
34238
34263
|
tags: [getting-started, bunki]
|
|
34239
34264
|
---
|
|
34240
34265
|
|
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,8 +31509,11 @@ class S3Uploader {
|
|
|
31502
31509
|
}
|
|
31503
31510
|
}
|
|
31504
31511
|
}
|
|
31505
|
-
async uploadImages(imagesDir) {
|
|
31512
|
+
async uploadImages(imagesDir, minYear) {
|
|
31506
31513
|
console.log(`[S3] Uploading all images from ${imagesDir} to bucket ${this.s3Config.bucket}...`);
|
|
31514
|
+
if (minYear) {
|
|
31515
|
+
console.log(`[S3] Filtering images from year ${minYear} onwards`);
|
|
31516
|
+
}
|
|
31507
31517
|
const imageUrls = {};
|
|
31508
31518
|
try {
|
|
31509
31519
|
console.log(`[S3] Checking if directory exists: ${imagesDir}`);
|
|
@@ -31546,8 +31556,19 @@ class S3Uploader {
|
|
|
31546
31556
|
cwd: imagesDir,
|
|
31547
31557
|
absolute: false
|
|
31548
31558
|
})) {
|
|
31549
|
-
|
|
31550
|
-
|
|
31559
|
+
if (minYear) {
|
|
31560
|
+
const yearMatch = file.match(/^(\d{4})\//);
|
|
31561
|
+
if (yearMatch) {
|
|
31562
|
+
const fileYear = parseInt(yearMatch[1], 10);
|
|
31563
|
+
if (fileYear >= minYear) {
|
|
31564
|
+
console.log(`[S3] Found image file: ${file}`);
|
|
31565
|
+
files.push(file);
|
|
31566
|
+
}
|
|
31567
|
+
}
|
|
31568
|
+
} else {
|
|
31569
|
+
console.log(`[S3] Found image file: ${file}`);
|
|
31570
|
+
files.push(file);
|
|
31571
|
+
}
|
|
31551
31572
|
}
|
|
31552
31573
|
const imageFiles = files;
|
|
31553
31574
|
if (imageFiles.length === 0) {
|
|
@@ -31622,8 +31643,11 @@ async function uploadImages(options2 = {}) {
|
|
|
31622
31643
|
};
|
|
31623
31644
|
}
|
|
31624
31645
|
console.log(`Uploading images from ${imagesDir} to bucket ${s3Config.bucket}`);
|
|
31646
|
+
if (options2.minYear) {
|
|
31647
|
+
console.log(`Filtering images from year ${options2.minYear} onwards`);
|
|
31648
|
+
}
|
|
31625
31649
|
const uploader = createUploader(s3Config);
|
|
31626
|
-
const imageUrlMap = await uploader.uploadImages(imagesDir);
|
|
31650
|
+
const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear);
|
|
31627
31651
|
if (options2.outputJson) {
|
|
31628
31652
|
const outputFile = path7.resolve(options2.outputJson);
|
|
31629
31653
|
await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
|
|
@@ -31641,7 +31665,7 @@ Image upload completed successfully!`);
|
|
|
31641
31665
|
return imageUrlMap;
|
|
31642
31666
|
} catch (error) {
|
|
31643
31667
|
console.error("Error uploading images:", error);
|
|
31644
|
-
|
|
31668
|
+
throw error;
|
|
31645
31669
|
}
|
|
31646
31670
|
}
|
|
31647
31671
|
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,7 @@ export declare class S3Uploader implements Uploader, ImageUploader {
|
|
|
13
13
|
* @returns The public URL for the file
|
|
14
14
|
*/
|
|
15
15
|
private getPublicUrl;
|
|
16
|
-
uploadImages(imagesDir: string): Promise<Record<string, string>>;
|
|
16
|
+
uploadImages(imagesDir: string, minYear?: number): Promise<Record<string, string>>;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Create an S3 uploader
|
package/package.json
CHANGED