#!/usr/bin/env node import { execFileSync } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import pg from 'pg'; const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const envPath = resolve(projectDir, '.env'); if (existsSync(envPath)) { for (const line of readFileSync(envPath, 'utf8').split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eq = trimmed.indexOf('='); if (eq === -1) continue; const key = trimmed.slice(0, eq).trim(); const value = trimmed.slice(eq + 1).trim(); if (key && process.env[key] === undefined) process.env[key] = value; } } const databaseUrl = process.env.DATABASE_URL; if (!databaseUrl) throw new Error('DATABASE_URL not set'); const recreate = process.argv.includes('--recreate'); const token = process.env.PERSISTENCE_CHECK_TOKEN ?? `persistence-${Date.now()}`; const pgKey = 'infra.persistence_check'; const redisKey = 'infra:persistence_check'; function run(command, args, options = {}) { return execFileSync(command, args, { cwd: projectDir, stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit', encoding: 'utf8', }); } function dockerCompose(args, options) { return run('docker', ['compose', ...args], options); } function dockerExec(container, args, options) { return run('docker', ['exec', container, ...args], options); } function waitForInfrastructure() { const deadline = Date.now() + 60_000; while (Date.now() < deadline) { try { dockerExec('mdv-dev-postgres', ['pg_isready', '-U', 'mdv', '-d', 'mercadodevida'], { capture: true, }); dockerExec('mdv-dev-redis', ['redis-cli', 'ping'], { capture: true }); return; } catch { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000); } } throw new Error('PostgreSQL/Redis did not become ready in 60s'); } async function writePostgresMarker() { const pool = new pg.Pool({ connectionString: databaseUrl }); try { await pool.query( `INSERT INTO app_meta (key, value, updated_at) VALUES ($1, $2, now()) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`, [pgKey, token], ); } finally { await pool.end(); } } async function readPostgresMarker() { const pool = new pg.Pool({ connectionString: databaseUrl }); try { const result = await pool.query('SELECT value FROM app_meta WHERE key = $1', [pgKey]); return result.rows[0]?.value; } finally { await pool.end(); } } function writeRedisMarker() { dockerExec('mdv-dev-redis', ['redis-cli', 'SET', redisKey, token]); } function readRedisMarker() { return dockerExec('mdv-dev-redis', ['redis-cli', 'GET', redisKey], { capture: true }).trim(); } function printMounts() { const pgMount = run( 'docker', [ 'inspect', 'mdv-dev-postgres', '--format', '{{range .Mounts}}{{.Name}} -> {{.Destination}}{{println}}{{end}}', ], { capture: true }, ).trim(); const redisMount = run( 'docker', [ 'inspect', 'mdv-dev-redis', '--format', '{{range .Mounts}}{{.Name}} -> {{.Destination}}{{println}}{{end}}', ], { capture: true }, ).trim(); const redisAppendOnly = dockerExec( 'mdv-dev-redis', ['redis-cli', 'CONFIG', 'GET', 'appendonly'], { capture: true, }, ).trim(); console.log(`PostgreSQL mount: ${pgMount}`); console.log(`Redis mount: ${redisMount}`); console.log(`Redis appendonly config:\n${redisAppendOnly}`); } console.log('[INFO] Starting infrastructure through docker compose...'); dockerCompose(['up', '-d', 'postgres', 'redis']); waitForInfrastructure(); printMounts(); console.log(`[INFO] Writing persistence marker: ${token}`); await writePostgresMarker(); writeRedisMarker(); if (recreate) { console.log('[INFO] Recreating PostgreSQL and Redis containers while keeping named volumes...'); dockerCompose(['stop', 'postgres', 'redis']); dockerCompose(['rm', '-f', 'postgres', 'redis']); dockerCompose(['up', '-d', 'postgres', 'redis']); } else { console.log('[INFO] Restarting PostgreSQL and Redis containers...'); dockerCompose(['restart', 'postgres', 'redis']); } waitForInfrastructure(); printMounts(); const pgValue = await readPostgresMarker(); const redisValue = readRedisMarker(); if (pgValue !== token) { throw new Error(`PostgreSQL marker mismatch: expected ${token}, got ${pgValue ?? ''}`); } if (redisValue !== token) { throw new Error(`Redis marker mismatch: expected ${token}, got ${redisValue || ''}`); } console.log('[OK] PostgreSQL and Redis persistence verified');