#!/usr/bin/env node import { callSpacetimeProcedureViaCli, ensureProcedureOk, } from './spacetime-migration-common.mjs'; const ACTIONS = new Map([ ['backfill', 'backfill_editor_canvas_layout_and_return'], ['activate', 'activate_editor_canvas_layout_and_return'], ['rollback', 'rollback_editor_canvas_layout_and_return'], ]); function usage() { return `用法: node scripts/spacetime-migrate-editor-canvas-layout.mjs \\ --database --project-id --owner-user-id \\ --action [--server ] [--apply] 默认只执行 dry-run 校验,不修改数据;追加 --apply 才写入。 必须使用已授权 database migration operator 的 spacetime CLI 登录态, 并显式传入 --server 或设置 GENARRATIVE_SPACETIME_SERVER。 存量项目的安全顺序: 1. backfill dry-run 2. backfill --apply 3. activate dry-run 4. activate --apply 需要回滚时先执行 rollback dry-run,确认 hash / revision / 2 MiB 门禁通过后再 --apply。`; } function parseOptions(argv) { const options = { action: '', apply: false, database: process.env.GENARRATIVE_SPACETIME_DATABASE || '', ownerUserId: '', passthrough: [], projectId: '', server: process.env.GENARRATIVE_SPACETIME_SERVER || '', serverUrl: process.env.GENARRATIVE_SPACETIME_SERVER_URL || '', }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; const readValue = () => { const value = argv[index + 1]; if (!value || value.startsWith('--')) { throw new Error(`${arg} 缺少参数值。`); } index += 1; return value.trim(); }; if (arg === '--action') { options.action = readValue(); } else if (arg === '--database') { options.database = readValue(); } else if (arg === '--project-id') { options.projectId = readValue(); } else if (arg === '--owner-user-id') { options.ownerUserId = readValue(); } else if (arg === '--server') { options.server = readValue(); } else if (arg === '--server-url') { options.serverUrl = readValue(); } else if (arg === '--apply') { options.apply = true; } else if (arg === '--help' || arg === '-h') { options.help = true; } else if (arg === '--no-config' || arg === '--anonymous') { options.passthrough.push(arg); } else { throw new Error(`未知参数: ${arg}`); } } return options; } try { const options = parseOptions(process.argv.slice(2)); if (options.help) { console.log(usage()); process.exit(0); } if (!options.database || !options.projectId || !options.ownerUserId) { throw new Error('--database、--project-id 和 --owner-user-id 均为必填。'); } if (!options.server && !options.serverUrl) { throw new Error( '必须显式传入 --server / --server-url,不使用默认 cloud target。', ); } const procedureName = ACTIONS.get(options.action); if (!procedureName) { throw new Error('--action 只支持 backfill、activate 或 rollback。'); } const result = await callSpacetimeProcedureViaCli(options, procedureName, { project_id: options.projectId, owner_user_id: options.ownerUserId, updated_at_micros: Date.now() * 1_000, dry_run: !options.apply, }); ensureProcedureOk(result); console.log( JSON.stringify( { action: options.action, applied: options.apply, procedure: procedureName, ...result, }, null, 2, ), ); if (!options.apply) { console.log('dry-run 已通过;确认输出后追加 --apply 重跑同一动作。'); } } catch (error) { console.error( `[spacetime:editor-canvas-layout:migrate] ${ error instanceof Error ? error.message : String(error) }`, ); process.exit(1); }