mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-29 23:22:22 +00:00
fix(opencode): normalize upgrade endpoint (#44686)
This commit is contained in:
parent
105b398c2a
commit
2a36236132
5 changed files with 51 additions and 54 deletions
|
|
@ -5,7 +5,8 @@ import { InstanceDisposed } from "@/server/event"
|
|||
import "@opencode-ai/core/account"
|
||||
import "@/server/event"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import semver from "semver"
|
||||
import { described } from "./metadata"
|
||||
|
||||
const GlobalHealth = Schema.Struct({
|
||||
|
|
@ -48,7 +49,9 @@ const GlobalEventSchema = Schema.Struct({
|
|||
}).annotate({ identifier: "GlobalEvent" })
|
||||
|
||||
export const GlobalUpgradeInput = Schema.Struct({
|
||||
target: Schema.optional(Schema.String),
|
||||
target: Schema.String.check(
|
||||
Schema.makeFilter((value) => (semver.valid(value) === null ? "Expected a semantic version" : undefined)),
|
||||
),
|
||||
})
|
||||
|
||||
const GlobalUpgradeResult = Schema.Union([
|
||||
|
|
@ -121,14 +124,14 @@ export const GlobalApi = HttpApi.make("global").add(
|
|||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("upgrade", GlobalPaths.upgrade, {
|
||||
payload: [HttpApiSchema.NoContent, GlobalUpgradeInput],
|
||||
payload: GlobalUpgradeInput,
|
||||
success: described(GlobalUpgradeResult, "Upgrade result"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "global.upgrade",
|
||||
summary: "Upgrade opencode",
|
||||
description: "Upgrade opencode to the specified version or latest if not specified.",
|
||||
description: "Upgrade opencode to the specified version.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import { EventV2 } from "@opencode-ai/core/event"
|
|||
import { Installation } from "@/installation"
|
||||
import { disposeAllInstancesAndEmitGlobalDisposed } from "@/server/global-lifecycle"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { Effect, Queue, Schema } from "effect"
|
||||
import { Effect, Queue } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import * as Sse from "effect/unstable/encoding/Sse"
|
||||
import { RootHttpApi } from "../api"
|
||||
|
|
@ -22,14 +22,6 @@ function eventData(data: unknown): Sse.Event {
|
|||
}
|
||||
}
|
||||
|
||||
function parseBody(body: string) {
|
||||
try {
|
||||
return JSON.parse(body || "{}") as unknown
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function eventResponse() {
|
||||
return Effect.gen(function* () {
|
||||
yield* Effect.logInfo("global event connected")
|
||||
|
|
@ -97,25 +89,22 @@ export const globalHandlers = HttpApiBuilder.group(RootHttpApi, "global", (handl
|
|||
const upgrade = Effect.fn("GlobalHttpApi.upgrade")(function* (ctx: { payload: typeof GlobalUpgradeInput.Type }) {
|
||||
const method = yield* installation.method()
|
||||
if (method === "unknown") {
|
||||
return {
|
||||
status: 400,
|
||||
body: { success: false as const, error: "Unknown installation method" },
|
||||
}
|
||||
return HttpServerResponse.jsonUnsafe(
|
||||
{ success: false as const, error: "Unknown installation method" },
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
const target = ctx.payload.target || (yield* installation.latest(method))
|
||||
const target = ctx.payload.target
|
||||
const result = yield* installation.upgrade(method, target).pipe(
|
||||
Effect.as({ status: 200, body: { success: true as const, version: target } }),
|
||||
Effect.as({ success: true as const, version: target }),
|
||||
Effect.catch((err) =>
|
||||
Effect.succeed({
|
||||
status: 500,
|
||||
body: {
|
||||
success: false as const,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
},
|
||||
success: false as const,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
}),
|
||||
),
|
||||
)
|
||||
if (!result.body.success) return result
|
||||
if (!result.success) return HttpServerResponse.jsonUnsafe(result, { status: 500 })
|
||||
GlobalBus.emit("event", {
|
||||
directory: "global",
|
||||
payload: {
|
||||
|
|
@ -123,26 +112,7 @@ export const globalHandlers = HttpApiBuilder.group(RootHttpApi, "global", (handl
|
|||
properties: { version: target },
|
||||
},
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const upgradeRaw = Effect.fn("GlobalHttpApi.upgradeRaw")(function* (ctx: {
|
||||
request: HttpServerRequest.HttpServerRequest
|
||||
}) {
|
||||
const body = yield* Effect.orDie(ctx.request.text)
|
||||
const json = parseBody(body)
|
||||
if (json === undefined) {
|
||||
return HttpServerResponse.jsonUnsafe({ success: false, error: "Invalid request body" }, { status: 400 })
|
||||
}
|
||||
const payload = yield* Schema.decodeUnknownEffect(GlobalUpgradeInput)(json).pipe(
|
||||
Effect.map((payload) => ({ valid: true as const, payload })),
|
||||
Effect.catch(() => Effect.succeed({ valid: false as const })),
|
||||
)
|
||||
if (!payload.valid) {
|
||||
return HttpServerResponse.jsonUnsafe({ success: false, error: "Invalid request body" }, { status: 400 })
|
||||
}
|
||||
const result = yield* upgrade({ payload: payload.payload })
|
||||
return HttpServerResponse.jsonUnsafe(result.body, { status: result.status })
|
||||
return HttpServerResponse.jsonUnsafe(result)
|
||||
})
|
||||
|
||||
return handlers
|
||||
|
|
@ -151,6 +121,6 @@ export const globalHandlers = HttpApiBuilder.group(RootHttpApi, "global", (handl
|
|||
.handle("configGet", configGet)
|
||||
.handle("configUpdate", configUpdate)
|
||||
.handle("dispose", dispose)
|
||||
.handleRaw("upgrade", upgradeRaw)
|
||||
.handle("upgrade", upgrade)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -43,24 +43,48 @@ const apiLayer = HttpRouter.serve(
|
|||
const it = testEffect(apiLayer)
|
||||
|
||||
describe("global HttpApi", () => {
|
||||
it.live("upgrades to latest when the request body is omitted", () =>
|
||||
it.live("upgrades to the requested version", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* HttpClient.post(GlobalPaths.upgrade)
|
||||
const response = yield* HttpClientRequest.post(GlobalPaths.upgrade).pipe(
|
||||
HttpClientRequest.bodyJsonUnsafe({ target: "9.9.9" }),
|
||||
HttpClient.execute,
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(yield* response.json).toEqual({ success: true, version: "9.9.9" })
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("rejects malformed upgrade payloads", () =>
|
||||
it.live("rejects invalid upgrade payloads", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* HttpClientRequest.post(GlobalPaths.upgrade).pipe(
|
||||
HttpClientRequest.setBody(HttpBody.text("{", "application/json")),
|
||||
HttpClientRequest.bodyJsonUnsafe({ target: 1 }),
|
||||
HttpClient.execute,
|
||||
)
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(yield* response.json).toEqual({ success: false, error: "Invalid request body" })
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("rejects invalid upgrade target versions", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* HttpClientRequest.post(GlobalPaths.upgrade).pipe(
|
||||
HttpClientRequest.bodyJsonUnsafe({ target: "latest" }),
|
||||
HttpClient.execute,
|
||||
)
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("rejects unsupported upgrade content types", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* HttpClientRequest.post(GlobalPaths.upgrade).pipe(
|
||||
HttpClientRequest.setBody(HttpBody.text('{"target":"1.0.0"}', "text/plain")),
|
||||
HttpClient.execute,
|
||||
)
|
||||
|
||||
expect(response.status).toBe(415)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1355,7 +1355,7 @@ export class Global extends HeyApiClient {
|
|||
/**
|
||||
* Upgrade opencode
|
||||
*
|
||||
* Upgrade opencode to the specified version or latest if not specified.
|
||||
* Upgrade opencode to the specified version.
|
||||
*/
|
||||
public upgrade<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
|
|
|
|||
|
|
@ -7353,7 +7353,7 @@ export type GlobalDisposeResponse = GlobalDisposeResponses[keyof GlobalDisposeRe
|
|||
|
||||
export type GlobalUpgradeData = {
|
||||
body?: {
|
||||
target?: string
|
||||
target: string
|
||||
}
|
||||
path?: never
|
||||
query?: never
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue