diff --git a/packages/opencode/src/cli/cmd/uninstall.ts b/packages/opencode/src/cli/cmd/uninstall.ts index de41f32a0d..31830f0859 100644 --- a/packages/opencode/src/cli/cmd/uninstall.ts +++ b/packages/opencode/src/cli/cmd/uninstall.ts @@ -1,6 +1,7 @@ import type { Argv } from "yargs" import { UI } from "../ui" import * as prompts from "@clack/prompts" +import { AppRuntime } from "@/effect/app-runtime" import { Installation } from "../../installation" import { Global } from "../../global" import fs from "fs/promises" @@ -57,7 +58,7 @@ export const UninstallCommand = { UI.empty() prompts.intro("Uninstall OpenCode") - const method = await Installation.method() + const method = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method())) prompts.log.info(`Installation method: ${method}`) const targets = await collectRemovalTargets(args, method) diff --git a/packages/opencode/src/cli/cmd/upgrade.ts b/packages/opencode/src/cli/cmd/upgrade.ts index 0182056633..3ffa0f228c 100644 --- a/packages/opencode/src/cli/cmd/upgrade.ts +++ b/packages/opencode/src/cli/cmd/upgrade.ts @@ -1,6 +1,7 @@ import type { Argv } from "yargs" import { UI } from "../ui" import * as prompts from "@clack/prompts" +import { AppRuntime } from "@/effect/app-runtime" import { Installation } from "../../installation" export const UpgradeCommand = { @@ -24,7 +25,7 @@ export const UpgradeCommand = { UI.println(UI.logo(" ")) UI.empty() prompts.intro("Upgrade") - const detectedMethod = await Installation.method() + const detectedMethod = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method())) const method = (args.method as Installation.Method) ?? detectedMethod if (method === "unknown") { prompts.log.error(`opencode is installed to ${process.execPath} and may be managed by a package manager`) @@ -42,7 +43,9 @@ export const UpgradeCommand = { } } prompts.log.info("Using method: " + method) - const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest() + const target = args.target + ? args.target.replace(/^v/, "") + : await AppRuntime.runPromise(Installation.Service.use((svc) => svc.latest())) if (Installation.VERSION === target) { prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`) @@ -53,7 +56,9 @@ export const UpgradeCommand = { prompts.log.info(`From ${Installation.VERSION} → ${target}`) const spinner = prompts.spinner() spinner.start("Upgrading...") - const err = await Installation.upgrade(method, target).catch((err) => err) + const err = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.upgrade(method, target))).catch( + (err) => err, + ) if (err) { spinner.stop("Upgrade failed", 1) if (err instanceof Installation.UpgradeFailedError) { diff --git a/packages/opencode/src/cli/upgrade.ts b/packages/opencode/src/cli/upgrade.ts index 7b7199d4ea..e902dcb921 100644 --- a/packages/opencode/src/cli/upgrade.ts +++ b/packages/opencode/src/cli/upgrade.ts @@ -1,12 +1,13 @@ import { Bus } from "@/bus" import { Config } from "@/config/config" +import { AppRuntime } from "@/effect/app-runtime" import { Flag } from "@/flag/flag" import { Installation } from "@/installation" export async function upgrade() { const config = await Config.getGlobal() - const method = await Installation.method() - const latest = await Installation.latest(method).catch(() => {}) + const method = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method())) + const latest = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.latest(method))).catch(() => {}) if (!latest) return if (Flag.OPENCODE_ALWAYS_NOTIFY_UPDATE) { @@ -25,7 +26,7 @@ export async function upgrade() { } if (method === "unknown") return - await Installation.upgrade(method, latest) + await AppRuntime.runPromise(Installation.Service.use((svc) => svc.upgrade(method, latest))) .then(() => Bus.publish(Installation.Event.Updated, { version: latest })) .catch(() => {}) } diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 06b673217f..29f9bf1be2 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -1,7 +1,6 @@ import { Effect, Layer, Schema, Context, Stream } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" -import { makeRuntime } from "@/effect/run-service" import { withTransientReadRetry } from "@/util/effect-http-client" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import path from "path" @@ -338,18 +337,4 @@ export namespace Installation { Layer.provide(FetchHttpClient.layer), Layer.provide(CrossSpawnSpawner.defaultLayer), ) - - const { runPromise } = makeRuntime(Service, defaultLayer) - - export async function method(): Promise { - return runPromise((svc) => svc.method()) - } - - export async function latest(installMethod?: Method): Promise { - return runPromise((svc) => svc.latest(installMethod)) - } - - export async function upgrade(m: Method, target: string): Promise { - return runPromise((svc) => svc.upgrade(m, target)) - } } diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index ec7afcf23d..6b0a9a164b 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -1,10 +1,12 @@ import { Hono, type Context } from "hono" import { describeRoute, resolver, validator } from "hono-openapi" import { streamSSE } from "hono/streaming" +import { Effect } from "effect" import z from "zod" import { BusEvent } from "@/bus/bus-event" import { SyncEvent } from "@/sync" import { GlobalBus } from "@/bus/global" +import { AppRuntime } from "@/effect/app-runtime" import { AsyncQueue } from "@/util/queue" import { Instance } from "../../project/instance" import { Installation } from "@/installation" @@ -290,25 +292,41 @@ export const GlobalRoutes = lazy(() => }), ), async (c) => { - const method = await Installation.method() - if (method === "unknown") { - return c.json({ success: false, error: "Unknown installation method" }, 400) + const result = await AppRuntime.runPromise( + Installation.Service.use((svc) => + Effect.gen(function* () { + const method = yield* svc.method() + if (method === "unknown") { + return { success: false as const, status: 400 as const, error: "Unknown installation method" } + } + + const target = c.req.valid("json").target || (yield* svc.latest(method)) + const result = yield* Effect.catch( + svc.upgrade(method, target).pipe(Effect.as({ success: true as const, version: target })), + (err) => + Effect.succeed({ + success: false as const, + status: 500 as const, + error: err instanceof Error ? err.message : String(err), + }), + ) + if (!result.success) return result + return { ...result, status: 200 as const } + }), + ), + ) + if (!result.success) { + return c.json({ success: false, error: result.error }, result.status) } - const target = c.req.valid("json").target || (await Installation.latest(method)) - const result = await Installation.upgrade(method, target) - .then(() => ({ success: true as const, version: target })) - .catch((e) => ({ success: false as const, error: e instanceof Error ? e.message : String(e) })) - if (result.success) { - GlobalBus.emit("event", { - directory: "global", - payload: { - type: Installation.Event.Updated.type, - properties: { version: target }, - }, - }) - return c.json(result) - } - return c.json(result, 500) + const target = result.version + GlobalBus.emit("event", { + directory: "global", + payload: { + type: Installation.Event.Updated.type, + properties: { version: target }, + }, + }) + return c.json({ success: true, version: target }) }, ), )