refactor(server): extract createApp function for server initialization

- Replace Server.App() with Server.Default() for internal server access
- Extract server app creation into Server.createApp(opts) for testability
- Move CORS whitelist from module-level variable to function parameter
- Update all tests to use Server.Default() instead of Server.App()
This commit is contained in:
Dax Raad
2026-03-09 17:13:52 -04:00
parent ee18c9976e
commit 89d6f60d25
6 changed files with 493 additions and 504 deletions
+1 -1
View File
@@ -667,7 +667,7 @@ export const RunCommand = cmd({
await bootstrap(process.cwd(), async () => {
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
const request = new Request(input, init)
return Server.App().fetch(request)
return Server.Default().fetch(request)
}) as typeof globalThis.fetch
const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn })
await execute(sdk)
+2 -2
View File
@@ -54,7 +54,7 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
const request = new Request(input, init)
const auth = getAuthorizationHeader()
if (auth) request.headers.set("Authorization", auth)
return Server.App().fetch(request)
return Server.Default().fetch(request)
}) as typeof globalThis.fetch
const sdk = createOpencodeClient({
@@ -110,7 +110,7 @@ export const rpc = {
headers,
body: input.body,
})
const response = await Server.App().fetch(request)
const response = await Server.Default().fetch(request)
const body = await response.text()
return {
status: response.status,
+4 -3
View File
@@ -25,8 +25,7 @@ export namespace Plugin {
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
directory: Instance.directory,
// @ts-ignore - fetch type incompatibility
fetch: async (...args) => Server.App().fetch(...args),
fetch: async (...args) => Server.Default().fetch(...args),
})
const config = await Config.get()
const hooks: Hooks[] = []
@@ -35,7 +34,9 @@ export namespace Plugin {
project: Instance.project,
worktree: Instance.worktree,
directory: Instance.directory,
serverUrl: Server.url(),
get serverUrl(): URL {
throw new Error("Server URL is no longer supported in plugins")
},
$: Bun.$,
}
+12 -24
View File
@@ -31,7 +31,6 @@ import { FileRoutes } from "./routes/file"
import { ConfigRoutes } from "./routes/config"
import { ExperimentalRoutes } from "./routes/experimental"
import { ProviderRoutes } from "./routes/provider"
import { lazy } from "../util/lazy"
import { InstanceBootstrap } from "../project/bootstrap"
import { NotFoundError } from "../storage/db"
import type { ContentfulStatusCode } from "hono/utils/http-status"
@@ -43,6 +42,7 @@ import { QuestionRoutes } from "./routes/question"
import { PermissionRoutes } from "./routes/permission"
import { GlobalRoutes } from "./routes/global"
import { MDNS } from "./mdns"
import { lazy } from "@/util/lazy"
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
globalThis.AI_SDK_LOG_WARNINGS = false
@@ -50,18 +50,11 @@ globalThis.AI_SDK_LOG_WARNINGS = false
export namespace Server {
const log = Log.create({ service: "server" })
let _url: URL | undefined
let _corsWhitelist: string[] = []
export function url(): URL {
return _url ?? new URL("http://localhost:4096")
}
export const Default = lazy(() => createApp({}))
export const createApp = (opts: { cors?: string[] }): Hono => {
const app = new Hono()
export const App: () => Hono = lazy(
() =>
// TODO: Break server.ts into smaller route files to fix type inference
app
return app
.onError((err, c) => {
log.error("failed", {
error: err,
@@ -124,7 +117,7 @@ export namespace Server {
if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) {
return input
}
if (_corsWhitelist.includes(input)) {
if (opts?.cors?.includes(input)) {
return input
}
@@ -282,8 +275,7 @@ export namespace Server {
"/path",
describeRoute({
summary: "Get paths",
description:
"Retrieve the current working directory and related path information for the OpenCode instance.",
description: "Retrieve the current working directory and related path information for the OpenCode instance.",
operationId: "path.get",
responses: {
200: {
@@ -322,8 +314,7 @@ export namespace Server {
"/vcs",
describeRoute({
summary: "Get VCS info",
description:
"Retrieve version control system (VCS) information for the current project, such as git branch.",
description: "Retrieve version control system (VCS) information for the current project, such as git branch.",
operationId: "vcs.get",
responses: {
200: {
@@ -576,12 +567,12 @@ export namespace Server {
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:",
)
return response
}) as unknown as Hono,
)
})
}
export async function openapi() {
// Cast to break excessive type recursion from long route chains
const result = await generateSpecs(App() as Hono, {
const result = await generateSpecs(Default(), {
documentation: {
info: {
title: "opencode",
@@ -601,12 +592,11 @@ export namespace Server {
mdnsDomain?: string
cors?: string[]
}) {
_corsWhitelist = opts.cors ?? []
const app = createApp(opts)
const args = {
hostname: opts.hostname,
idleTimeout: 0,
fetch: App().fetch,
fetch: app.fetch,
websocket: websocket,
} as const
const tryServe = (port: number) => {
@@ -619,8 +609,6 @@ export namespace Server {
const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port)
if (!server) throw new Error(`Failed to start server on port ${opts.port}`)
_url = server.url
const shouldPublishMDNS =
opts.mdns &&
server.port &&
@@ -19,7 +19,7 @@ afterEach(async () => {
describe("project.initGit endpoint", () => {
test("initializes git and reloads immediately", async () => {
await using tmp = await tmpdir()
const app = Server.App()
const app = Server.Default()
const seen: { directory?: string; payload: { type: string } }[] = []
const fn = (evt: { directory?: string; payload: { type: string } }) => {
seen.push(evt)
@@ -75,7 +75,7 @@ describe("project.initGit endpoint", () => {
test("does not reload when the project is already git", async () => {
await using tmp = await tmpdir({ git: true })
const app = Server.App()
const app = Server.Default()
const seen: { directory?: string; payload: { type: string } }[] = []
const fn = (evt: { directory?: string; payload: { type: string } }) => {
seen.push(evt)
@@ -17,7 +17,7 @@ describe("tui.selectSession endpoint", () => {
const session = await Session.create({})
// #when
const app = Server.App()
const app = Server.Default()
const response = await app.request("/tui/select-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -42,7 +42,7 @@ describe("tui.selectSession endpoint", () => {
const nonExistentSessionID = "ses_nonexistent123"
// #when
const app = Server.App()
const app = Server.Default()
const response = await app.request("/tui/select-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -63,7 +63,7 @@ describe("tui.selectSession endpoint", () => {
const invalidSessionID = "invalid_session_id"
// #when
const app = Server.App()
const app = Server.Default()
const response = await app.request("/tui/select-session", {
method: "POST",
headers: { "Content-Type": "application/json" },