// Push base content and inspo images to the public `stylize` R2 bucket. // node scripts/upload.ts [base] [inspo] (default: both) // Writes base/posts.remote.json and ref//inspo.json with public URLs. import { execFileSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { tmpdir } from 'node:os'; export const BUCKET = 'stylize'; export const PUBLIC = 'https://pub-cdd85b5c268446a6b5c5838f5fccf28c.r2.dev'; const ROOT = resolve(import.meta.dirname, '..'); const TMP = join(tmpdir(), 'stylize-upload'); mkdirSync(TMP, { recursive: true }); function put(key: string, file: string, contentType: string) { execFileSync('npx', ['wrangler', 'r2', 'object', 'put', `${BUCKET}/${key}`, '--file', file, '--content-type', contentType, '--remote'], { stdio: 'ignore' }); console.log(`${PUBLIC}/${key}`); return `${PUBLIC}/${key}`; } const TYPES: Record = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' }; const only = process.argv.slice(2); const want = (part: string) => !only.length || only.includes(part); // base: media + posts.json with absolute media URLs if (want('base')) { for (const name of readdirSync(join(ROOT, 'base/media'))) { put(`base/media/${name}`, join(ROOT, 'base/media', name), TYPES[name.split('.').pop()!]); } const posts = readFileSync(join(ROOT, 'base/posts.json'), 'utf8').replaceAll('"media/', `"${PUBLIC}/base/media/`).replaceAll('src=\\"media/', `src=\\"${PUBLIC}/base/media/`); writeFileSync(join(ROOT, 'base/posts.remote.json'), posts); put('base/posts.json', join(ROOT, 'base/posts.remote.json'), 'application/json'); } // inspo: downscale to <=2000px JPEG so each image fits the API's per-image limit // Images already in a ref's inspo.json are kept as-is; only new files are converted and uploaded. for (const ref of want('inspo') ? readdirSync(join(ROOT, 'ref')) : []) { const dir = join(ROOT, 'ref', ref, 'inspo'); let files: string[]; try { files = readdirSync(dir).filter(f => /\.(png|jpe?g|gif|webp)$/i.test(f)); } catch { continue; } const manifestFile = join(ROOT, 'ref', ref, 'inspo.json'); const existing = new Map(); try { for (const m of JSON.parse(readFileSync(manifestFile, 'utf8'))) existing.set(m.name, m); } catch {} const manifest = []; for (const f of files.sort()) { if (existing.has(f)) { manifest.push(existing.get(f)); continue; } const id = createHash('sha1').update(f).digest('hex').slice(0, 10); const out = join(TMP, `${id}.jpg`); execFileSync('sips', ['-Z', '2000', '-s', 'format', 'jpeg', '-s', 'formatOptions', '85', join(dir, f), '--out', out], { stdio: 'ignore' }); manifest.push({ id, name: f, url: put(`inspo/${ref}/${id}.jpg`, out, 'image/jpeg') }); } writeFileSync(manifestFile, JSON.stringify(manifest, null, 2)); }