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>
90 lines
3 KiB
TypeScript
90 lines
3 KiB
TypeScript
/**
|
|
* 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 });
|
|
}
|