/** * Turns an export of first-party custom avatar items into a D1 migration that seeds the * `custom_avatar_item` table with them, the way `apps/rooms/migrations/0002_import_rooms.sql` * seeds the rooms. * * bun apps/api/scripts/import-custom-avatar-items.ts * * `export.json` is a JSON array of `CustomAvatarItem` records as the official service serves * them (a `{ Results: [...] }` page or a single record are accepted too). `NNNN` is the * migration number; the file is written to `apps/api/migrations/NNNN_import_custom_avatar_items.sql`. * * For a load that needs no migration and no deploy — the usual case — use * `just admin cai-load` instead, which runs the same statements straight against a database. * The statements come from src/custom-avatar-items-load.ts either way: each record stored as * the row's JSON minus `PurchaseInfo`, with `CreatorAccountId` forced to the Coach account (1) * and each save's `ThumbnailFileName` put under `avatar/`, an existing id REPLACED, nothing * deleted. * * The export's `Price` is mostly 0; run `price-custom-avatar-items.ts` against it first to fill * in prices from the storefront dump. * * The saves name assetbundles by bare filename; the migration does nothing about those files, * which have to be served from wherever the client fetches them. */ import { readFileSync, writeFileSync } from 'node:fs' import { customAvatarItemUpsertStatements, readCustomAvatarItemExport, } from '../src/custom-avatar-items-load' const [input, number] = process.argv.slice(2) if (!input || !number || !/^\d{4}$/.test(number)) { console.error('usage: bun apps/api/scripts/import-custom-avatar-items.ts ') process.exit(2) } // Tolerate the BOM a .NET export carries (`JSON.parse` rejects U+FEFF). const records = readCustomAvatarItemExport( JSON.parse(readFileSync(input, 'utf8').replace(/^/, '')) ) const lines = [ `-- Seed first-party custom avatar items (${records.length} records) from ${input.split('/').pop()}.`, '-- Generated by apps/api/scripts/import-custom-avatar-items.ts — do not edit by hand.', '-- Each record is the official `CustomAvatarItem`, filed under the Coach account (CreatorAccountId 1);', '-- an existing id is replaced.', '', ...customAvatarItemUpsertStatements(records), ] const out = decodeURIComponent( new URL(`../migrations/${number}_import_custom_avatar_items.sql`, import.meta.url).pathname ) writeFileSync(out, lines.join('\n') + '\n') console.log(`wrote ${records.length} records to ${out}`)