/** * The per-device instance registry: which autofirmer processes run on this * machine, on which ports, against which databases. * * scripts/instances.json is created and edited by add-instance.sh / * remove-instance.sh. When the file is absent the device is a legacy * single-instance install and everything behaves exactly as it always has: * one PM2 process named `autofirmer` on port 3000 using ./autotrader.sqlite. * * Entry shape: { "name": "autofirmer-2", "port": 3100, "db": ".data/instance-2/autotrader.sqlite" } * `db` is relative to the project root. Instance 1 keeps the legacy * name/port/db so the clicker and extension (hardwired to :3000) still work. */ import { existsSync, readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); export const REGISTRY_FILE = path.join(ROOT, 'scripts', 'instances.json'); const LEGACY = [{ name: 'autofirmer', port: Number(process.env.AUTOFIRMER_PORT ?? '3000'), db: 'autotrader.sqlite', }]; export function loadInstances() { if (!existsSync(REGISTRY_FILE)) return LEGACY; let parsed; try { parsed = JSON.parse(readFileSync(REGISTRY_FILE, 'utf8')); } catch (err) { throw new Error(`scripts/instances.json is not valid JSON: ${err.message}`); } if (!Array.isArray(parsed) || parsed.length === 0) return LEGACY; for (const e of parsed) { if (typeof e?.name !== 'string' || !e.name || !Number.isInteger(e?.port) || typeof e?.db !== 'string' || !e.db) { throw new Error(`scripts/instances.json entry is malformed: ${JSON.stringify(e)}`); } } return parsed; }