import { spawnSync } from 'node:child_process'; import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; import { dirname, join, resolve } from 'node:path'; import process from 'node:process'; const outputArgument = process.argv[2]; const timestamp = new Date().toISOString().replaceAll(':', '-').replace(/\.\d{3}Z$/, 'Z'); const outputPath = resolve(outputArgument || `./data/backups/shd-stage-bootstrap-${timestamp}.json`); const temporaryBackupPath = `${outputPath}.database.json`; const databaseBackup = spawnSync(process.execPath, ['scripts/db-backup.mjs', temporaryBackupPath], { cwd: process.cwd(), stdio: 'inherit', env: process.env }); if (databaseBackup.error) throw databaseBackup.error; if (databaseBackup.status !== 0) throw new Error('Database backup failed; no staging bootstrap bundle was created.'); async function collectFiles(directory, pattern) { try { const names = (await readdir(directory)).filter((name) => pattern.test(name)).sort(); return await Promise.all(names.map(async (name) => ({ name, contentBase64: (await readFile(join(directory, name))).toString('base64') }))); } catch (error) { if (error?.code === 'ENOENT') return []; throw error; } } try { const database = JSON.parse(await readFile(temporaryBackupPath, 'utf8')); const avatars = await collectFiles( resolve(process.env.TDR_AVATAR_STORAGE_PATH || './data/avatars'), /^[0-9a-f-]{36}\.webp$/i ); const gameIcons = await collectFiles( resolve(process.env.TDR_GAME_ICON_STORAGE_PATH || './data/game-icons'), /^[a-z_]+-[0-9a-f-]{36}\.webp$/i ); const bundle = { format: 'shd-build-stage-bootstrap', version: 1, createdAt: new Date().toISOString(), database, files: { avatars, gameIcons } }; await mkdir(dirname(outputPath), { recursive: true }); await writeFile(outputPath, `${JSON.stringify(bundle, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' }); console.log(`Staging bootstrap bundle includes ${avatars.length} avatars and ${gameIcons.length} game-data icons.`); console.log(`Bootstrap bundle: ${outputPath}`); } finally { await rm(temporaryBackupPath, { force: true }); }