import { spawnSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import postgres from 'postgres'; export type SqlClient = ReturnType; function loadEnvFile(filePath: string) { if (!fs.existsSync(filePath)) { return; } const content = fs.readFileSync(filePath, 'utf8'); for (const rawLine of content.split(/\r?\n/)) { const line = rawLine.trim(); if (!line || line.startsWith('#')) { continue; } const separatorIndex = line.indexOf('='); if (separatorIndex === -1) { continue; } const key = line.slice(0, separatorIndex).trim(); let value = line.slice(separatorIndex + 1).trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } if (!(key in process.env)) { process.env[key] = value; } } } export function loadLocalEnv() { loadEnvFile(path.resolve(process.cwd(), '.env.local')); loadEnvFile(path.resolve(process.cwd(), '.env')); } export function requireEnv(name: string): string { const value = process.env[name]?.trim(); if (!value) { throw new Error(`Missing required environment variable: ${name}`); } return value; } export function createSqlClient(): SqlClient { loadLocalEnv(); return postgres(requireEnv('DATABASE_URL'), { prepare: false }); } export function deterministicId(input: string): string { const digest = createHash('sha256').update(input).digest('hex'); const part3 = `5${digest.slice(13, 16)}`; const part4 = `a${digest.slice(17, 20)}`; return [ digest.slice(0, 8), digest.slice(8, 12), part3, part4, digest.slice(20, 32) ].join('-'); } export const NODE_TS_RUNTIME_ARGS = [ '--disable-warning=ExperimentalWarning', '--disable-warning=MODULE_TYPELESS_PACKAGE_JSON', '--experimental-strip-types' ]; export function runNodeScript(label: string, scriptPath: string, args: string[] = []) { const result = spawnSync( process.execPath, [...NODE_TS_RUNTIME_ARGS, scriptPath, ...args], { stdio: 'inherit', env: process.env } ); if (result.status !== 0) { throw new Error(`[${label}] failed with exit code ${result.status ?? 1}`); } } export function runCommand(label: string, command: string, args: string[] = []) { const result = spawnSync(command, args, { stdio: 'inherit', env: process.env, shell: process.platform === 'win32' }); if (result.status !== 0) { throw new Error(`[${label}] failed with exit code ${result.status ?? 1}`); } }