Files
S. EsmaeiliandClaude Haiku 5.5 10e453e4be build: ship pre-built API bundle; images copy output instead of installing
- API: `pnpm build` bundles dist/ into apps/api/.output (esbuild), with
  Linux musl native packages for sharp and argon2 kept alongside
- docker/api.Dockerfile copies that output; no pnpm install on the server
- add dist/migrate.js (prisma migrate deploy equivalent, same
  _prisma_migrations table and lock) so the Prisma CLI is not needed at runtime
- pnpm-workspace: supportedArchitectures for linux/x64/musl
- admin: apps/admin/.env.production sets VITE_SITE_URL for the production build
- drop NPM_REGISTRY / SITE_URL build args from compose and .env.example
- gitignore: anchor node_modules rules so pre-built node_modules are committed
- README: describe the pre-built deploy flow

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-24 14:41:31 +03:30

66 lines
2.8 KiB
JavaScript

import { createHash, randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import pg from "pg";
if (existsSync(".env"))
process.loadEnvFile(".env");
const MIGRATIONS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../prisma/migrations");
const LOCK_ID = 72707369;
const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
async function main() {
await client.query(`SELECT pg_advisory_lock(${LOCK_ID})`);
await client.query(`CREATE TABLE IF NOT EXISTS "_prisma_migrations" (
"id" VARCHAR(36) PRIMARY KEY NOT NULL,
"checksum" VARCHAR(64) NOT NULL,
"finished_at" TIMESTAMPTZ,
"migration_name" VARCHAR(255) NOT NULL,
"logs" TEXT,
"rolled_back_at" TIMESTAMPTZ,
"started_at" TIMESTAMPTZ NOT NULL DEFAULT now(),
"applied_steps_count" INTEGER NOT NULL DEFAULT 0
)`);
const { rows } = await client.query(`SELECT migration_name, finished_at FROM "_prisma_migrations" WHERE rolled_back_at IS NULL`);
const failed = rows.find((r) => !r.finished_at);
if (failed) {
throw new Error(`migration ${failed.migration_name} failed earlier; fix the database, then mark it with ` +
`\`prisma migrate resolve\` (see its "logs" column in _prisma_migrations)`);
}
const applied = new Set(rows.map((r) => r.migration_name));
const entries = await readdir(MIGRATIONS_DIR, { withFileTypes: true });
const pending = entries
.filter((e) => e.isDirectory() && !applied.has(e.name))
.map((e) => e.name)
.sort();
for (const name of pending) {
const script = await readFile(path.join(MIGRATIONS_DIR, name, "migration.sql"), "utf8");
const id = randomUUID();
await client.query(`INSERT INTO "_prisma_migrations" (id, checksum, migration_name) VALUES ($1, $2, $3)`, [id, createHash("sha256").update(script).digest("hex"), name]);
try {
await client.query(script);
}
catch (e) {
await client.query(`UPDATE "_prisma_migrations" SET logs = $2 WHERE id = $1`, [
id,
String(e),
]);
throw new Error(`migration ${name} failed`, { cause: e });
}
await client.query(`UPDATE "_prisma_migrations" SET finished_at = now(), applied_steps_count = 1 WHERE id = $1`, [id]);
console.log(`applied ${name}`);
}
console.log(pending.length ? `${pending.length} migration(s) applied` : "no pending migrations");
}
try {
await client.connect();
await main();
}
catch (e) {
console.error(e);
process.exitCode = 1;
}
finally {
await client.end();
}
//# sourceMappingURL=migrate.js.map