11#!/usr/bin/env bun
22
33/**
4- * Upload images from public/assets to Cloudflare R2
4+ * Upload assets ( images, audio) from public/assets to Cloudflare R2
55 *
66 * This script:
7- * - Scans public/assets for images ( png, jpg, jpeg, webp, gif, svg)
8- * - Generates SHA-256 hash for each image
7+ * - Scans public/assets for media files (images: png, jpg, jpeg, webp, gif, svg; audio: m4a, mp3, wav, ogg )
8+ * - Generates SHA-256 hash for each file
99 * - Uploads to R2 with content-addressable key (hash.ext)
1010 * - Creates manifest mapping original paths to R2 URLs
11- * - Skips uploads for images already in R2 (idempotent)
11+ * - Skips uploads for files already in R2 (idempotent)
1212 *
1313 * Path structure:
1414 * - Local: public/assets/talks/codegen-in-rust/slide-1.png
@@ -29,10 +29,10 @@ import { join, relative } from "path";
2929
3030const BUCKET_NAME = "just-be-dev-assets" ;
3131const CUSTOM_DOMAIN = "https://assets.just-be.dev" ;
32- const IMAGE_DIR = join ( import . meta. dir , "../public/assets" ) ;
32+ const ASSETS_DIR = join ( import . meta. dir , "../public/assets" ) ;
3333const MANIFEST_PATH = join ( import . meta. dir , "../src/content/image-manifest.json" ) ;
3434
35- interface ImageManifest {
35+ interface AssetManifest {
3636 version : string ;
3737 images : Record < string , {
3838 hash : string ;
@@ -46,8 +46,13 @@ async function hashFile(filePath: string): Promise<string> {
4646 return createHash ( "sha256" ) . update ( content ) . digest ( "hex" ) ;
4747}
4848
49- async function findImages ( dir : string ) : Promise < string [ ] > {
50- const imageExtensions = [ '.png' , '.jpg' , '.jpeg' , '.webp' , '.gif' , '.svg' ] ;
49+ async function findAssets ( dir : string ) : Promise < string [ ] > {
50+ const assetExtensions = [
51+ // Images
52+ '.png' , '.jpg' , '.jpeg' , '.webp' , '.gif' , '.svg' ,
53+ // Audio
54+ '.m4a' , '.mp3' , '.wav' , '.ogg' , '.aac' , '.flac'
55+ ] ;
5156 const results : string [ ] = [ ] ;
5257
5358 async function walk ( currentDir : string ) : Promise < void > {
@@ -58,7 +63,7 @@ async function findImages(dir: string): Promise<string[]> {
5863 await walk ( fullPath ) ;
5964 } else if ( entry . isFile ( ) ) {
6065 const ext = entry . name . toLowerCase ( ) . slice ( entry . name . lastIndexOf ( '.' ) ) ;
61- if ( imageExtensions . includes ( ext ) ) {
66+ if ( assetExtensions . includes ( ext ) ) {
6267 results . push ( fullPath ) ;
6368 }
6469 }
@@ -83,27 +88,27 @@ async function uploadToR2(localPath: string, key: string): Promise<void> {
8388 await $ `wrangler r2 object put ${ BUCKET_NAME } /${ key } --file ${ localPath } --remote` ;
8489}
8590
86- async function uploadImages ( ) {
87- console . log ( `Scanning images in: ${ IMAGE_DIR } \n` ) ;
91+ async function uploadAssets ( ) {
92+ console . log ( `Scanning assets in: ${ ASSETS_DIR } \n` ) ;
8893
89- const imagePaths = await findImages ( IMAGE_DIR ) ;
90- console . log ( `Found ${ imagePaths . length } images \n` ) ;
94+ const assetPaths = await findAssets ( ASSETS_DIR ) ;
95+ console . log ( `Found ${ assetPaths . length } assets \n` ) ;
9196
92- const manifest : ImageManifest = {
97+ const manifest : AssetManifest = {
9398 version : "1.0" ,
9499 images : { } ,
95100 } ;
96101
97102 let uploadCount = 0 ;
98103 let skipCount = 0 ;
99104
100- for ( const imagePath of imagePaths ) {
101- const hash = await hashFile ( imagePath ) ;
102- const ext = imagePath . split ( "." ) . pop ( ) ! ;
105+ for ( const assetPath of assetPaths ) {
106+ const hash = await hashFile ( assetPath ) ;
107+ const ext = assetPath . split ( "." ) . pop ( ) ! ;
103108 const key = `${ hash } .${ ext } ` ;
104109
105110 // Store path relative to public/assets (e.g., "talks/codegen-in-rust/slide-1.png")
106- const originalPath = relative ( join ( import . meta. dir , "../public/assets" ) , imagePath ) ;
111+ const originalPath = relative ( join ( import . meta. dir , "../public/assets" ) , assetPath ) ;
107112
108113 const exists = await checkR2Exists ( key ) ;
109114
@@ -112,11 +117,11 @@ async function uploadImages() {
112117 skipCount ++ ;
113118 } else {
114119 console . log ( `⬆ ${ originalPath } → ${ key } ` ) ;
115- await uploadToR2 ( imagePath , key ) ;
120+ await uploadToR2 ( assetPath , key ) ;
116121 uploadCount ++ ;
117122 }
118123
119- const stats = await Bun . file ( imagePath ) . stat ( ) ;
124+ const stats = await Bun . file ( assetPath ) . stat ( ) ;
120125 manifest . images [ originalPath ] = {
121126 hash,
122127 size : stats . size ,
@@ -126,10 +131,10 @@ async function uploadImages() {
126131
127132 await writeFile ( MANIFEST_PATH , JSON . stringify ( manifest , null , 2 ) + "\n" ) ;
128133 console . log ( `\n✅ Manifest written to ${ MANIFEST_PATH } ` ) ;
129- console . log ( `\nUploaded: ${ uploadCount } , Skipped: ${ skipCount } , Total: ${ imagePaths . length } ` ) ;
134+ console . log ( `\nUploaded: ${ uploadCount } , Skipped: ${ skipCount } , Total: ${ assetPaths . length } ` ) ;
130135}
131136
132- uploadImages ( ) . catch ( ( error ) => {
137+ uploadAssets ( ) . catch ( ( error ) => {
133138 console . error ( "Fatal error:" , error ) ;
134139 process . exit ( 1 ) ;
135140} ) ;
0 commit comments