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 CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Coverage Status](https://coveralls.io/repos/github/kahwee/bunki/badge.svg?branch=main)](https://coveralls.io/github/kahwee/bunki?branch=main)
5
5
  [![npm version](https://badge.fury.io/js/bunki.svg)](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, image uploads (S3/R2), and Nunjucks templating.
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
- Upload images to Cloudflare R2 or S3:
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
+ ![Eiffel Tower at sunset](https://cdn.example.com/2024/paris-trip/eiffel-tower.jpg)
347
+
348
+ ![Louvre Museum](https://cdn.example.com/2024/paris-trip/louvre.jpg)
349
+
350
+ ## Evening Stroll
351
+
352
+ The Parisian streets at night are magical.
353
+
354
+ ![Seine River at night](https://cdn.example.com/2024/paris-trip/seine-night.jpg)
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
- # Set up environment variables first
131
- cat > .env << EOF
132
- R2_ACCESS_KEY_ID=...
133
- R2_SECRET_ACCESS_KEY=...
134
- R2_BUCKET=...
135
- R2_PUBLIC_URL=https://cdn.example.com
136
- EOF
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
- # Upload images
139
- bunki images:push --images ./images --output-json image-map.json
505
+ ```bash
506
+ export S3_PUBLIC_URL="https://img.example.com"
140
507
  ```
141
508
 
142
- Supported formats: JPG, PNG, GIF, WebP, SVG
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
- - **Images**: Direct S3/R2 uploads with URL mapping
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.5.3 (Current)
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 = ["postcss", inputPath, "-o", outputPath, "--config", configPath];
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
- console.log(`[S3] Found image file: ${file}`);
33643
- files.push(file);
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
- process.exit(1);
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: ${new Date().toISOString()}
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 = ["postcss", inputPath, "-o", outputPath, "--config", configPath];
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
- console.log(`[S3] Found image file: ${file}`);
31550
- files.push(file);
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
- process.exit(1);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunki",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
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",