mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-05 07:10:03 +00:00
refactor(core): centralize service use helper
This commit is contained in:
parent
2fc0793f1f
commit
8890121da0
25 changed files with 60 additions and 95 deletions
|
|
@ -15,6 +15,7 @@ type ServiceUse<Identifier, Shape> = {
|
|||
}
|
||||
|
||||
export const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, Shape>) => {
|
||||
const cache = new Map<string, (...args: unknown[]) => Effect.Effect<unknown, unknown, unknown>>()
|
||||
// This is the only dynamic boundary: TypeScript knows the accessor shape,
|
||||
// but Proxy property names are runtime values.
|
||||
const access = new Proxy(
|
||||
|
|
@ -22,7 +23,9 @@ export const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, S
|
|||
{
|
||||
get: (_, key) => {
|
||||
if (typeof key !== "string") return undefined
|
||||
return (...args: unknown[]) =>
|
||||
const cached = cache.get(key)
|
||||
if (cached) return cached
|
||||
const accessor = (...args: unknown[]) =>
|
||||
tag.use((service) => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy keys are checked at runtime.
|
||||
const method = service[key as keyof Shape]
|
||||
|
|
@ -30,6 +33,8 @@ export const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, S
|
|||
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- ServiceUse exposes only Effect-returning methods.
|
||||
return (method as (...args: unknown[]) => Effect.Effect<unknown, unknown, unknown>)(...args)
|
||||
})
|
||||
cache.set(key, accessor)
|
||||
return accessor
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -3,46 +3,10 @@ import { dirname, join, relative, resolve as pathResolve } from "path"
|
|||
import { realpathSync } from "fs"
|
||||
import * as NFS from "fs/promises"
|
||||
import { lookup } from "mime-types"
|
||||
import { Effect, FileSystem, Layer, Schema, Context } from "effect"
|
||||
import { Context, Effect, FileSystem, Layer, Schema } from "effect"
|
||||
import type { PlatformError } from "effect/PlatformError"
|
||||
import { Glob } from "./util/glob"
|
||||
|
||||
type EffectMethod = (...args: ReadonlyArray<never>) => Effect.Effect<unknown, unknown, unknown>
|
||||
|
||||
type ServiceUse<Identifier, Shape> = {
|
||||
readonly [Key in keyof Shape as Shape[Key] extends EffectMethod ? Key : never]: Shape[Key] extends (
|
||||
...args: infer Args
|
||||
) => infer Return
|
||||
? Args extends ReadonlyArray<unknown>
|
||||
? Return extends Effect.Effect<infer A, infer E, infer R>
|
||||
? (...args: Args) => Effect.Effect<A, E, R | Identifier>
|
||||
: never
|
||||
: never
|
||||
: never
|
||||
}
|
||||
|
||||
const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, Shape>) => {
|
||||
// This is the only dynamic boundary: TypeScript knows the accessor shape,
|
||||
// but Proxy property names are runtime values.
|
||||
const access = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_, key) => {
|
||||
if (typeof key !== "string") return undefined
|
||||
return (...args: unknown[]) =>
|
||||
tag.use((service) => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy keys are checked at runtime.
|
||||
const method = service[key as keyof Shape]
|
||||
if (typeof method !== "function") return Effect.die(new Error(`Service method not found: ${key}`))
|
||||
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- ServiceUse exposes only Effect-returning methods.
|
||||
return (method as (...args: unknown[]) => Effect.Effect<unknown, unknown, unknown>)(...args)
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy implements the mapped accessor surface lazily.
|
||||
return access as ServiceUse<Identifier, Shape>
|
||||
}
|
||||
import { serviceUse } from "./effect/service-use"
|
||||
|
||||
export namespace AppFileSystem {
|
||||
export class FileSystemError extends Schema.TaggedErrorClass<FileSystemError>()("FileSystemError", {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import {
|
||||
FetchHttpClient,
|
||||
HttpClient,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { eq } from "drizzle-orm"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Effect, Layer, Option, Schema, Context } from "effect"
|
||||
|
||||
import { Database } from "@/storage/db"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Config } from "@/config/config"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { ModelID, ProviderID } from "../provider/schema"
|
||||
import { generateObject, streamObject, type ModelMessage } from "ai"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { BusEvent } from "./bus-event"
|
|||
import { GlobalBus } from "./global"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Identifier } from "@/id/id"
|
||||
import type { InstanceContext } from "@/project/instance-context"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import os from "os"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Context, Effect, FiberMap, Iterable, Layer, Schema, Stream } from "effect"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { FetchHttpClient, HttpBody, HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http"
|
||||
import { Database } from "@/storage/db"
|
||||
import { asc } from "drizzle-orm"
|
||||
|
|
|
|||
2
packages/opencode/src/env/index.ts
vendored
2
packages/opencode/src/env/index.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import { Context, Effect, Layer } from "effect"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
||||
type State = Record<string, string | undefined>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import path from "path"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Cause, Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect"
|
||||
import type { PlatformError } from "effect/PlatformError"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect, Layer, Schema, Context, Stream } from "effect"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { withTransientReadRetry } from "@/util/effect-http-client"
|
||||
import { errorMessage } from "@/util/error"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import path from "path"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Effect, Layer, Context, Option, Schema } from "effect"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { dynamicTool, type Tool, jsonSchema, type JSONSchema7 } from "ai"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { GlobalBus } from "@/bus/global"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { disposeInstance as runDisposers } from "@/effect/instance-registry"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { AppProcess } from "@opencode-ai/core/process"
|
|||
import { Project as ProjectV2 } from "@opencode-ai/core/project"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { AbsolutePath, NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
|
||||
const log = Log.create({ service: "project" })
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Auth } from "@/auth"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import * as Log from "@opencode-ai/core/util/log"
|
|||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { Hash } from "@opencode-ai/core/util/hash"
|
||||
import { Plugin } from "../plugin"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { type LanguageModelV3 } from "@ai-sdk/provider"
|
||||
import * as ModelsDev from "@opencode-ai/core/models-dev"
|
||||
import { Auth } from "../auth"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { Effect, Layer, Context, Schema } from "effect"
|
|||
import * as DateTime from "effect/DateTime"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { isOverflow as overflow, usable } from "./overflow"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Provider } from "@/provider/provider"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Slug } from "@opencode-ai/core/util/slug"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import path from "path"
|
||||
import { BackgroundJob } from "@/background/job"
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type * as SDK from "@opencode-ai/sdk/v2"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Effect, Exit, Layer, Option, Schema, Scope, Context, Stream } from "effect"
|
||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { Account } from "@/account/account"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { EventID } from "./schema"
|
|||
import { Context, Effect, Layer, Schema as EffectSchema } from "effect"
|
||||
import type { DeepMutable } from "@opencode-ai/core/schema"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
|
|
|
|||
|
|
@ -26,11 +26,6 @@ import {
|
|||
import { InstanceRuntime } from "@/project/instance-runtime"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
/** Infra layer that provides FileSystem, Path, ChildProcessSpawner for test fixtures */
|
||||
const infra = CrossSpawnSpawner.defaultLayer.pipe(
|
||||
Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)),
|
||||
)
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { pathToFileURL } from "url"
|
||||
|
|
@ -42,6 +37,11 @@ import { AccountTest } from "../fake/account"
|
|||
import { AuthTest } from "../fake/auth"
|
||||
import { NpmTest } from "../fake/npm"
|
||||
|
||||
/** Infra layer that provides FileSystem, Path, ChildProcessSpawner for test fixtures */
|
||||
const infra = CrossSpawnSpawner.defaultLayer.pipe(
|
||||
Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)),
|
||||
)
|
||||
|
||||
const testFlock = EffectFlock.defaultLayer
|
||||
|
||||
const unexpectedHttp = HttpClient.make((request) =>
|
||||
|
|
@ -148,28 +148,29 @@ async function writeConfig(dir: string, config: object, name = "opencode.json")
|
|||
}
|
||||
|
||||
const writeConfigEffect = (dir: string, config: object, name = "opencode.json") =>
|
||||
AppFileSystem.use.writeFileString(path.join(dir, name), JSON.stringify(config))
|
||||
AppFileSystem.use.writeWithDirs(path.join(dir, name), JSON.stringify(config))
|
||||
|
||||
const project = {
|
||||
dir: (relative: string) =>
|
||||
TestInstance.use((test) => AppFileSystem.use.ensureDir(path.join(test.directory, relative))),
|
||||
file: (relative: string, content: string) =>
|
||||
TestInstance.use((test) => AppFileSystem.use.writeWithDirs(path.join(test.directory, relative), content)),
|
||||
}
|
||||
const withInstanceDir = <A, E, R>(dir: string, effect: Effect.Effect<A, E, R>) =>
|
||||
effect.pipe(
|
||||
Effect.provideService(TestInstance, { directory: dir }),
|
||||
provideInstanceEffect(dir),
|
||||
Effect.provide(testInstanceStoreLayer),
|
||||
Effect.provide(CrossSpawnSpawner.defaultLayer),
|
||||
)
|
||||
|
||||
const withGlobalConfigDir = <A, E, R>(dir: string, effect: Effect.Effect<A, E, R>) =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.gen(function* () {
|
||||
const previous = Global.Path.config
|
||||
;(Global.Path as { config: string }).config = dir
|
||||
yield* clearEffect(true)
|
||||
yield* clearEffect()
|
||||
return previous
|
||||
}),
|
||||
() => effect,
|
||||
(previous) =>
|
||||
Effect.gen(function* () {
|
||||
;(Global.Path as { config: string }).config = previous
|
||||
yield* clearEffect(true)
|
||||
yield* clearEffect()
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -190,17 +191,17 @@ const withConfigTree = <A, E, R>(
|
|||
Effect.gen(function* () {
|
||||
const root = yield* tmpdirScoped()
|
||||
const directory = path.join(root, "project")
|
||||
const local = path.join(directory, ".opencode")
|
||||
yield* AppFileSystem.use.ensureDir(local)
|
||||
if (input.global) yield* writeConfigEffect(root, schemaConfig(input.global))
|
||||
if (input.project) yield* writeConfigEffect(directory, schemaConfig(input.project))
|
||||
if (input.local) yield* writeConfigEffect(local, schemaConfig(input.local))
|
||||
return yield* effect.pipe(
|
||||
Effect.provideService(TestInstance, { directory }),
|
||||
provideInstanceEffect(directory),
|
||||
Effect.provide(testInstanceStoreLayer),
|
||||
Effect.provide(CrossSpawnSpawner.defaultLayer),
|
||||
yield* Effect.all(
|
||||
[
|
||||
input.global ? writeConfigEffect(root, schemaConfig(input.global)) : undefined,
|
||||
input.project ? writeConfigEffect(directory, schemaConfig(input.project)) : undefined,
|
||||
input.local ? writeConfigEffect(path.join(directory, ".opencode"), schemaConfig(input.local)) : undefined,
|
||||
].filter(
|
||||
(effect): effect is Effect.Effect<void, AppFileSystem.Error, AppFileSystem.Service> => effect !== undefined,
|
||||
),
|
||||
{ concurrency: "unbounded" },
|
||||
)
|
||||
return yield* withInstanceDir(directory, effect)
|
||||
})
|
||||
|
||||
const wellKnown = (input: {
|
||||
|
|
@ -705,7 +706,6 @@ it.instance("migrates mode field to agent field", () =>
|
|||
it.instance("loads config from .opencode directory", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "agent"))
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "agent", "test.md"),
|
||||
`---
|
||||
|
|
@ -728,7 +728,6 @@ Test agent prompt`,
|
|||
it.instance("agent markdown permission config preserves user key order", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "agent"))
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "agent", "ordered.md"),
|
||||
`---
|
||||
|
|
@ -748,7 +747,6 @@ Ordered permissions`,
|
|||
it.instance("loads agents from .opencode/agents (plural)", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "agents", "nested"))
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "agents", "helper.md"),
|
||||
`---
|
||||
|
|
@ -788,7 +786,6 @@ Nested agent prompt`,
|
|||
it.instance("loads commands from .opencode/command (singular)", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "command", "nested"))
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "command", "hello.md"),
|
||||
`---
|
||||
|
|
@ -822,7 +819,6 @@ Nested command template`,
|
|||
it.instance("loads commands from .opencode/commands (plural)", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "commands", "nested"))
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "commands", "hello.md"),
|
||||
`---
|
||||
|
|
@ -912,7 +908,6 @@ it.instance("resolves scoped npm plugins in config", () =>
|
|||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const pluginDir = path.join(test.directory, "node_modules", "@scope", "plugin")
|
||||
yield* AppFileSystem.use.ensureDir(pluginDir)
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, "package.json"),
|
||||
JSON.stringify({ name: "config-fixture", version: "1.0.0", type: "module" }, null, 2),
|
||||
|
|
@ -960,7 +955,6 @@ it.effect("merges plugin arrays from global and local configs", () =>
|
|||
it.instance("does not error when only custom agent is a subagent", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "agent"))
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "agent", "helper.md"),
|
||||
`---
|
||||
|
|
@ -1671,8 +1665,11 @@ describe("deduplicatePluginOrigins", () => {
|
|||
withConfigTree(
|
||||
{ global: { plugin: ["my-plugin@1.0.0"] } },
|
||||
Effect.gen(function* () {
|
||||
yield* project.dir(".opencode/plugin")
|
||||
yield* project.file(".opencode/plugin/my-plugin.js", "export default {}")
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "plugin", "my-plugin.js"),
|
||||
"export default {}",
|
||||
)
|
||||
|
||||
const plugins = (yield* Config.use.get()).plugin ?? []
|
||||
expect(plugins.some((p) => ConfigPlugin.pluginSpecifier(p) === "my-plugin@1.0.0")).toBe(true)
|
||||
|
|
@ -1704,7 +1701,6 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
|
|||
"true",
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "command"))
|
||||
yield* AppFileSystem.use.writeWithDirs(
|
||||
path.join(test.directory, ".opencode", "command", "test-cmd.md"),
|
||||
"# Test Command\nThis is a test command.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue