refactor(server): inject config options

This commit is contained in:
Dax Raad 2026-07-20 21:05:13 -04:00
commit 9445b4d940
17 changed files with 133 additions and 53 deletions

View file

@ -70,6 +70,7 @@ function testLayer(
watcher?: Layer.Layer<Watcher.Service>,
credentialNode = emptyCredentialNode,
wellknownNode = emptyWellknownNode,
options?: Config.Options,
) {
const locationLayer = Layer.succeed(
Location.Service,
@ -81,6 +82,7 @@ function testLayer(
),
)
return AppNodeBuilder.build(LayerNode.group([Config.node, EventV2.node]), [
[Config.node, Config.layer(options)],
[Location.node, locationLayer],
[Global.node, Global.layerWith({ config: globalDirectory, home: path.join(globalDirectory, "home") })],
[Credential.node, credentialNode],
@ -98,6 +100,44 @@ const provider = {
}
describe("Config", () => {
it.live("skips project configuration when project discovery is disabled", () =>
Effect.acquireRelease(
Effect.promise(() => tmpdir()),
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
).pipe(
Effect.flatMap((tmp) => {
const global = path.join(tmp.path, "global")
const project = path.join(tmp.path, "project")
return Effect.promise(async () => {
await fs.mkdir(global, { recursive: true })
await fs.mkdir(project, { recursive: true })
await fs.writeFile(path.join(global, "opencode.json"), JSON.stringify({ shell: "global" }))
await fs.writeFile(path.join(project, "opencode.json"), JSON.stringify({ shell: "project" }))
}).pipe(
Effect.andThen(
Effect.gen(function* () {
const config = yield* Config.Service
expect(Config.latest(yield* config.entries(), "shell")).toBe("global")
}).pipe(
Effect.provide(
testLayer(
project,
global,
project,
undefined,
undefined,
emptyCredentialNode,
emptyWellknownNode,
{ project: false },
),
),
),
),
)
}),
),
)
it.live("reloads external config and publishes directory updates", () =>
Effect.acquireRelease(
Effect.promise(() => tmpdir()),

View file

@ -81,6 +81,17 @@ describe("layer node", () => {
expect(await Effect.runPromise(program)).toBe("hello simulation")
})
test("preserves source dependencies when replacing a node with a layer", async () => {
const replacement = Layer.effect(
Greeting,
Effect.map(Value, (item) => Greeting.of({ value: `replaced ${item.value}` })),
)
const program = Effect.map(Greeting, (item) => item.value).pipe(
Effect.provide(build(LayerNode.group([greeting]), [[greeting, replacement]])),
)
expect(await Effect.runPromise(program)).toBe("replaced production")
})
test("replaces every use of the same layer", async () => {
const leftLayer = Layer.effect(
Left,

View file

@ -20,8 +20,10 @@ const instructionLayer = (input: {
config: string
locationServiceLayer: Layer.Layer<Location.Service>
filesystemLayer?: Layer.Layer<FSUtil.Service>
project?: boolean
}) =>
AppNodeBuilder.build(InstructionDiscovery.node, [
[InstructionDiscovery.node, InstructionDiscovery.layer({ project: input.project })],
[Global.node, Global.layerWith({ config: input.config })],
[Location.node, input.locationServiceLayer],
...(input.filesystemLayer ? [[FSUtil.node, input.filesystemLayer] as const] : []),
@ -242,15 +244,14 @@ describe("InstructionDiscovery", () => {
it.effect("honors the project instruction opt-out", () =>
Effect.gen(function* () {
const previous = process.env.OPENCODE_DISABLE_PROJECT_CONFIG
let scanned = false
process.env.OPENCODE_DISABLE_PROJECT_CONFIG = "1"
yield* InstructionDiscovery.Service.pipe(
Effect.flatMap((service) => service.load()),
Effect.provide(
instructionLayer({
config: "/global",
project: false,
filesystemLayer: Layer.effect(
FSUtil.Service,
FSUtil.Service.pipe(
@ -263,12 +264,6 @@ describe("InstructionDiscovery", () => {
),
}),
),
Effect.ensuring(
Effect.sync(() => {
if (previous === undefined) delete process.env.OPENCODE_DISABLE_PROJECT_CONFIG
else process.env.OPENCODE_DISABLE_PROJECT_CONFIG = previous
}),
),
)
expect(scanned).toBe(false)

View file

@ -161,7 +161,7 @@ const buildLayer = (state: Ref.Ref<MockState>, options: ModelsDev.Options = { fe
// every test would reuse the cachedInvalidateWithTTL state from the first run.
Layer.fresh(
AppNodeBuilder.build(ModelsDev.node, [
[ModelsDev.node, ModelsDev.nodeWith(options)],
[ModelsDev.node, ModelsDev.layer(options)],
[LayerNodePlatform.httpClient, Layer.succeed(HttpClient.HttpClient, makeMockClient(state))],
]),
)

View file

@ -26,7 +26,7 @@ const layer = AppNodeBuilder.build(LayerNode.group([Catalog.node, Integration.no
])
const it = testEffect(layer)
const models = (file: string) =>
AppNodeBuilder.build(ModelsDev.node, [[ModelsDev.node, ModelsDev.nodeWith({ file, fetch: false })]])
AppNodeBuilder.build(ModelsDev.node, [[ModelsDev.node, ModelsDev.layer({ file, fetch: false })]])
describe("ModelsDevPlugin", () => {
it.effect("projects normalized models.dev snapshots into the catalog", () =>