Files
Genarrative/scripts/spacetime-export-migration-json.mjs
T
kdletters 187d7735b9 修复编辑器定价持久化与资源预览
将模型定价改为 SpacetimeDB 强类型持久化并兼容旧配置种子迁移
修复快速编辑支付弹窗期间框选显示和交互冻结
补齐后台图片放大以及视频音频资源预览
收紧主站和 External 资源换签授权并记录管理员跨用户审计
固化外部生成入队价格、attempt 钱包结算和最终 lease 失败收口
加固运行时身份轮换、bootstrap secret 与生产构建发布门禁
同步生成绑定、定向测试、运维脚本和项目文档
2026-07-10 18:28:06 +08:00

137 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
import { writeFile } from 'node:fs/promises';
import path from 'node:path';
import {
callSpacetimeProcedure,
callSpacetimeProcedureViaCli,
createSpacetimeWebIdentity,
ensureParentDir,
ensureProcedureOk,
parseArgs,
resolveBootstrapSecret,
} from './spacetime-migration-common.mjs';
try {
const options = parseArgs(process.argv.slice(2));
if (!options.out) {
throw new Error('必须传入 --out。');
}
const input = {
include_tables: options.includeTables,
};
const webOptions = await prepareWebExportOptions(options);
let result;
try {
result = await callSpacetimeProcedure(webOptions, 'export_database_migration_to_file', input);
} finally {
await revokeTemporaryWebIdentity(webOptions);
}
ensureProcedureOk(result);
if (typeof result.migration_json !== 'string' || result.migration_json.trim() === '') {
throw new Error('导出 procedure 没有返回 migration_json。');
}
const outPath = path.resolve(options.out);
await ensureParentDir(outPath);
await writeFile(outPath, result.migration_json, 'utf8');
console.log(`[spacetime:migration:export] 已写入 ${outPath}`);
printTableStats(result.table_stats);
printMigrationWarnings(result.warnings);
} catch (error) {
console.error(
`[spacetime:migration:export] ${error instanceof Error ? error.message : String(error)}`,
);
process.exit(1);
}
async function prepareWebExportOptions(options) {
if (options.token) {
return { ...options, useHttp: true };
}
const identity = await createSpacetimeWebIdentity(options);
console.log(
`[spacetime:migration:export] 已通过 Web API 创建临时 identity: ${identity.identity}`,
);
try {
const authorizeResult = await callSpacetimeProcedureViaCli(
options,
'authorize_database_migration_operator',
{
bootstrap_secret: await resolveBootstrapSecret(options),
operator_identity_hex: identity.identity,
note: options.note || 'temporary web api migration export',
},
);
ensureProcedureOk(authorizeResult);
} catch (error) {
throw new Error(
`授权临时 Web API identity 失败。当前 spacetime CLI identity 必须已经是迁移操作员;如果旧库迁移操作员表不为空,bootstrap secret 不会越权授权新的操作员。可先用已有迁移操作员授权当前部署机 identity,或为导出脚本提供已有迁移操作员的 --token。原始错误: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
console.log(`[spacetime:migration:export] 已授权临时 Web API identity`);
return {
...options,
token: identity.token,
temporaryWebIdentity: identity.identity,
useHttp: true,
};
}
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:export] 已撤销临时 Web API identity`);
} catch (error) {
console.warn(
`[spacetime:migration:export] 撤销临时 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,
exported: stat.exported_row_count,
}));
console.table(rows);
}
function printMigrationWarnings(warnings) {
if (!Array.isArray(warnings) || warnings.length === 0) {
return;
}
console.warn('[spacetime:migration:export] 迁移告警:');
console.table(
warnings.map((warning) => ({
table: warning.table_name,
kind: warning.warning_kind,
message: warning.message,
})),
);
}