feat(migrate): CLI dispatch + verify
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
25
migrate/cli.js
Normal file
25
migrate/cli.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'dotenv/config';
|
||||
import { ensureSpace } from './spaces.js';
|
||||
import { importPlans } from './sources/plans.js';
|
||||
import { importVoid1 } from './sources/void1.js';
|
||||
import { importKarakeep } from './sources/karakeep.js';
|
||||
import { importBookstack } from './sources/bookstack.js';
|
||||
import { verify } from './verify.js';
|
||||
|
||||
const [, , cmd, ...rest] = process.argv;
|
||||
const dryRun = rest.includes('--dry-run');
|
||||
|
||||
const run = {
|
||||
async plans() { return importPlans({ dir: process.env.PLANS_DIR || '/root/.claude/plans', spaceId: await ensureSpace('plans', 'Plans'), dryRun }); },
|
||||
async void1() { return importVoid1({ sqlitePath: process.env.VOID1_DB || '/tmp/void1.db', spaceId: await ensureSpace('void1', 'Void 1'), dryRun }); },
|
||||
async karakeep() { return importKarakeep({ spaceId: await ensureSpace('bookmarks', 'Bookmarks'), dryRun }); },
|
||||
async bookstack() { return importBookstack({ spaceId: await ensureSpace('wiki', 'Wiki'), dryRun }); },
|
||||
async verify() { return verify(); }
|
||||
};
|
||||
|
||||
if (!run[cmd]) {
|
||||
console.error(`usage: node migrate/cli.js <plans|void1|karakeep|bookstack|verify> [--dry-run]`);
|
||||
process.exit(1);
|
||||
}
|
||||
run[cmd]().then(r => { console.log(JSON.stringify(r, null, 2)); process.exit(0); })
|
||||
.catch(e => { console.error('migrate failed:', e.message); process.exit(1); });
|
||||
10
migrate/verify.js
Normal file
10
migrate/verify.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { pool } from '../lib/db/pool.js';
|
||||
|
||||
// Per-source/type migrated counts (from migration_map) + Karakeep refs (which use
|
||||
// upsertByExternal, not migration_map). Used to audit completeness before cutover.
|
||||
export async function verify() {
|
||||
const { rows: map } = await pool.query(
|
||||
`SELECT source, entity_type, count(*)::int n FROM migration_map GROUP BY source, entity_type ORDER BY source, entity_type`);
|
||||
const { rows: [{ n: bookmarks }] } = await pool.query(`SELECT count(*)::int n FROM refs WHERE source_kind='karakeep'`);
|
||||
return { map, bookmarks };
|
||||
}
|
||||
15
tests/migrate/verify.test.js
Normal file
15
tests/migrate/verify.test.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import { resetDb } from '../helpers/db.js';
|
||||
import { migrateUp } from '../../lib/db/migrate.js';
|
||||
import * as map from '../../lib/db/repos/migration_map.js';
|
||||
import { verify } from '../../migrate/verify.js';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
beforeAll(async () => { await resetDb(); await migrateUp(); await map.record('plans', 'a.md', 'page', randomUUID()); });
|
||||
|
||||
describe('verify', () => {
|
||||
it('reports per-source/type counts', async () => {
|
||||
const out = await verify();
|
||||
expect(out.map.find(r => r.source === 'plans' && r.entity_type === 'page').n).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user