66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
import type { FirstRunSetupDecision } from './first-run-setup-gate'
|
|
|
|
export interface PrimaryBackendStartupOptions<Backend, RuntimeBackend, Remote, Connection> {
|
|
connectRemote: (remote: Remote) => Promise<Connection>
|
|
ensureLocalRuntime: (backend: Backend) => Promise<RuntimeBackend>
|
|
prepareLocalBackend: () => Backend | Promise<Backend>
|
|
resolveRemote: () => Promise<Remote | null>
|
|
waitForDecision: (backend: Backend) => Promise<FirstRunSetupDecision>
|
|
waitForLocalStart: () => Promise<unknown>
|
|
}
|
|
|
|
export type PrimaryBackendStartupResult<RuntimeBackend, Connection> =
|
|
{ kind: 'local'; backend: RuntimeBackend } | { kind: 'remote'; connection: Connection }
|
|
|
|
export class FirstRunSetupResetError extends Error {
|
|
readonly firstRunSetupReset = true
|
|
|
|
constructor() {
|
|
super('First-run setup was reset before a choice completed.')
|
|
this.name = 'FirstRunSetupResetError'
|
|
}
|
|
}
|
|
|
|
// Owns the production startHermes path up to the local process spawn. Keeping
|
|
// the full ordering here makes the first-run remote boundary executable in a
|
|
// test: an already-saved remote wins immediately; otherwise update exclusion
|
|
// and local backend resolution happen before the setup gate, and a remote Apply
|
|
// re-resolves persisted config without ever entering ensureRuntime/bootstrap.
|
|
export async function runPrimaryBackendStartup<Backend, RuntimeBackend, Remote, Connection>({
|
|
connectRemote,
|
|
ensureLocalRuntime,
|
|
prepareLocalBackend,
|
|
resolveRemote,
|
|
waitForDecision,
|
|
waitForLocalStart
|
|
}: PrimaryBackendStartupOptions<Backend, RuntimeBackend, Remote, Connection>): Promise<
|
|
PrimaryBackendStartupResult<RuntimeBackend, Connection>
|
|
> {
|
|
const savedRemote = await resolveRemote()
|
|
|
|
if (savedRemote) {
|
|
return { kind: 'remote', connection: await connectRemote(savedRemote) }
|
|
}
|
|
|
|
await waitForLocalStart()
|
|
|
|
const backend = await prepareLocalBackend()
|
|
const decision = await waitForDecision(backend)
|
|
|
|
if (decision === 'remote-applied') {
|
|
const appliedRemote = await resolveRemote()
|
|
|
|
if (!appliedRemote) {
|
|
throw new Error('First-run remote setup completed without a saved remote backend.')
|
|
}
|
|
|
|
return { kind: 'remote', connection: await connectRemote(appliedRemote) }
|
|
}
|
|
|
|
if (decision === 'reset') {
|
|
throw new FirstRunSetupResetError()
|
|
}
|
|
|
|
return { kind: 'local', backend: await ensureLocalRuntime(backend) }
|
|
}
|