fix(deploy): vollständige pg-Closure + funktionierender RUN_SEED im Runner-Image
Zwei BLOCKING-Befunde für "Deployment (Docker + externes Traefik)":
1) pg-Laufzeit-Closure unvollständig: pg-types/lib/binaryParsers.js macht
eager require('pg-int8'), postgres-interval/index.js require('xtend/mutable').
Beide lagen NICHT im Runner-Image -> node docker/migrate.mjs crasht beim
Container-Start mit ERR_MODULE_NOT_FOUND, Deploy kaputt. Fix: COPY für
pg-int8 + xtend ergänzt. Neuer Test berechnet die reale Closure aus
node_modules und schlägt fehl, sobald ein Paket nicht ins Image kopiert
wird (schützt vor erneutem Brechen bei pg/drizzle-Updates).
2) RUN_SEED toter Pfad: entrypoint.sh rief docker/seed.mjs auf, das nie
existierte -> RUN_SEED=true no-opte still zu leerem Katalog. Fix:
scripts/build-seed-bundle.mjs bündelt src/db/seed (inkl. Schema + Daten,
pg/drizzle-orm extern) per esbuild zu selbstständigem docker/seed.mjs;
im builder erzeugt und ins Runner-Image kopiert. entrypoint.sh bricht
jetzt laut ab, wenn RUN_SEED=true und das Bundle fehlt, statt still zu
überspringen. docker/seed.mjs ist generiert -> gitignored.
Verifiziert offline: tsc --noEmit, vitest (deployment + seed-Daten, 36 grün),
Bundle baut + lädt sauber (externe Imports nur pg/drizzle-orm, exit 1 ohne
DATABASE_URL). docker build/run sind im Sandbox deferred (kein Docker/Postgres).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,70 @@ describe("Deployment-Artefakte", () => {
|
||||
expect(df).toMatch(/entrypoint\.sh/);
|
||||
});
|
||||
|
||||
it("Dockerfile kopiert die VOLLSTÄNDIGE pg/drizzle-orm-Laufzeit-Closure ins Runner-Image", () => {
|
||||
// Reale Laufzeit-Closure aus node_modules berechnen (nur dependencies,
|
||||
// wie sie zur Laufzeit von docker/migrate.mjs + docker/seed.mjs benötigt
|
||||
// wird). Jedes Paket MUSS eine COPY-Zeile im Runner-Stage haben, sonst
|
||||
// crasht der Container-Start mit ERR_MODULE_NOT_FOUND (z. B. pg-int8,
|
||||
// xtend). Schützt vor erneutem Brechen bei pg/drizzle-Updates.
|
||||
const df = read("Dockerfile");
|
||||
const nodeModules = resolve(root, "node_modules");
|
||||
|
||||
const seen = new Set<string>();
|
||||
const resolvePkg = (name: string, fromDir: string): string | null => {
|
||||
let cur = fromDir;
|
||||
for (;;) {
|
||||
const candidate = resolve(cur, "node_modules", name);
|
||||
if (existsSync(resolve(candidate, "package.json"))) return candidate;
|
||||
const parent = dirname(cur);
|
||||
if (parent === cur) break;
|
||||
cur = parent;
|
||||
}
|
||||
const top = resolve(nodeModules, name);
|
||||
return existsSync(resolve(top, "package.json")) ? top : null;
|
||||
};
|
||||
const walk = (dir: string): void => {
|
||||
const pkg = JSON.parse(
|
||||
readFileSync(resolve(dir, "package.json"), "utf8"),
|
||||
) as { dependencies?: Record<string, string> };
|
||||
for (const dep of Object.keys(pkg.dependencies ?? {})) {
|
||||
if (seen.has(dep)) continue;
|
||||
seen.add(dep);
|
||||
const resolved = resolvePkg(dep, dir);
|
||||
// Nur skalierbar prüfbar, wenn auflösbar; sonst meldet npm ci es ohnehin.
|
||||
if (resolved) walk(resolved);
|
||||
}
|
||||
};
|
||||
|
||||
const pgRoot = resolve(nodeModules, "pg");
|
||||
const drizzleRoot = resolve(nodeModules, "drizzle-orm");
|
||||
walk(pgRoot);
|
||||
walk(drizzleRoot);
|
||||
|
||||
// pg + drizzle-orm selbst sind ebenfalls Teil der Closure.
|
||||
const closure = new Set<string>([...seen, "pg", "drizzle-orm"]);
|
||||
|
||||
const missing = [...closure].filter(
|
||||
(name) =>
|
||||
!df.includes(`/app/node_modules/${name} ./node_modules/${name}`),
|
||||
);
|
||||
expect(missing, `nicht ins Runner-Image kopiert: ${missing.join(", ")}`).toEqual(
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it("Dockerfile bündelt den Seed (build-seed-bundle) und kopiert docker/seed.mjs ins Runner-Image", () => {
|
||||
const df = read("Dockerfile");
|
||||
// Im builder wird der Seed zu Plain-ESM gebündelt ...
|
||||
expect(df).toMatch(/build-seed-bundle\.mjs/);
|
||||
// ... und ins Runner-Image kopiert, damit RUN_SEED=true nicht still no-opt.
|
||||
expect(df).toMatch(/docker\/seed\.mjs/);
|
||||
// Der Bundler-Entrypoint existiert.
|
||||
expect(existsSync(resolve(root, "scripts/build-seed-bundle.mjs"))).toBe(true);
|
||||
// Das Seed-Quellmodul, das gebündelt wird, existiert.
|
||||
expect(existsSync(resolve(root, "src/db/seed/index.ts"))).toBe(true);
|
||||
});
|
||||
|
||||
it(".dockerignore schließt node_modules, .next und Secrets aus", () => {
|
||||
const di = read(".dockerignore");
|
||||
expect(di).toMatch(/node_modules/);
|
||||
|
||||
Reference in New Issue
Block a user