|
|
@@ -11,7 +11,7 @@ import type {
|
|
|
WslServersEvent,
|
|
|
WslServersState,
|
|
|
} from "../../preload/types"
|
|
|
-import { WSL_SERVERS_KEY } from "../constants"
|
|
|
+import { WSL_SERVERS_KEY } from "../store-keys"
|
|
|
import { getStore } from "../store"
|
|
|
import { expectOpencodeVersion, pendingRestartAfterWslInstall, wslServerIdsToStartOnInitialize } from "./startup"
|
|
|
import { clearWslDistroState, wslServerIdToRestart } from "./policy"
|
|
|
@@ -43,18 +43,33 @@ type ControllerLogger = {
|
|
|
error: (message: string, meta?: unknown) => void
|
|
|
}
|
|
|
|
|
|
+type WslServersControllerOptions = {
|
|
|
+ logger?: ControllerLogger
|
|
|
+ readServers?: () => WslServerConfig[]
|
|
|
+ writeServers?: (servers: WslServerConfig[]) => void
|
|
|
+ resolveOpencode?: typeof resolveWslOpencode
|
|
|
+ readCommandVersion?: typeof readWslCommandVersion
|
|
|
+}
|
|
|
+
|
|
|
export type WslServersController = ReturnType<typeof createWslServersController>
|
|
|
|
|
|
export function wslServerIdForDistro(distro: string) {
|
|
|
return `wsl:${distro}`
|
|
|
}
|
|
|
|
|
|
-export function createWslServersController(appVersion: string, spawnSidecar: SpawnSidecar, logger?: ControllerLogger) {
|
|
|
+export function createWslServersController(
|
|
|
+ appVersion: string,
|
|
|
+ spawnSidecar: SpawnSidecar,
|
|
|
+ options?: WslServersControllerOptions,
|
|
|
+) {
|
|
|
let state: WslServersState = initialState()
|
|
|
const listeners = new Set<(event: WslServersEvent) => void>()
|
|
|
const sidecars = new Map<string, RunningSidecar>()
|
|
|
const startAttempts = new Map<string, number>()
|
|
|
let jobAbort: AbortController | undefined
|
|
|
+ const logger = options?.logger
|
|
|
+ const readServers = options?.readServers ?? readPersistedServers
|
|
|
+ const writeServers = options?.writeServers ?? writePersistedServers
|
|
|
|
|
|
const emit = () => {
|
|
|
for (const listener of listeners) listener({ type: "state", state })
|
|
|
@@ -66,7 +81,7 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
|
|
|
}
|
|
|
|
|
|
const persistServers = (servers: WslServerConfig[]) => {
|
|
|
- getStore().set(WSL_SERVERS_KEY, { servers })
|
|
|
+ writeServers(servers)
|
|
|
}
|
|
|
|
|
|
const updateServer = (id: string, update: (item: WslServerItem) => WslServerItem) => {
|
|
|
@@ -89,7 +104,7 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
|
|
|
}
|
|
|
|
|
|
const refreshFromStore = () => {
|
|
|
- const persisted = readPersistedServers()
|
|
|
+ const persisted = readServers()
|
|
|
const items: WslServerItem[] = persisted.map((config) => {
|
|
|
const existing = state.servers.find((item) => item.config.id === config.id)
|
|
|
return {
|
|
|
@@ -113,10 +128,50 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ const checkOpencode = async (distro: string, opts?: { signal?: AbortSignal }) => {
|
|
|
+ const resolved = await (options?.resolveOpencode ?? resolveWslOpencode)(distro, opts)
|
|
|
+ const version = resolved ? await (options?.readCommandVersion ?? readWslCommandVersion)(resolved, distro, opts) : null
|
|
|
+ return opencodeCheck(distro, resolved, version, appVersion)
|
|
|
+ }
|
|
|
+
|
|
|
const refreshOpencodeCheck = async (distro: string, opts?: { signal?: AbortSignal }) => {
|
|
|
- const resolved = await resolveWslOpencode(distro, opts)
|
|
|
- const version = resolved ? await readWslCommandVersion(resolved, distro, opts) : null
|
|
|
- setOpencodeCheck(distro, opencodeCheck(distro, resolved, version, appVersion))
|
|
|
+ setOpencodeCheck(distro, await checkOpencode(distro, opts))
|
|
|
+ }
|
|
|
+
|
|
|
+ const hasServer = (id: string, distro: string) => {
|
|
|
+ return state.servers.some((item) => item.config.id === id && item.config.distro === distro)
|
|
|
+ }
|
|
|
+
|
|
|
+ const refreshOpencodeCheckBackground = (id: string, distro: string) => {
|
|
|
+ void checkOpencode(distro)
|
|
|
+ .then((check) => {
|
|
|
+ if (!hasServer(id, distro)) return
|
|
|
+ setOpencodeCheck(distro, check)
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ const message = error instanceof Error ? error.message : String(error)
|
|
|
+ logger?.error("wsl opencode check failed", { id, distro, message })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const refreshOpencodeChecks = async () => {
|
|
|
+ await Promise.all(
|
|
|
+ state.servers.map((item) =>
|
|
|
+ checkOpencode(item.config.distro)
|
|
|
+ .then((check) => {
|
|
|
+ if (!hasServer(item.config.id, item.config.distro)) return
|
|
|
+ setOpencodeCheck(item.config.distro, check)
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ const message = error instanceof Error ? error.message : String(error)
|
|
|
+ logger?.error("wsl opencode check failed", {
|
|
|
+ id: item.config.id,
|
|
|
+ distro: item.config.distro,
|
|
|
+ message,
|
|
|
+ })
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
const refreshDistroLists = async (opts: { signal?: AbortSignal }) => {
|
|
|
@@ -170,10 +225,7 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
|
|
|
setRuntime(id, { kind: "failed", message })
|
|
|
logger?.error("wsl sidecar exited", { id, distro: item.config.distro, code, signal })
|
|
|
})
|
|
|
- void refreshOpencodeCheck(item.config.distro).catch((error) => {
|
|
|
- const message = error instanceof Error ? error.message : String(error)
|
|
|
- logger?.error("wsl opencode check failed", { id, distro: item.config.distro, message })
|
|
|
- })
|
|
|
+ refreshOpencodeCheckBackground(id, item.config.distro)
|
|
|
logger?.log("wsl sidecar ready", { id, distro: item.config.distro, url: sidecar.url })
|
|
|
} catch (error) {
|
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
|
@@ -225,6 +277,7 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
|
|
|
|
|
|
async initialize() {
|
|
|
refreshFromStore()
|
|
|
+ void refreshOpencodeChecks()
|
|
|
for (const id of wslServerIdsToStartOnInitialize(state.servers.map((item) => item.config))) void startServer(id)
|
|
|
},
|
|
|
|
|
|
@@ -311,7 +364,7 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
|
|
|
id,
|
|
|
distro,
|
|
|
}
|
|
|
- persistServers([...readPersistedServers(), config])
|
|
|
+ persistServers([...readServers(), config])
|
|
|
setState({
|
|
|
servers: [...state.servers, { config, runtime: { kind: "starting" } }],
|
|
|
})
|
|
|
@@ -323,7 +376,7 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
|
|
|
const distro = state.servers.find((item) => item.config.id === id)?.config.distro
|
|
|
invalidateStartAttempt(id)
|
|
|
await stopServerInternal(id)
|
|
|
- const remaining = readPersistedServers().filter((item) => item.id !== id)
|
|
|
+ const remaining = readServers().filter((item) => item.id !== id)
|
|
|
persistServers(remaining)
|
|
|
setState({
|
|
|
servers: state.servers.filter((item) => item.config.id !== id),
|
|
|
@@ -371,6 +424,10 @@ function readPersistedServers(): WslServerConfig[] {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
+function writePersistedServers(servers: WslServerConfig[]) {
|
|
|
+ getStore().set(WSL_SERVERS_KEY, { servers })
|
|
|
+}
|
|
|
+
|
|
|
function normalizePersistedServer(value: unknown): WslServerConfig[] {
|
|
|
if (!value || typeof value !== "object") return []
|
|
|
const record = value as Record<string, unknown>
|