#!/usr/bin/env node import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { assertReadableFile, callSpacetimeProcedure, callSpacetimeProcedureViaCli, createSpacetimeWebIdentity, ensureProcedureOk, parseArgs, } from './spacetime-migration-common.mjs'; try { const options = parseArgs(process.argv.slice(2)); if (!options.in) { throw new Error('必须传入 --in。'); } const inPath = path.resolve(options.in); await assertReadableFile(inPath); const migrationJson = await readFile(inPath, 'utf8'); if (!migrationJson.trim()) { throw new Error(`迁移文件为空: ${inPath}`); } const webOptions = await prepareWebImportOptions(options); let result; try { result = await importMigrationJsonDirect(webOptions, migrationJson); } finally { await revokeTemporaryWebIdentity(webOptions); } ensureProcedureOk(result); console.log( `[spacetime:migration:import] ${options.dryRun ? 'dry-run 完成' : '导入完成'}: ${inPath}`, ); printTableStats(result.table_stats); } catch (error) { console.error( `[spacetime:migration:import] ${error instanceof Error ? error.message : String(error)}`, ); process.exit(1); } async function prepareWebImportOptions(options) { if (options.token) { return { ...options, useHttp: true }; } const identity = await createSpacetimeWebIdentity(options); console.log( `[spacetime:migration:import] 已通过 Web API 创建临时 identity: ${identity.identity}`, ); const authorizeResult = await callSpacetimeProcedureViaCli( options, 'authorize_database_migration_operator', { bootstrap_secret: options.bootstrapSecret || '', operator_identity_hex: identity.identity, note: options.note || 'temporary web api migration import', }, ); ensureProcedureOk(authorizeResult); console.log(`[spacetime:migration:import] 已授权临时 Web API identity`); return { ...options, token: identity.token, temporaryWebIdentity: identity.identity, useHttp: true, }; } async function importMigrationJsonDirect(options, migrationJson) { const input = { migration_json: migrationJson, include_tables: options.includeTables, replace_existing: options.replaceExisting === true, dry_run: options.dryRun === true, }; return callSpacetimeProcedure(options, 'import_database_migration_from_file', input); } async function revokeTemporaryWebIdentity(options) { if (!options.temporaryWebIdentity) { return; } try { const revokeResult = await callSpacetimeProcedure( options, 'revoke_database_migration_operator', { operator_identity_hex: options.temporaryWebIdentity }, ); ensureProcedureOk(revokeResult); console.log(`[spacetime:migration:import] 已撤销临时 Web API identity`); } catch (error) { console.warn( `[spacetime:migration:import] 撤销临时 Web API identity 失败: ${ error instanceof Error ? error.message : String(error) }`, ); } } function printTableStats(tableStats) { if (!Array.isArray(tableStats) || tableStats.length === 0) { return; } const rows = tableStats.map((stat) => ({ table: stat.table_name, imported: stat.imported_row_count, skipped: stat.skipped_row_count, })); console.table(rows); }