#!/usr/bin/env node // BOM generator — walks asset directories, lists files, prompts for descriptions, outputs BOM.yaml // // Usage: // npx tsx scripts/bom-gen.ts [asset-root] // // Expected directory structure: // audio/ — audio files // images/ — image files // fonts/ — font files // videos/ — material video clips // // Output: BOM.yaml (YAML frontmatter-style) import { readdirSync, statSync, writeFileSync } from "node:fs"; import { resolve, relative, join } from "node:path"; import { createInterface } from "node:readline"; type Asset = { path: string; size: number; description: string }; const ASSET_DIRS = ["audio", "images", "fonts", "videos"]; function walk(dir: string): Asset[] { const assets: Asset[] = []; for (const entry of readdirSync(dir, { withFileTypes: true })) { const full = resolve(dir, entry.name); if (entry.isDirectory()) { assets.push(...walk(full)); } else { assets.push({ path: relative(".", full), size: statSync(full).size, description: "", }); } } return assets; } function humanSize(bytes: number): string { const units = ["B", "KB", "MB", "GB"]; let i = 0; while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++; } return `${bytes.toFixed(1)} ${units[i]}`; } async function main() { const root = process.argv[2] || "."; const rl = createInterface({ input: process.stdin, output: process.stdout }); console.log("BOM Generator"); console.log("=============\n"); let all: Asset[] = []; for (const dir of ASSET_DIRS) { const full = resolve(root, dir); try { const assets = walk(full); all.push(...assets); console.log(` ${dir}/ — ${assets.length} file(s)`); } catch { console.log(` ${dir}/ — not found (skipped)`); } } if (all.length === 0) { console.log("\nNo assets found. Nothing to do.\n"); rl.close(); return; } const totalSize = all.reduce((s, a) => s + a.size, 0); console.log(`\nTotal: ${all.length} file(s), ${humanSize(totalSize)}\n`); for (let i = 0; i < all.length; i++) { const a = all[i]; console.log(`[${i + 1}/${all.length}] ${a.path} (${humanSize(a.size)})`); a.description = (await new Promise((r) => rl.question(" Description: ", r) )).trim(); } // Build YAML manually (no dependency) let yaml = "---\nbom:\n"; for (const a of all) { const desc = a.description.replace(/"/g, '\\"'); yaml += ` ${a.path}:\n description: "${desc}"\n`; } yaml += `total_files: ${all.length}\ntotal_size: ${totalSize}\n...`; const bomPath = resolve(root, "BOM.yaml"); writeFileSync(bomPath, yaml); console.log(`\n✓ Written to ${relative(root, bomPath)}\n`); rl.close(); } main().catch(console.error);