4 Commits

Author SHA1 Message Date
irene 8f860e037a feat: add BOM generator (v2 with hashes) 2026-09-09 17:53:11 +09:00
irene 475783556f feat: add BOM generator script 2026-09-09 17:50:46 +09:00
irene a7823f1747 docs: add port pinning option to quickstart 2026-09-09 17:46:12 +09:00
irene 18de75ee28 docs: note port auto-increment in quickstart 2026-09-09 17:45:42 +09:00
2 changed files with 104 additions and 1 deletions
+5 -1
View File
@@ -12,7 +12,11 @@ npm install # install dependencies
npm run dev # start Remotion Studio
```
Remotion Studio opens at **http://localhost:3000** in your browser.
Remotion Studio opens at **http://localhost:3000** in your browser. If 3000 is already in use, Remotion automatically bumps to 3001, 3002, etc. — watch the terminal output for the actual port number. You can also pin a specific port:
```bash
npx remotion studio --port=3001
```
## Commands
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env tsx
// BOM generator — walks asset directories, lists files, prompts for descriptions, outputs BOM.yaml
import { readdirSync, statSync, writeFileSync } from "node:fs";
import { resolve, relative } from "node:path";
import { createInterface } from "node:readline";
type Asset = { path: string; size: number; description: string; hash: 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 {
const s = statSync(full);
const content = readFileSync(full);
// Simple hash: first 16 chars of node hash
const { createHash } = await import("node:crypto");
const hash = createHash("md5").update(content).digest("hex").slice(0, 8);
assets.push({
path: relative(".", full),
size: s.size,
description: "",
hash,
});
}
}
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<string>((r) =>
rl.question(" Description: ", r)
)).trim();
}
// Build YAML
let yaml = "---\nbom:\n";
for (const a of all) {
yaml += ` - file: ${a.path}\n`;
yaml += ` size: ${a.size}\n`;
yaml += ` hash: ${a.hash}\n`;
yaml += ` description: "${a.description.replace(/"/g, '\\"')}"\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);