migrateCodebase
uses node:path
Move a codebase to this tcm's hash format, safely: the database as it was is kept beside it
(codebase.v<n>.sqlite), a copy is migrated (migrateFile), and the copy is used only if this tcm then reads
every live definition back to its hash (verify); otherwise the codebase is left as it was. Undefined if it's in
this format already.
import { Codebase } from "#codebase/tcmts/cli/codebase"; import { formatsOf, migrateFile } from "#codebase/tcmts/cli/migrate"; import type { Migrated } from "#codebase/tcmts/cli/migrate"; import { IR_VERSION } from "#codebase/tcmts/core/hash"; import fs from "node:fs"; import path from "node:path"; import { DatabaseSync } from "node:sqlite"; export function migrateCodebase( root: string, ): (Migrated & { backup: string; verified: number }) | undefined { const db = path.join(root, ".tcm", "codebase.sqlite"); // everything written so far in the database file itself, not its log const wal = new DatabaseSync(db); wal.exec("PRAGMA wal_checkpoint(TRUNCATE)"); wal.close(); const from = formatsOf(db); if (from.every((v) => v === IR_VERSION)) return undefined; if (from.some((v) => v > IR_VERSION)) throw new Error( `this codebase is in hash format v${Math.max(...from)}, newer than this tcm's (v${IR_VERSION}): use a newer tcm`, ); let backup = path.join(root, ".tcm", `codebase.v${Math.max(...from)}.sqlite`); if (fs.existsSync(backup)) backup = backup.replace(/\.sqlite$/, `.${Date.now()}.sqlite`); fs.copyFileSync(db, backup); const work = `${db}.migrating`; fs.copyFileSync(db, work); let report: Migrated; try { report = migrateFile(work); } catch (e) { fs.rmSync(work, { force: true }); throw new Error(`the migration failed, and the codebase is as it was: ${(e as Error).message}`); } for (const f of [`${db}-wal`, `${db}-shm`]) fs.rmSync(f, { force: true }); fs.renameSync(work, db); try { const cb = Codebase.find(root); const v = cb.verify(true); if (v.mismatches.length || v.problems.length) { const said = [ ...v.mismatches.map( (m) => `${m.name} hashes to ${m.rehashed.slice(0, 8)}, not ${m.stored.slice(0, 8)}`, ), ...v.problems, ].slice(0, 5); cb.store.close(); throw new Error(said.join("\n")); } cb.refreshShim(true); cb.store.close(); return { ...report, backup, verified: v.checked }; } catch (e) { for (const f of [`${db}-wal`, `${db}-shm`]) fs.rmSync(f, { force: true }); fs.copyFileSync(backup, db); throw new Error( `the migrated codebase didn't read back as itself, so it's as it was:\n${(e as Error).message}`, ); } }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.migrate.Migrated
- tcmts.cli.migrate.formatsOf
- tcmts.cli.migrate.migrateFile
- tcmts.core.hash.IR_VERSION