import { existsSync, mkdirSync, readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { spawn } from 'node:child_process'; export function parseArgs(argv) { const parsed = { flags: new Set(), values: new Map(), positionals: [] }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === '--') continue; if (!arg.startsWith('--')) { parsed.positionals.push(arg); continue; } const eq = arg.indexOf('='); if (eq !== -1) { parsed.values.set(arg.slice(2, eq), arg.slice(eq + 1)); continue; } const key = arg.slice(2); const next = argv[index + 1]; if (next && !next.startsWith('--')) { parsed.values.set(key, next); index += 1; } else { parsed.flags.add(key); } } return parsed; } export function option(args, name, fallback = undefined) { return args.values.has(name) ? args.values.get(name) : fallback; } export function flag(args, name) { return args.flags.has(name); } export function readEnvFile(filePath, { optional = false } = {}) { if (!existsSync(filePath)) { if (optional) return {}; throw new Error(`Env file not found: ${filePath}`); } const env = {}; const content = readFileSync(filePath, 'utf8'); for (const rawLine of content.split(/\r?\n/)) { const line = rawLine.trim(); if (!line || line.startsWith('#')) continue; const eq = line.indexOf('='); if (eq === -1) continue; const key = line.slice(0, eq).trim(); let value = line.slice(eq + 1).trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } env[key] = value; } return env; } export function ensureParentDir(filePath) { mkdirSync(dirname(filePath), { recursive: true }); } export function timestampSlug(date = new Date()) { return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z'); } export function shellQuote(value) { if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value; return `'${value.replace(/'/g, "'\\''")}'`; } export function commandToString(command, { stdin, stdout } = {}) { const rendered = command.map((part) => shellQuote(part)).join(' '); const withStdin = stdin ? `${rendered} < ${shellQuote(stdin)}` : rendered; return stdout ? `${withStdin} > ${shellQuote(stdout)}` : withStdin; } export function composePrefix({ envFile, composeFile }) { return ['docker', 'compose', '--env-file', envFile, '-f', composeFile]; } export function resolvePath(path) { return resolve(process.cwd(), path); } export function runCommand(command, options = {}) { return new Promise((resolveRun, rejectRun) => { const child = spawn(command[0], command.slice(1), { stdio: options.stdio ?? 'inherit', cwd: options.cwd ?? process.cwd(), }); child.on('error', rejectRun); child.on('close', (code) => { if (code === 0) { resolveRun(); } else { rejectRun(new Error(`Command failed (${code}): ${commandToString(command)}`)); } }); }); } export function quotePgLiteral(value) { return `'${value.replace(/'/g, "''")}'`; }