refactor(opencode): remove JSON storage migration (#30461)

This commit is contained in:
Dax
2026-06-02 19:05:14 -04:00
committed by GitHub
parent 113e7be5ac
commit ca2acc4f8d
20 changed files with 17 additions and 1590 deletions
-8
View File
@@ -18,13 +18,5 @@ declare module "virtual:opencode-server" {
export namespace Log {
export const init: typeof import("../../../opencode/dist/types/src/node").Log.init
}
export namespace Database {
export const getPath: typeof import("../../../opencode/dist/types/src/node").Database.getPath
export const Client: typeof import("../../../opencode/dist/types/src/node").Database.Client
}
export namespace JsonMigration {
export type Progress = import("../../../opencode/dist/types/src/node").JsonMigration.Progress
export const run: typeof import("../../../opencode/dist/types/src/node").JsonMigration.run
}
export const bootstrap: typeof import("../../../opencode/dist/types/src/node").bootstrap
}
+8 -63
View File
@@ -1,6 +1,5 @@
import { randomUUID } from "node:crypto"
import { EventEmitter } from "node:events"
import { existsSync, mkdirSync, rmSync } from "node:fs"
import { mkdirSync, rmSync } from "node:fs"
import * as http from "node:http"
import { createServer } from "node:net"
import { homedir, tmpdir } from "node:os"
@@ -11,10 +10,10 @@ import { app, BrowserWindow } from "electron"
import contextMenu from "electron-context-menu"
import type { InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types"
import type { ServerReadyData, WslConfig } from "../preload/types"
import { checkAppExists, resolveAppPath, wslPath } from "./apps"
import { CHANNEL, UPDATER_ENABLED } from "./constants"
import { registerIpcHandlers, sendDeepLinks, sendMenuCommand, sendSqliteMigrationProgress } from "./ipc"
import { registerIpcHandlers, sendDeepLinks, sendMenuCommand } from "./ipc"
import { exportDebugLogs, initCrashReporter, initLogging, startNetLog, write as writeLog } from "./logging"
import { parseMarkdown } from "./markdown"
import { createMenu } from "./menu"
@@ -28,7 +27,6 @@ import {
type SidecarListener,
} from "./server"
import {
createLoadingWindow,
createMainWindow,
registerRendererProtocol,
setRelaunchHandler,
@@ -56,9 +54,6 @@ let logger: ReturnType<typeof initLogging>
let mainWindow: BrowserWindow | null = null
let server: SidecarListener | null = null
const initEmitter = new EventEmitter()
let initStep: InitStep = { phase: "server_waiting" }
const pendingDeepLinks: string[] = []
function useEnvProxy() {
@@ -76,12 +71,6 @@ function emitDeepLinks(urls: string[]) {
if (mainWindow) sendDeepLinks(mainWindow, urls)
}
function setInitStep(step: InitStep) {
initStep = step
logger.log("init step", { step })
initEmitter.emit("step", step)
}
async function killSidecar() {
if (!server) return
const current = server
@@ -219,23 +208,15 @@ const main = Effect.gen(function* () {
}
const serverReady = Deferred.makeUnsafe<ServerReadyData>()
const loadingComplete = Deferred.makeUnsafe<void>()
registerIpcHandlers({
killSidecar: () => killSidecar(),
awaitInitialization: Effect.fnUntraced(
function* (sendStep) {
sendStep(initStep)
const listener = (step: InitStep) => sendStep(step)
initEmitter.on("step", listener)
try {
logger.log("awaiting server ready")
const res = yield* Deferred.await(serverReady)
logger.log("server ready", { url: res.url })
return res
} finally {
initEmitter.off("step", listener)
}
function* () {
logger.log("awaiting server ready")
const res = yield* Deferred.await(serverReady)
logger.log("server ready", { url: res.url })
return res
},
(e) => Effect.runPromise(e),
),
@@ -251,7 +232,6 @@ const main = Effect.gen(function* () {
checkAppExists: (appName) => checkAppExists(appName),
wslPath: async (path, mode) => wslPath(path, mode),
resolveAppPath: async (appName) => resolveAppPath(appName),
loadingWindowComplete: () => Deferred.doneUnsafe(loadingComplete, Effect.void),
runUpdater: async (alertOnFail) => checkForUpdates(alertOnFail, killSidecar),
checkUpdate: async () => checkUpdate(),
installUpdate: async () => installUpdate(killSidecar),
@@ -275,15 +255,6 @@ const main = Effect.gen(function* () {
),
)
const needsMigration = ((): boolean => {
if (process.env.OPENCODE_DB === ":memory:") return false
const xdg = process.env.XDG_DATA_HOME
const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".local", "share")
return !existsSync(join(base, "opencode", "opencode.db"))
})()
let overlay: BrowserWindow | null = null
const port = yield* Effect.gen(function* () {
const fromEnv = process.env.OPENCODE_PORT
if (fromEnv) {
@@ -314,21 +285,13 @@ const main = Effect.gen(function* () {
const loadingTask = yield* Effect.gen(function* () {
logger.log("sidecar connection started", { url })
initEmitter.on("sqlite", (progress: SqliteMigrationProgress) => {
setInitStep({ phase: "sqlite_waiting" })
if (overlay) sendSqliteMigrationProgress(overlay, progress)
if (mainWindow) sendSqliteMigrationProgress(mainWindow, progress)
})
ensureLoopbackNoProxy()
useEnvProxy()
logger.log("spawning sidecar", { url })
const { listener, health } = yield* Effect.promise(() =>
spawnLocalServer(hostname, port, password, {
needsMigration,
userDataPath: app.getPath("userData"),
onSqliteProgress: (progress) => initEmitter.emit("sqlite", progress),
onStdout: (message) => writeLog("server", "stdout", { message }),
onStderr: (message) => writeLog("server", "stderr", { message }, "warn"),
onExit: (code) => writeLog("utility", "sidecar exited", { code }, "warn"),
@@ -353,23 +316,7 @@ const main = Effect.gen(function* () {
logger.log("loading task finished")
}).pipe(Effect.forkChild)
if (needsMigration) {
const show = yield* loadingTask.pipe(
Fiber.await,
Effect.timeout("1 second"),
Effect.as(false),
Effect.catch(() => Effect.succeed(true)),
)
if (show) {
overlay = createLoadingWindow()
yield* Effect.sleep("1 second")
}
}
yield* Fiber.await(loadingTask)
setInitStep({ phase: "done" })
if (overlay) yield* Deferred.await(loadingComplete)
mainWindow = createMainWindow()
if (mainWindow) {
@@ -389,8 +336,6 @@ const main = Effect.gen(function* () {
},
})
}
overlay?.close()
})
Effect.runFork(main)
+2 -13
View File
@@ -4,10 +4,8 @@ import type { IpcMainEvent, IpcMainInvokeEvent } from "electron"
import type { DesktopMenuAction } from "@opencode-ai/app/desktop-menu"
import type {
InitStep,
FatalRendererError,
ServerReadyData,
SqliteMigrationProgress,
TitlebarTheme,
WindowConfig,
WslConfig,
@@ -23,7 +21,7 @@ const pickerFilters = (ext?: string[]) => {
type Deps = {
killSidecar: () => Promise<void> | void
awaitInitialization: (sendStep: (step: InitStep) => void) => Promise<ServerReadyData>
awaitInitialization: () => Promise<ServerReadyData>
getWindowConfig: () => Promise<WindowConfig> | WindowConfig
consumeInitialDeepLinks: () => Promise<string[]> | string[]
getDefaultServerUrl: () => Promise<string | null> | string | null
@@ -36,7 +34,6 @@ type Deps = {
checkAppExists: (appName: string) => Promise<boolean> | boolean
wslPath: (path: string, mode: "windows" | "linux" | null) => Promise<string>
resolveAppPath: (appName: string) => Promise<string | null>
loadingWindowComplete: () => void
runUpdater: (alertOnFail: boolean) => Promise<void> | void
checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }>
installUpdate: () => Promise<void> | void
@@ -47,10 +44,7 @@ type Deps = {
export function registerIpcHandlers(deps: Deps) {
ipcMain.handle("kill-sidecar", () => deps.killSidecar())
ipcMain.handle("await-initialization", (event: IpcMainInvokeEvent) => {
const send = (step: InitStep) => event.sender.send("init-step", step)
return deps.awaitInitialization(send)
})
ipcMain.handle("await-initialization", () => deps.awaitInitialization())
ipcMain.handle("get-window-config", () => deps.getWindowConfig())
ipcMain.handle("consume-initial-deep-links", () => deps.consumeInitialDeepLinks())
ipcMain.handle("get-default-server-url", () => deps.getDefaultServerUrl())
@@ -69,7 +63,6 @@ export function registerIpcHandlers(deps: Deps) {
deps.wslPath(path, mode),
)
ipcMain.handle("resolve-app-path", (_event: IpcMainInvokeEvent, appName: string) => deps.resolveAppPath(appName))
ipcMain.on("loading-window-complete", () => deps.loadingWindowComplete())
ipcMain.handle("run-updater", (_event: IpcMainInvokeEvent, alertOnFail: boolean) => deps.runUpdater(alertOnFail))
ipcMain.handle("check-update", () => deps.checkUpdate())
ipcMain.handle("install-update", () => deps.installUpdate())
@@ -216,10 +209,6 @@ export function registerIpcHandlers(deps: Deps) {
})
}
export function sendSqliteMigrationProgress(win: BrowserWindow, progress: SqliteMigrationProgress) {
win.webContents.send("sqlite-migration-progress", progress)
}
export function sendMenuCommand(win: BrowserWindow, id: string) {
win.webContents.send("menu-command", id)
}
-10
View File
@@ -5,14 +5,12 @@ import type { Details } from "electron"
import { DEFAULT_SERVER_URL_KEY, WSL_ENABLED_KEY } from "./constants"
import { getUserShell, loadShellEnv } from "./shell-env"
import { getStore } from "./store"
import type { SqliteMigrationProgress } from "../preload/types"
export type WslConfig = { enabled: boolean }
export type HealthCheck = { wait: Promise<void> }
type SidecarMessage =
| { type: "sqlite"; progress: SqliteMigrationProgress }
| { type: "ready" }
| { type: "stopped" }
| { type: "error"; error: { message: string; stack?: string } }
@@ -24,9 +22,7 @@ const SIDECAR_START_STALL_TIMEOUT = 60_000
const SIDECAR_STOP_TIMEOUT = 6_000
type SpawnLocalServerOptions = {
needsMigration: boolean
userDataPath: string
onSqliteProgress?: (progress: SqliteMigrationProgress) => void
onStdout?: (message: string) => void
onStderr?: (message: string) => void
onExit?: (code: number) => void
@@ -118,11 +114,6 @@ export async function spawnLocalServer(
}
const onMessage = (message: SidecarMessage) => {
if (message.type === "sqlite") {
refreshTimeout()
options.onSqliteProgress?.(message.progress)
return
}
if (message.type === "ready") {
if (done) return
done = true
@@ -152,7 +143,6 @@ export async function spawnLocalServer(
port,
password,
userDataPath: options.userDataPath,
needsMigration: options.needsMigration,
})
}).catch((error) => {
if (!exited) child.kill()
+1 -21
View File
@@ -1,4 +1,3 @@
import { drizzle } from "drizzle-orm/node-sqlite/driver"
import * as http from "node:http"
import * as tls from "node:tls"
@@ -17,14 +16,12 @@ type StartCommand = {
port: number
password: string
userDataPath: string
needsMigration: boolean
}
type StopCommand = { type: "stop" }
type SidecarCommand = StartCommand | StopCommand
type SidecarMessage =
| { type: "sqlite"; progress: { type: "InProgress"; value: number } | { type: "Done" } }
| { type: "ready" }
| { type: "stopped" }
| { type: "error"; error: { message: string; stack?: string } }
@@ -57,24 +54,9 @@ async function start(command: StartCommand) {
ensureLoopbackNoProxy()
useSystemCertificates()
useEnvProxy()
const { Database, JsonMigration, Log, Server } = await import("virtual:opencode-server")
const { Log, Server } = await import("virtual:opencode-server")
await Log.init({ level: "WARN" })
if (command.needsMigration) {
await JsonMigration.run(drizzle({ client: Database.Client().$client }), {
progress: (event: { current: number; total: number }) => {
parentPort.postMessage({
type: "sqlite",
progress: {
type: "InProgress",
value: event.total === 0 ? 100 : Math.round((event.current / event.total) * 100),
},
})
},
})
parentPort.postMessage({ type: "sqlite", progress: { type: "Done" } })
}
listener = await Server.listen({
port: command.port,
hostname: command.hostname,
@@ -155,14 +137,12 @@ function parseCommand(value: unknown): SidecarCommand | undefined {
if (typeof command.port !== "number") return
if (typeof command.password !== "string") return
if (typeof command.userDataPath !== "string") return
if (typeof command.needsMigration !== "boolean") return
return {
type: "start",
hostname: command.hostname,
port: command.port,
password: command.password,
userDataPath: command.userDataPath,
needsMigration: command.needsMigration,
}
}
-35
View File
@@ -181,41 +181,6 @@ export function createMainWindow() {
return win
}
export function createLoadingWindow() {
const mode = tone()
const win = new BrowserWindow({
width: 640,
height: 480,
resizable: false,
center: true,
show: true,
autoHideMenuBar: true,
icon: iconPath(),
backgroundColor: backgroundColor ?? defaultBackgroundColor(),
...(process.platform === "darwin" ? { titleBarStyle: "hidden" as const } : {}),
...(process.platform === "win32"
? {
frame: false,
titleBarStyle: "hidden" as const,
titleBarOverlay: overlay({ mode }),
}
: {}),
webPreferences: {
preload: join(root, "../preload/index.js"),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
})
allowRendererPermissions(win)
wireWindowRecovery(win, "loading")
loadWindow(win, "loading.html")
return win
}
export function registerRendererProtocol() {
if (protocol.isProtocolHandled(rendererProtocol)) return