import { join } from 'node:path'; import { serviceDir } from './paths'; // Running a sidecar's setup.sh, streaming what it prints, and collecting what it hands back. // // The script contract lives in `templates/README.md`. Two halves of it are implemented here: answers go // in as environment (never as prompts, which behind a web form is a hang with no output), and results // come back as `OFFICER_RESULT_=value` lines on stdout. // // ── Why results are a line protocol and not JSON on stdout ── // // The same stream is the user's live log — it goes to a terminal panel while the install runs. A script // that must emit clean JSON cannot also narrate, and one that emits both needs a framing convention // anyway. A prefixed line is that convention, it survives being interleaved with `docker compose` // output, and a human reading the log can see exactly what was handed back. // // This mirrors the progress sentinel the job runner already uses (`@@officer:progress@@`), for the same // reason and with the same rule: the marker lines are plucked out of the stream, and everything else is // passed through as narration. const RESULT_PREFIX = 'OFFICER_RESULT_'; /** * Pull `OFFICER_RESULT_*` assignments out of a script's output. * * Pure, so the protocol is testable without spawning anything. * * Later lines win. A script that reports a value twice has changed its mind — a retry inside the script * that finally succeeded, say — and the last word is the one that reflects reality. */ export function parseResults(output: string): Record { const results: Record = {}; for (const raw of output.split('\n')) { const line = raw.trim(); if (!line.startsWith(RESULT_PREFIX)) continue; const eq = line.indexOf('='); // A prefix with no `=` is a script bug, not a value. Skipping beats storing a key with an empty // string, which would look like a deliberate blank later. if (eq <= RESULT_PREFIX.length) continue; const key = line.slice(RESULT_PREFIX.length, eq).toLowerCase(); results[key] = line.slice(eq + 1); } return results; } export type RunScriptParams = { sidecarId: string; /** Directory holding the template's `setup.sh`, i.e. `app-store/templates/