refactor(flags): move embedded web ui flag to runtime flags (#27613)
This commit is contained in:
@@ -62,7 +62,6 @@ export const Flag = {
|
|||||||
OPENCODE_ENABLE_PARALLEL: truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL"),
|
OPENCODE_ENABLE_PARALLEL: truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL"),
|
||||||
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
|
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
|
||||||
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
|
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
|
||||||
OPENCODE_DISABLE_EMBEDDED_WEB_UI: truthy("OPENCODE_DISABLE_EMBEDDED_WEB_UI"),
|
|
||||||
OPENCODE_DB: process.env["OPENCODE_DB"],
|
OPENCODE_DB: process.env["OPENCODE_DB"],
|
||||||
OPENCODE_DISABLE_CHANNEL_DB: truthy("OPENCODE_DISABLE_CHANNEL_DB"),
|
OPENCODE_DISABLE_CHANNEL_DB: truthy("OPENCODE_DISABLE_CHANNEL_DB"),
|
||||||
OPENCODE_SKIP_MIGRATIONS: truthy("OPENCODE_SKIP_MIGRATIONS"),
|
OPENCODE_SKIP_MIGRATIONS: truthy("OPENCODE_SKIP_MIGRATIONS"),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const enabledByExperimental = (name: string) =>
|
|||||||
export class Service extends ConfigService.Service<Service>()("@opencode/RuntimeFlags", {
|
export class Service extends ConfigService.Service<Service>()("@opencode/RuntimeFlags", {
|
||||||
pure: bool("OPENCODE_PURE"),
|
pure: bool("OPENCODE_PURE"),
|
||||||
disableDefaultPlugins: bool("OPENCODE_DISABLE_DEFAULT_PLUGINS"),
|
disableDefaultPlugins: bool("OPENCODE_DISABLE_DEFAULT_PLUGINS"),
|
||||||
|
disableEmbeddedWebUi: bool("OPENCODE_DISABLE_EMBEDDED_WEB_UI"),
|
||||||
disableClaudeCodeSkills: Config.all({
|
disableClaudeCodeSkills: Config.all({
|
||||||
broad: bool("OPENCODE_DISABLE_CLAUDE_CODE"),
|
broad: bool("OPENCODE_DISABLE_CLAUDE_CODE"),
|
||||||
direct: bool("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS"),
|
direct: bool("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS"),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { File } from "@/file"
|
|||||||
import { FileWatcher } from "@/file/watcher"
|
import { FileWatcher } from "@/file/watcher"
|
||||||
import { Ripgrep } from "@/file/ripgrep"
|
import { Ripgrep } from "@/file/ripgrep"
|
||||||
import { Format } from "@/format"
|
import { Format } from "@/format"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
import { LSP } from "@/lsp/lsp"
|
import { LSP } from "@/lsp/lsp"
|
||||||
import { MCP } from "@/mcp"
|
import { MCP } from "@/mcp"
|
||||||
import { Permission } from "@/permission"
|
import { Permission } from "@/permission"
|
||||||
@@ -172,7 +173,10 @@ const uiRoute = HttpRouter.use((router) =>
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
const client = yield* HttpClient.HttpClient
|
const client = yield* HttpClient.HttpClient
|
||||||
yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
yield* router.add("*", "/*", (request) =>
|
||||||
|
serveUIEffect(request, { fs, client, disableEmbeddedWebUi: flags.disableEmbeddedWebUi }),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
).pipe(Layer.provide(authOnlyRouterLayer))
|
).pipe(Layer.provide(authOnlyRouterLayer))
|
||||||
|
|
||||||
@@ -206,6 +210,7 @@ export function createRoutes(corsOptions?: CorsOptions) {
|
|||||||
PtyTicket.defaultLayer,
|
PtyTicket.defaultLayer,
|
||||||
Question.defaultLayer,
|
Question.defaultLayer,
|
||||||
Ripgrep.defaultLayer,
|
Ripgrep.defaultLayer,
|
||||||
|
RuntimeFlags.defaultLayer,
|
||||||
Session.defaultLayer,
|
Session.defaultLayer,
|
||||||
SessionCompaction.defaultLayer,
|
SessionCompaction.defaultLayer,
|
||||||
SessionPrompt.defaultLayer,
|
SessionPrompt.defaultLayer,
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
import { Effect, Stream } from "effect"
|
import { Effect, Stream } from "effect"
|
||||||
import { HttpBody, HttpClient, HttpClientRequest, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
import { HttpBody, HttpClient, HttpClientRequest, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||||
import { createHash } from "node:crypto"
|
import { createHash } from "node:crypto"
|
||||||
import { ProxyUtil } from "../proxy-util"
|
import { ProxyUtil } from "../proxy-util"
|
||||||
|
|
||||||
const embeddedUIPromise = Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI
|
let embeddedUIPromise: Promise<Record<string, string> | null> | undefined
|
||||||
? Promise.resolve(null)
|
|
||||||
: // @ts-expect-error - generated file at build time
|
|
||||||
import("opencode-web-ui.gen.ts").then((module) => module.default as Record<string, string>).catch(() => null)
|
|
||||||
|
|
||||||
export const UI_UPSTREAM = new URL("https://app.opencode.ai")
|
export const UI_UPSTREAM = new URL("https://app.opencode.ai")
|
||||||
|
|
||||||
@@ -45,9 +41,11 @@ export function upstreamURL(path: string) {
|
|||||||
return new URL(path, UI_UPSTREAM).toString()
|
return new URL(path, UI_UPSTREAM).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function embeddedUI() {
|
export function embeddedUI(disableEmbeddedWebUi: boolean) {
|
||||||
if (Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI) return Promise.resolve(null)
|
if (disableEmbeddedWebUi) return Promise.resolve(null)
|
||||||
return embeddedUIPromise
|
return (embeddedUIPromise ??=
|
||||||
|
// @ts-expect-error - generated file at build time
|
||||||
|
import("opencode-web-ui.gen.ts").then((module) => module.default as Record<string, string>).catch(() => null))
|
||||||
}
|
}
|
||||||
|
|
||||||
function notFound() {
|
function notFound() {
|
||||||
@@ -79,10 +77,10 @@ export function serveEmbeddedUIEffect(
|
|||||||
|
|
||||||
export function serveUIEffect(
|
export function serveUIEffect(
|
||||||
request: HttpServerRequest.HttpServerRequest,
|
request: HttpServerRequest.HttpServerRequest,
|
||||||
services: { fs: AppFileSystem.Interface; client: HttpClient.HttpClient },
|
services: { fs: AppFileSystem.Interface; client: HttpClient.HttpClient; disableEmbeddedWebUi: boolean },
|
||||||
) {
|
) {
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
const embeddedWebUI = yield* Effect.promise(() => embeddedUI())
|
const embeddedWebUI = yield* Effect.promise(() => embeddedUI(services.disableEmbeddedWebUi))
|
||||||
const path = new URL(request.url, "http://localhost").pathname
|
const path = new URL(request.url, "http://localhost").pathname
|
||||||
|
|
||||||
if (embeddedWebUI) return yield* serveEmbeddedUIEffect(path, services.fs, embeddedWebUI)
|
if (embeddedWebUI) return yield* serveEmbeddedUIEffect(path, services.fs, embeddedWebUI)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ describe("RuntimeFlags", () => {
|
|||||||
fromConfig({
|
fromConfig({
|
||||||
OPENCODE_PURE: "true",
|
OPENCODE_PURE: "true",
|
||||||
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
|
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
|
||||||
|
OPENCODE_DISABLE_EMBEDDED_WEB_UI: "true",
|
||||||
OPENCODE_EXPERIMENTAL: "true",
|
OPENCODE_EXPERIMENTAL: "true",
|
||||||
OPENCODE_ENABLE_EXA: "true",
|
OPENCODE_ENABLE_EXA: "true",
|
||||||
OPENCODE_ENABLE_PARALLEL: "true",
|
OPENCODE_ENABLE_PARALLEL: "true",
|
||||||
@@ -28,6 +29,7 @@ describe("RuntimeFlags", () => {
|
|||||||
|
|
||||||
expect(flags.pure).toBe(true)
|
expect(flags.pure).toBe(true)
|
||||||
expect(flags.disableDefaultPlugins).toBe(true)
|
expect(flags.disableDefaultPlugins).toBe(true)
|
||||||
|
expect(flags.disableEmbeddedWebUi).toBe(true)
|
||||||
expect(flags.enableExa).toBe(true)
|
expect(flags.enableExa).toBe(true)
|
||||||
expect(flags.enableParallel).toBe(true)
|
expect(flags.enableParallel).toBe(true)
|
||||||
expect(flags.enableExperimentalModels).toBe(true)
|
expect(flags.enableExperimentalModels).toBe(true)
|
||||||
@@ -67,6 +69,7 @@ describe("RuntimeFlags", () => {
|
|||||||
|
|
||||||
expect(flags.pure).toBe(false)
|
expect(flags.pure).toBe(false)
|
||||||
expect(flags.disableDefaultPlugins).toBe(true)
|
expect(flags.disableDefaultPlugins).toBe(true)
|
||||||
|
expect(flags.disableEmbeddedWebUi).toBe(false)
|
||||||
expect(flags.disableClaudeCodeSkills).toBe(false)
|
expect(flags.disableClaudeCodeSkills).toBe(false)
|
||||||
expect(flags.enableExa).toBe(false)
|
expect(flags.enableExa).toBe(false)
|
||||||
expect(flags.experimentalIconDiscovery).toBe(false)
|
expect(flags.experimentalIconDiscovery).toBe(false)
|
||||||
@@ -186,6 +189,7 @@ describe("RuntimeFlags", () => {
|
|||||||
|
|
||||||
expect(flags.pure).toBe(false)
|
expect(flags.pure).toBe(false)
|
||||||
expect(flags.disableDefaultPlugins).toBe(false)
|
expect(flags.disableDefaultPlugins).toBe(false)
|
||||||
|
expect(flags.disableEmbeddedWebUi).toBe(false)
|
||||||
expect(flags.disableClaudeCodeSkills).toBe(false)
|
expect(flags.disableClaudeCodeSkills).toBe(false)
|
||||||
expect(flags.enableExa).toBe(false)
|
expect(flags.enableExa).toBe(false)
|
||||||
expect(flags.experimentalIconDiscovery).toBe(false)
|
expect(flags.experimentalIconDiscovery).toBe(false)
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import {
|
|||||||
HttpServerResponse,
|
HttpServerResponse,
|
||||||
} from "effect/unstable/http"
|
} from "effect/unstable/http"
|
||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
|
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||||
import { ServerAuth } from "../../src/server/auth"
|
import { ServerAuth } from "../../src/server/auth"
|
||||||
import { authorizationRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/authorization"
|
import { authorizationRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/authorization"
|
||||||
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
||||||
import { serveEmbeddedUIEffect, serveUIEffect } from "../../src/server/shared/ui"
|
import { serveEmbeddedUIEffect, serveUIEffect } from "../../src/server/shared/ui"
|
||||||
import { Server } from "../../src/server/server"
|
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
|
|
||||||
void Log.init({ print: false })
|
void Log.init({ print: false })
|
||||||
@@ -25,7 +25,6 @@ void Log.init({ print: false })
|
|||||||
const testStateLayer = Layer.effectDiscard(
|
const testStateLayer = Layer.effectDiscard(
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const original = {
|
const original = {
|
||||||
OPENCODE_DISABLE_EMBEDDED_WEB_UI: Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI,
|
|
||||||
OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD,
|
OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD,
|
||||||
OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
|
OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
|
||||||
envPassword: process.env.OPENCODE_SERVER_PASSWORD,
|
envPassword: process.env.OPENCODE_SERVER_PASSWORD,
|
||||||
@@ -34,7 +33,6 @@ const testStateLayer = Layer.effectDiscard(
|
|||||||
|
|
||||||
yield* Effect.addFinalizer(() =>
|
yield* Effect.addFinalizer(() =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = original.OPENCODE_DISABLE_EMBEDDED_WEB_UI
|
|
||||||
Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD
|
Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD
|
||||||
Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME
|
Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME
|
||||||
restoreEnv("OPENCODE_SERVER_PASSWORD", original.envPassword)
|
restoreEnv("OPENCODE_SERVER_PASSWORD", original.envPassword)
|
||||||
@@ -44,7 +42,7 @@ const testStateLayer = Layer.effectDiscard(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const it = testEffect(Layer.mergeAll(testStateLayer, AppFileSystem.defaultLayer))
|
const it = testEffect(Layer.mergeAll(testStateLayer, AppFileSystem.defaultLayer, RuntimeFlags.layer()))
|
||||||
|
|
||||||
function restoreEnv(key: string, value: string | undefined) {
|
function restoreEnv(key: string, value: string | undefined) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
@@ -82,19 +80,28 @@ function app(input?: { password?: string; username?: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function uiApp(input?: { password?: string; username?: string; client?: Layer.Layer<HttpClient.HttpClient> }) {
|
function uiApp(input?: {
|
||||||
|
password?: string
|
||||||
|
username?: string
|
||||||
|
client?: Layer.Layer<HttpClient.HttpClient>
|
||||||
|
disableEmbeddedWebUi?: boolean
|
||||||
|
}) {
|
||||||
const handler = HttpRouter.toWebHandler(
|
const handler = HttpRouter.toWebHandler(
|
||||||
HttpRouter.use((router) =>
|
HttpRouter.use((router) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
const client = yield* HttpClient.HttpClient
|
const client = yield* HttpClient.HttpClient
|
||||||
yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
yield* router.add("*", "/*", (request) =>
|
||||||
|
serveUIEffect(request, { fs, client, disableEmbeddedWebUi: flags.disableEmbeddedWebUi }),
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
).pipe(
|
).pipe(
|
||||||
Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))),
|
Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))),
|
||||||
Layer.provide([
|
Layer.provide([
|
||||||
AppFileSystem.defaultLayer,
|
AppFileSystem.defaultLayer,
|
||||||
input?.client ?? httpClient(new Response("ui")),
|
input?.client ?? httpClient(new Response("ui")),
|
||||||
|
RuntimeFlags.layer({ disableEmbeddedWebUi: input?.disableEmbeddedWebUi ?? false }),
|
||||||
HttpServer.layerServices,
|
HttpServer.layerServices,
|
||||||
ConfigProvider.layer(
|
ConfigProvider.layer(
|
||||||
ConfigProvider.fromUnknown({
|
ConfigProvider.fromUnknown({
|
||||||
@@ -120,6 +127,48 @@ function uiApp(input?: { password?: string; username?: string; client?: Layer.La
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function routeOrderingApp() {
|
||||||
|
let proxiedUrl: string | undefined
|
||||||
|
const handler = HttpRouter.toWebHandler(
|
||||||
|
HttpRouter.use((router) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const fs = yield* AppFileSystem.Service
|
||||||
|
const client = yield* HttpClient.HttpClient
|
||||||
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
yield* router.add("GET", "/session/:sessionID", () =>
|
||||||
|
Effect.succeed(HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 })),
|
||||||
|
)
|
||||||
|
yield* router.add("*", "/*", (request) =>
|
||||||
|
serveUIEffect(request, { fs, client, disableEmbeddedWebUi: flags.disableEmbeddedWebUi }),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
).pipe(
|
||||||
|
Layer.provide([
|
||||||
|
AppFileSystem.defaultLayer,
|
||||||
|
RuntimeFlags.layer({ disableEmbeddedWebUi: true }),
|
||||||
|
httpClient(new Response("ui"), (request) => {
|
||||||
|
proxiedUrl = request.url
|
||||||
|
}),
|
||||||
|
HttpServer.layerServices,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
{ disableLogger: true },
|
||||||
|
).handler
|
||||||
|
return {
|
||||||
|
proxiedUrl: () => proxiedUrl,
|
||||||
|
request(input: string | URL | Request, init?: RequestInit) {
|
||||||
|
return Effect.promise(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
handler(
|
||||||
|
input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init),
|
||||||
|
ExperimentalHttpApiServer.context,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function httpClient(response: Response, onRequest?: (request: HttpClientRequest.HttpClientRequest) => void) {
|
function httpClient(response: Response, onRequest?: (request: HttpClientRequest.HttpClientRequest) => void) {
|
||||||
return Layer.succeed(
|
return Layer.succeed(
|
||||||
HttpClient.HttpClient,
|
HttpClient.HttpClient,
|
||||||
@@ -137,10 +186,10 @@ function responseText(response: Response) {
|
|||||||
describe("HttpApi UI fallback", () => {
|
describe("HttpApi UI fallback", () => {
|
||||||
it.live("serves the web UI through the experimental backend", () =>
|
it.live("serves the web UI through the experimental backend", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
|
|
||||||
let proxiedUrl: string | undefined
|
let proxiedUrl: string | undefined
|
||||||
|
|
||||||
const response = yield* uiApp({
|
const response = yield* uiApp({
|
||||||
|
disableEmbeddedWebUi: true,
|
||||||
client: httpClient(
|
client: httpClient(
|
||||||
new Response("<html>opencode</html>", { headers: { "content-type": "text/html" } }),
|
new Response("<html>opencode</html>", { headers: { "content-type": "text/html" } }),
|
||||||
(request) => {
|
(request) => {
|
||||||
@@ -158,35 +207,39 @@ describe("HttpApi UI fallback", () => {
|
|||||||
|
|
||||||
it.live("strips upstream transfer encoding headers from proxied assets", () =>
|
it.live("strips upstream transfer encoding headers from proxied assets", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
|
|
||||||
let proxiedUrl: string | undefined
|
let proxiedUrl: string | undefined
|
||||||
|
|
||||||
const response = yield* Effect.gen(function* () {
|
const response = yield* Effect.gen(function* () {
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
const client = yield* HttpClient.HttpClient
|
const client = yield* HttpClient.HttpClient
|
||||||
|
const flags = yield* RuntimeFlags.Service
|
||||||
return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/assets/app.js")), {
|
return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/assets/app.js")), {
|
||||||
fs,
|
fs,
|
||||||
client,
|
client,
|
||||||
|
disableEmbeddedWebUi: flags.disableEmbeddedWebUi,
|
||||||
})
|
})
|
||||||
}).pipe(
|
}).pipe(
|
||||||
Effect.provide(
|
Effect.provide(
|
||||||
Layer.succeed(
|
Layer.mergeAll(
|
||||||
HttpClient.HttpClient,
|
RuntimeFlags.layer({ disableEmbeddedWebUi: true }),
|
||||||
HttpClient.make((request) => {
|
Layer.succeed(
|
||||||
proxiedUrl = request.url
|
HttpClient.HttpClient,
|
||||||
return Effect.succeed(
|
HttpClient.make((request) => {
|
||||||
HttpClientResponse.fromWeb(
|
proxiedUrl = request.url
|
||||||
request,
|
return Effect.succeed(
|
||||||
new Response("console.log('ok')", {
|
HttpClientResponse.fromWeb(
|
||||||
headers: {
|
request,
|
||||||
"content-encoding": "br",
|
new Response("console.log('ok')", {
|
||||||
"content-length": "999",
|
headers: {
|
||||||
"content-type": "text/javascript",
|
"content-encoding": "br",
|
||||||
},
|
"content-length": "999",
|
||||||
}),
|
"content-type": "text/javascript",
|
||||||
),
|
},
|
||||||
)
|
}),
|
||||||
}),
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Effect.map(HttpServerResponse.toWeb),
|
Effect.map(HttpServerResponse.toWeb),
|
||||||
@@ -206,29 +259,32 @@ describe("HttpApi UI fallback", () => {
|
|||||||
// causing browsers to fail with `ERR_INVALID_CHUNKED_ENCODING`.
|
// causing browsers to fail with `ERR_INVALID_CHUNKED_ENCODING`.
|
||||||
it.live("strips upstream transfer-encoding header from proxied assets", () =>
|
it.live("strips upstream transfer-encoding header from proxied assets", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
|
|
||||||
|
|
||||||
const response = yield* Effect.gen(function* () {
|
const response = yield* Effect.gen(function* () {
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
const client = yield* HttpClient.HttpClient
|
const client = yield* HttpClient.HttpClient
|
||||||
|
const flags = yield* RuntimeFlags.Service
|
||||||
return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/")), {
|
return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/")), {
|
||||||
fs,
|
fs,
|
||||||
client,
|
client,
|
||||||
|
disableEmbeddedWebUi: flags.disableEmbeddedWebUi,
|
||||||
})
|
})
|
||||||
}).pipe(
|
}).pipe(
|
||||||
Effect.provide(
|
Effect.provide(
|
||||||
Layer.succeed(
|
Layer.mergeAll(
|
||||||
HttpClient.HttpClient,
|
RuntimeFlags.layer({ disableEmbeddedWebUi: true }),
|
||||||
HttpClient.make((request) =>
|
Layer.succeed(
|
||||||
Effect.succeed(
|
HttpClient.HttpClient,
|
||||||
HttpClientResponse.fromWeb(
|
HttpClient.make((request) =>
|
||||||
request,
|
Effect.succeed(
|
||||||
new Response("<html>opencode</html>", {
|
HttpClientResponse.fromWeb(
|
||||||
headers: {
|
request,
|
||||||
"transfer-encoding": "chunked",
|
new Response("<html>opencode</html>", {
|
||||||
"content-type": "text/html",
|
headers: {
|
||||||
},
|
"transfer-encoding": "chunked",
|
||||||
}),
|
"content-type": "text/html",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -301,17 +357,21 @@ describe("HttpApi UI fallback", () => {
|
|||||||
|
|
||||||
it.live("keeps matched API routes ahead of the UI fallback", () =>
|
it.live("keeps matched API routes ahead of the UI fallback", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const response = yield* Effect.promise(() => Promise.resolve(Server.Default().app.request("/session/ses_nope")))
|
const server = routeOrderingApp()
|
||||||
|
const response = yield* server.request("/session/ses_nope")
|
||||||
|
|
||||||
expect(response.status).toBe(404)
|
expect(response.status).toBe(404)
|
||||||
|
expect(server.proxiedUrl()).toBeUndefined()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("requires server password for the web UI", () =>
|
it.live("requires server password for the web UI", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
|
const response = yield* uiApp({
|
||||||
|
password: "secret",
|
||||||
const response = yield* uiApp({ password: "secret", username: "opencode" }).request("/")
|
username: "opencode",
|
||||||
|
disableEmbeddedWebUi: true,
|
||||||
|
}).request("/")
|
||||||
|
|
||||||
expect(response.status).toBe(401)
|
expect(response.status).toBe(401)
|
||||||
expect(response.headers.get("www-authenticate")).toBe('Basic realm="Secure Area"')
|
expect(response.headers.get("www-authenticate")).toBe('Basic realm="Secure Area"')
|
||||||
@@ -320,11 +380,10 @@ describe("HttpApi UI fallback", () => {
|
|||||||
|
|
||||||
it.live("accepts auth token for the web UI", () =>
|
it.live("accepts auth token for the web UI", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
|
|
||||||
|
|
||||||
const response = yield* uiApp({
|
const response = yield* uiApp({
|
||||||
password: "secret",
|
password: "secret",
|
||||||
username: "opencode",
|
username: "opencode",
|
||||||
|
disableEmbeddedWebUi: true,
|
||||||
client: httpClient(new Response("<html>opencode</html>", { headers: { "content-type": "text/html" } })),
|
client: httpClient(new Response("<html>opencode</html>", { headers: { "content-type": "text/html" } })),
|
||||||
}).request(`/?auth_token=${btoa("opencode:secret")}`)
|
}).request(`/?auth_token=${btoa("opencode:secret")}`)
|
||||||
|
|
||||||
@@ -335,9 +394,11 @@ describe("HttpApi UI fallback", () => {
|
|||||||
|
|
||||||
it.live("accepts basic auth for the web UI", () =>
|
it.live("accepts basic auth for the web UI", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
|
const response = yield* uiApp({
|
||||||
|
password: "secret",
|
||||||
const response = yield* uiApp({ password: "secret", username: "opencode" }).request("/", {
|
username: "opencode",
|
||||||
|
disableEmbeddedWebUi: true,
|
||||||
|
}).request("/", {
|
||||||
headers: { authorization: `Basic ${btoa("opencode:secret")}` },
|
headers: { authorization: `Basic ${btoa("opencode:secret")}` },
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -352,12 +413,11 @@ describe("HttpApi UI fallback", () => {
|
|||||||
// should bypass auth.
|
// should bypass auth.
|
||||||
it.live("serves the PWA manifest without auth even when a server password is set", () =>
|
it.live("serves the PWA manifest without auth even when a server password is set", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI = true
|
|
||||||
|
|
||||||
for (const path of ["/site.webmanifest", "/web-app-manifest-192x192.png", "/web-app-manifest-512x512.png"]) {
|
for (const path of ["/site.webmanifest", "/web-app-manifest-192x192.png", "/web-app-manifest-512x512.png"]) {
|
||||||
const response = yield* uiApp({
|
const response = yield* uiApp({
|
||||||
password: "secret",
|
password: "secret",
|
||||||
username: "opencode",
|
username: "opencode",
|
||||||
|
disableEmbeddedWebUi: true,
|
||||||
client: httpClient(new Response("ok")),
|
client: httpClient(new Response("ok")),
|
||||||
}).request(path)
|
}).request(path)
|
||||||
expect(response.status).not.toBe(401)
|
expect(response.status).not.toBe(401)
|
||||||
|
|||||||
Reference in New Issue
Block a user