import fs from 'node:fs'; const MAX_SAFE_DEV_MEMORY = 4 * 1024 * 1024 * 1024; const MAX_SAFE_DEV_CPUS = 4; const MAX_SAFE_DEV_PIDS = 256; const positive = (value) => typeof value === 'number' ? value > 0 : Number(value) > 0; export const validateComposeModel = (model, mode) => { const errors = []; const runtime = model?.services?.runtime; if (!runtime) return ['runtime service is missing']; if (!positive(runtime.mem_limit)) errors.push('runtime memory limit must be positive'); if (!positive(runtime.memswap_limit)) errors.push('runtime memory+swap limit must be positive'); if (Number(runtime.memswap_limit) !== Number(runtime.mem_limit)) { errors.push('runtime memory+swap limit must equal its memory limit'); } if (!positive(runtime.cpus)) errors.push('runtime CPU limit must be positive'); if (!positive(runtime.pids_limit)) errors.push('runtime PID limit must be positive'); if (mode === 'development') { if (runtime.environment?.RUNTIME_MODE !== 'development') { errors.push('development runtime mode must be literal development'); } if (runtime.environment?.CORE_SOURCE_MODE !== 'bind') { errors.push('development Core source mode must be bind'); } } else if (runtime.environment?.CORE_SOURCE_MODE !== 'clone') { errors.push(`${mode} Core source mode must be clone`); } else if (runtime.environment?.RUNTIME_MODE === 'development') { errors.push(`${mode} runtime must not use development mode`); } if (mode === 'development' || mode === 'smoke') { for (const name of ['postgres', 'redis', 'runtime', 'caddy']) { if (model?.services?.[name]?.restart !== 'no') errors.push(`${name} ${mode} restart policy must be no`); } if (Number(runtime.mem_limit) > MAX_SAFE_DEV_MEMORY) errors.push(`${mode} runtime memory limit exceeds 4 GiB`); if (Number(runtime.cpus) > MAX_SAFE_DEV_CPUS) errors.push(`${mode} runtime CPU limit exceeds 4`); if (Number(runtime.pids_limit) > MAX_SAFE_DEV_PIDS) errors.push(`${mode} runtime PID limit exceeds 256`); } return errors; }; if (process.argv[1] && process.argv[1].endsWith('validate-compose-model.mjs')) { const mode = process.argv[2]; const model = JSON.parse(fs.readFileSync(0, 'utf8')); const errors = validateComposeModel(model, mode); if (errors.length) { console.error(`Compose ${mode} safety validation failed:`); for (const error of errors) console.error(`- ${error}`); process.exitCode = 65; } else { console.log(`Compose ${mode} safety validation passed.`); } }