Skip to content

Commit a7c1266

Browse files
committed
feat(web): embed UI in binary; bypass auth for static routes
Build the app during 'bun run script/build.ts' and bundle all dist files into the binary using Bun's 'with { type: "file" }' import syntax. Falls back to the existing app.opencode.ai CDN proxy when built with --skip-embed-web-ui, or when OPENCODE_DISABLE_EMBEDDED_WEB_UI=1. Also adds --skip-app-build flag to skip the Vite build step when dist is already fresh. Auth is bypassed for all non-/v1 paths so the UI can load its own login prompt before the user has authenticated. Closes anomalyco#8549, anomalyco#6352, anomalyco#12445
1 parent 42f10f1 commit a7c1266

3 files changed

Lines changed: 42 additions & 2 deletions

File tree

‎packages/opencode/script/build.ts‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,26 @@ console.log(`Loaded ${migrations.length} migrations`)
5959
const singleFlag = process.argv.includes("--single")
6060
const baselineFlag = process.argv.includes("--baseline")
6161
const skipInstall = process.argv.includes("--skip-install")
62+
const skipEmbedWebUi = process.argv.includes("--skip-embed-web-ui")
63+
const skipAppBuild = process.argv.includes("--skip-app-build")
64+
65+
const createEmbeddedWebUIBundle = async () => {
66+
console.log("Building Web UI to embed in the binary")
67+
const appDir = path.join(import.meta.dirname, "../../app")
68+
if (!skipAppBuild) await $`bun run --cwd ${appDir} build`
69+
const allFiles = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: path.join(appDir, "dist") }))
70+
const fileMap = `
71+
// Import all files as file_$i with type: "file"
72+
${allFiles.map((filePath, i) => `import file_${i} from "${path.join(appDir, "dist", filePath)}" with { type: "file" };`).join("\n")}
73+
// Export with original mappings
74+
export default {
75+
${allFiles.map((filePath, i) => `"${filePath}": file_${i},`).join("\n")}
76+
}
77+
`.trim()
78+
return fileMap
79+
}
80+
81+
const embeddedFileMap = skipEmbedWebUi ? null : await createEmbeddedWebUIBundle()
6282

6383
const allTargets: {
6484
os: string
@@ -183,7 +203,10 @@ for (const item of targets) {
183203
execArgv: [`--user-agent=opencode/${Script.version}`, "--use-system-ca", "--"],
184204
windows: {},
185205
},
186-
entrypoints: ["./src/index.ts", parserWorker, workerPath],
206+
files: {
207+
...(embeddedFileMap ? { "opencode-web-ui.gen.ts": embeddedFileMap } : {}),
208+
},
209+
entrypoints: ["./src/index.ts", parserWorker, workerPath, ...(embeddedFileMap ? ["opencode-web-ui.gen.ts"] : [])],
187210
define: {
188211
OPENCODE_VERSION: `'${Script.version}'`,
189212
OPENCODE_MIGRATIONS: JSON.stringify(migrations),

‎packages/opencode/src/flag/flag.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export namespace Flag {
5555
export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN")
5656
export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"]
5757
export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"]
58+
export const OPENCODE_DISABLE_EMBEDDED_WEB_UI = truthy("OPENCODE_DISABLE_EMBEDDED_WEB_UI")
5859

5960
function number(key: string) {
6061
const value = process.env[key]

‎packages/opencode/src/server/server.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export namespace Server {
8181
// Allow CORS preflight requests to succeed without auth.
8282
// Browser clients sending Authorization headers will preflight with OPTIONS.
8383
if (c.req.method === "OPTIONS") return next()
84+
// Allow the embedded UI through without auth — it handles auth itself via the API password prompt
85+
if (!c.req.path.startsWith("/v1")) return next()
8486
const password = Flag.OPENCODE_SERVER_PASSWORD
8587
if (!password) return next()
8688
const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
@@ -541,8 +543,22 @@ export namespace Server {
541543
},
542544
)
543545
.all("/*", async (c) => {
544-
const path = c.req.path
546+
const embeddedWebUI = Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI
547+
? null
548+
// @ts-expect-error - generated file at build time
549+
: await import("opencode-web-ui.gen.ts").then((m) => m.default as Record<string, string>).catch(() => null)
550+
551+
if (embeddedWebUI) {
552+
const reqPath = c.req.path.replace(/^\//, "")
553+
const match = embeddedWebUI[reqPath] ?? embeddedWebUI["index.html"] ?? null
554+
if (!match) return c.json({ error: "Not Found" }, 404)
555+
const file = Bun.file(match)
556+
if (!(await file.exists())) return c.json({ error: "Not Found" }, 404)
557+
c.header("Content-Type", file.type)
558+
return c.body(await file.arrayBuffer())
559+
}
545560

561+
const path = c.req.path
546562
const response = await proxy(`https://app.opencode.ai${path}`, {
547563
...c.req,
548564
headers: {

0 commit comments

Comments
 (0)