#!/usr/bin/env node import { execFileSync } from 'node:child_process'; import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { buildAppDataArchive } from './lib/appdata-archive.mjs'; const root = dirname(fileURLToPath(new URL('../package.json', import.meta.url))); const args = parseArgs(process.argv.slice(2)); if (args.help) { printHelp(); process.exit(0); } const outPath = resolve(root, args.out ?? defaultArchiveName()); const serverRequire = createRequire(resolve(root, 'apps/server/package.json')); const { PrismaClient } = serverRequire('@prisma/client'); const prisma = new PrismaClient(); try { const rows = await prisma.appData.findMany({ orderBy: { key: 'asc' }, select: { key: true, value: true, updatedAt: true }, }); const archive = buildAppDataArchive(rows, { appVersion: process.env.APP_VERSION || readPackageVersion(), appBuildTime: process.env.APP_BUILD_TIME || null, sourceCommit: process.env.GITHUB_SHA || readGitCommit(), }); mkdirSync(dirname(outPath), { recursive: true }); writeFileSync(outPath, `${JSON.stringify(archive, null, 2)}\n`); console.log(`AppData archive exported: ${outPath}`); console.log(`Rows: ${archive.metadata.rowCount}; keys: ${archive.keys.join(', ') || '(none)'}`); console.log(`Payload checksum: ${archive.metadata.payloadChecksum}`); } finally { await prisma.$disconnect(); } function parseArgs(argv) { const parsed = { help: false, out: null }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === '--') continue; else if (arg === '--help' || arg === '-h') parsed.help = true; else if (arg === '--out') parsed.out = argv[++index]; else if (arg.startsWith('--out=')) parsed.out = arg.slice('--out='.length); else throw new Error(`Unknown argument: ${arg}`); } return parsed; } function defaultArchiveName() { const stamp = new Date().toISOString().replace(/[:.]/g, '-'); return `appdata-archive-${stamp}.json`; } function readPackageVersion() { try { const pkg = JSON.parse(readFileSync(resolve(root, 'apps/server/package.json'), 'utf8')); return pkg.version || 'unknown'; } catch { return 'unknown'; } } function readGitCommit() { try { return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); } catch { return 'unknown'; } } function printHelp() { console.log(`Usage: pnpm appdata:archive:export -- --out backups/appdata-archive.json Exports every row from app_data to a JSON archive with key list, app version metadata, per-row SHA-256 checksums, and a payload checksum. Options: --out Output archive path. Defaults to appdata-archive-.json. -h, --help Show this help. `); }