diff --git a/migrate/cli.js b/migrate/cli.js new file mode 100644 index 0000000..28ff001 --- /dev/null +++ b/migrate/cli.js @@ -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 [--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); }); diff --git a/migrate/verify.js b/migrate/verify.js new file mode 100644 index 0000000..5f484d0 --- /dev/null +++ b/migrate/verify.js @@ -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 }; +} diff --git a/tests/migrate/verify.test.js b/tests/migrate/verify.test.js new file mode 100644 index 0000000..7894570 --- /dev/null +++ b/tests/migrate/verify.test.js @@ -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); + }); +});