db(cards): baseline migration + drizzle-tracking bootstrap script
Some checks are pending
CI / validate (push) Waiting to run
Some checks are pending
CI / validate (push) Waiting to run
Schließt die Ops-Lücke „kein versioniertes Schema-Tracking" aus FEATURE_IDEAS.md. * apps/api/src/db/migrations/0000_baseline.sql — Drizzle-generierte Baseline-Migration, 355 Zeilen, 25 Tabellen + 5 Enums (cards- und marketplace-Schema). Eingefrostet auf den Live-Stand 2026-05-12. * apps/api/scripts/bootstrap-drizzle-tracking.ts — neues Script, markiert die Baseline in einer bestehenden DB als „bereits angewandt", ohne SQL erneut auszuführen. Verwendet sha256 wie drizzle-orm/migrator (Hash 312d67ba1aeb…), idempotent. * package.json: drizzle:migrate + drizzle:bootstrap-tracking npm-scripts. * docs/playbooks/DRIZZLE_MIGRATIONS_BOOTSTRAP.md — Hand-Over für Prod (Bootstrap einmalig, dann normaler Workflow: schema → generate → commit → migrate, kein push --force mehr). Lokal verifiziert: 17/104 Tests grün, bootstrap idempotent, drizzle-kit migrate erkennt die Baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5a29dd9a8c
commit
4bb1390180
7 changed files with 3523 additions and 5 deletions
90
apps/api/scripts/bootstrap-drizzle-tracking.ts
Normal file
90
apps/api/scripts/bootstrap-drizzle-tracking.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Bootstrap-Script für drizzle.__drizzle_migrations.
|
||||
*
|
||||
* Eine Cards-DB, die bisher über `drizzle-kit push` (Schema-Sync ohne
|
||||
* Migrations-Tracking) gepflegt wurde, hat das Drizzle-Tracking-Schema
|
||||
* nicht. Dieses Script holt das nach:
|
||||
*
|
||||
* 1. Erstellt `drizzle.__drizzle_migrations` (idempotent).
|
||||
* 2. Liest `src/db/migrations/meta/_journal.json`.
|
||||
* 3. Markiert jede dort gelistete Migration als „bereits angewandt",
|
||||
* mit dem gleichen Hash, den `drizzle-orm/migrator` selbst
|
||||
* berechnen würde — sha256(file_content) als hex.
|
||||
*
|
||||
* Nutzung:
|
||||
* DATABASE_URL=postgresql://… pnpm bootstrap:drizzle
|
||||
*
|
||||
* Idempotent: ein zweiter Run macht nichts neu, sondern überspringt
|
||||
* Migrations, deren Hash schon eingetragen ist.
|
||||
*
|
||||
* Nach dem Bootstrap wird `drizzle-kit migrate` jede Migration aus
|
||||
* dem Journal als bekannt erkennen und keine SQL erneut ausführen.
|
||||
* Künftige Schema-Änderungen ⇒ `drizzle-kit generate` ⇒ commit ⇒
|
||||
* `drizzle-kit migrate` führt nur die neuen Migrations aus.
|
||||
*/
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import postgres from 'postgres';
|
||||
|
||||
const url = process.env.DATABASE_URL;
|
||||
if (!url) {
|
||||
console.error('DATABASE_URL not set');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const migrationsDir = resolve(here, '..', 'src', 'db', 'migrations');
|
||||
const journalPath = resolve(migrationsDir, 'meta', '_journal.json');
|
||||
|
||||
interface JournalEntry {
|
||||
idx: number;
|
||||
version: string;
|
||||
when: number;
|
||||
tag: string;
|
||||
breakpoints: boolean;
|
||||
}
|
||||
interface Journal {
|
||||
version: string;
|
||||
dialect: string;
|
||||
entries: JournalEntry[];
|
||||
}
|
||||
|
||||
const journal: Journal = JSON.parse(readFileSync(journalPath, 'utf-8'));
|
||||
|
||||
const sql = postgres(url, { max: 1 });
|
||||
|
||||
try {
|
||||
await sql`CREATE SCHEMA IF NOT EXISTS drizzle`;
|
||||
await sql`CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
hash text NOT NULL,
|
||||
created_at bigint
|
||||
)`;
|
||||
|
||||
const existing = await sql<{ hash: string }[]>`SELECT hash FROM drizzle.__drizzle_migrations`;
|
||||
const existingHashes = new Set(existing.map((r) => r.hash));
|
||||
|
||||
for (const entry of journal.entries) {
|
||||
const migrationPath = resolve(migrationsDir, `${entry.tag}.sql`);
|
||||
const fileContent = readFileSync(migrationPath, 'utf-8');
|
||||
const hash = createHash('sha256').update(fileContent).digest('hex');
|
||||
|
||||
if (existingHashes.has(hash)) {
|
||||
console.log(`SKIP ${entry.tag} (hash ${hash.slice(0, 12)}… already tracked)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await sql`INSERT INTO drizzle.__drizzle_migrations ("hash", "created_at")
|
||||
VALUES (${hash}, ${entry.when})`;
|
||||
console.log(`MARKED ${entry.tag} (hash ${hash.slice(0, 12)}…)`);
|
||||
}
|
||||
|
||||
const final = await sql<{ count: number }[]>`SELECT COUNT(*)::int AS count
|
||||
FROM drizzle.__drizzle_migrations`;
|
||||
console.log(`\nTracking-Tabelle hat jetzt ${final[0]!.count} Eintrag/Einträge.`);
|
||||
} finally {
|
||||
await sql.end({ timeout: 5 });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue