mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-13 15:44:56 +00:00
effect(cli): kill TuiConfig/Installation/SessionCompaction facades
Remove makeRuntime + async facade exports from TuiConfig, Installation, and SessionCompaction. Migrate production callers to AppRuntime.runPromise with yielded services, and route tests through dedicated mockable hooks where they previously spied on the facade.
This commit is contained in:
@@ -197,7 +197,7 @@ Most of the original facade-removal backlog is already done. The practical remai
|
||||
## Checklist
|
||||
|
||||
- [ ] `src/npm/index.ts` (`Npm`) - still exports runtime-backed async facade helpers on top of `Npm.Service`
|
||||
- [ ] `src/cli/cmd/tui/config/tui.ts` (`TuiConfig`) - still exports runtime-backed async facade helpers on top of `TuiConfig.Service`
|
||||
- [x] `src/cli/cmd/tui/config/tui.ts` (`TuiConfig`) - facades removed
|
||||
- [x] `src/session/session.ts` / `src/session/prompt.ts` / `src/session/revert.ts` / `src/session/summary.ts` - service-local facades removed
|
||||
- [x] `src/agent/agent.ts` (`Agent`) - service-local facades removed
|
||||
- [x] `src/permission/index.ts` (`Permission`) - service-local facades removed
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Context, Effect, Layer } from "effect"
|
||||
import { stringifyKeyStroke } from "@opentui/keymap"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { TuiKeybind } from "@/cli/cmd/tui/config/keybind"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { reusePendingTask } from "./runtime.shared"
|
||||
import { resolveSession, sessionHistory } from "./session.shared"
|
||||
@@ -40,7 +41,7 @@ export type SessionInfo = {
|
||||
variant: string | undefined
|
||||
}
|
||||
|
||||
type Config = Awaited<ReturnType<typeof TuiConfig.get>>
|
||||
type Config = TuiConfig.Resolved
|
||||
type BootService = {
|
||||
readonly resolveModelInfo: (
|
||||
sdk: RunInput["sdk"],
|
||||
@@ -60,8 +61,14 @@ const configTask: { current?: Promise<Config> } = {}
|
||||
|
||||
class Service extends Context.Service<Service, BootService>()("@opencode/RunBoot") {}
|
||||
|
||||
// Exposed on `TuiConfigLoader` so tests can mock without depending on the
|
||||
// TuiConfig namespace; production calls TuiConfig.Service via AppRuntime.
|
||||
export const TuiConfigLoader = {
|
||||
get: () => AppRuntime.runPromise(TuiConfig.Service.use((svc) => svc.get()).pipe(Effect.provide(TuiConfig.layer))),
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
return reusePendingTask(configTask, () => TuiConfig.get())
|
||||
return reusePendingTask(configTask, () => TuiConfigLoader.get())
|
||||
}
|
||||
|
||||
function emptyModelInfo(): ModelInfo {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { cmd } from "../cmd"
|
||||
import { UI } from "@/cli/ui"
|
||||
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { validateSession } from "./validate-session"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
@@ -66,7 +68,9 @@ export const AttachCommand = cmd({
|
||||
}
|
||||
})()
|
||||
const headers = ServerAuth.headers({ password: args.password, username: args.username })
|
||||
const config = await TuiConfig.get()
|
||||
const config = await AppRuntime.runPromise(
|
||||
TuiConfig.Service.use((svc) => svc.get()).pipe(Effect.provide(TuiConfig.layer)),
|
||||
)
|
||||
const { tui } = await import("./app")
|
||||
|
||||
try {
|
||||
|
||||
@@ -16,7 +16,6 @@ import { CurrentWorkingDirectory } from "./cwd"
|
||||
import { ConfigPlugin } from "@/config/plugin"
|
||||
import { TuiKeybind } from "./keybind"
|
||||
import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { ConfigVariable } from "@/config/variable"
|
||||
@@ -245,13 +244,3 @@ export const layer = Layer.effect(
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Npm.defaultLayer), Layer.provide(AppFileSystem.defaultLayer))
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
|
||||
export async function waitForDependencies() {
|
||||
await runPromise((svc) => svc.waitForDependencies())
|
||||
}
|
||||
|
||||
export async function get() {
|
||||
return runPromise((svc) => svc.get())
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { errorData, errorMessage } from "@/util/error"
|
||||
import { isRecord } from "@/util/record"
|
||||
@@ -42,6 +44,14 @@ import { createCommandShim } from "./command-shim"
|
||||
|
||||
ensureRuntimePluginSupport({ additional: keymapRuntimeModules })
|
||||
|
||||
// Exposed as a separate function so tests can mock it without touching the
|
||||
// TuiConfig service. Production callers wait on plugin install fibers via
|
||||
// AppRuntime + TuiConfig.layer; tests replace this with a no-op.
|
||||
export const waitForDependencies = () =>
|
||||
AppRuntime.runPromise(
|
||||
TuiConfig.Service.use((svc) => svc.waitForDependencies()).pipe(Effect.provide(TuiConfig.layer)),
|
||||
)
|
||||
|
||||
type PluginLoad = {
|
||||
options: ConfigPlugin.Options | undefined
|
||||
spec: string
|
||||
@@ -837,7 +847,7 @@ async function addPluginBySpec(state: RuntimeState | undefined, raw: string) {
|
||||
state.pending.delete(spec)
|
||||
return true
|
||||
}
|
||||
const ready = await resolveExternalPlugins([cfg], () => TuiConfig.waitForDependencies()).catch((error) => {
|
||||
const ready = await resolveExternalPlugins([cfg], () => waitForDependencies()).catch((error) => {
|
||||
fail("failed to add tui plugin", { path: next, error })
|
||||
return [] as PluginLoad[]
|
||||
})
|
||||
@@ -1049,7 +1059,7 @@ async function load(input: { api: Api; config: TuiConfig.Resolved }) {
|
||||
})
|
||||
}
|
||||
|
||||
const ready = await resolveExternalPlugins(records, () => TuiConfig.waitForDependencies())
|
||||
const ready = await resolveExternalPlugins(records, () => waitForDependencies())
|
||||
await addExternalPluginEntries(next, ready)
|
||||
|
||||
applyInitialPluginEnabledState(next, config)
|
||||
|
||||
@@ -14,6 +14,8 @@ import type { EventSource } from "./context/sdk"
|
||||
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
||||
import { writeHeapSnapshot } from "v8"
|
||||
import { TuiConfig } from "./config/tui"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Effect } from "effect"
|
||||
import {
|
||||
OPENCODE_PROCESS_ROLE,
|
||||
OPENCODE_RUN_ID,
|
||||
@@ -187,7 +189,9 @@ export const TuiThreadCommand = cmd({
|
||||
}
|
||||
|
||||
const prompt = await input(args.prompt)
|
||||
const config = await TuiConfig.get()
|
||||
const config = await AppRuntime.runPromise(
|
||||
TuiConfig.Service.use((svc) => svc.get()).pipe(Effect.provide(TuiConfig.layer)),
|
||||
)
|
||||
|
||||
const network = resolveNetworkOptionsNoConfig(args)
|
||||
const external =
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Argv } from "yargs"
|
||||
import { UI } from "../ui"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { Installation } from "../../installation"
|
||||
import { AppRuntime } from "../../effect/app-runtime"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
@@ -57,7 +58,7 @@ export const UninstallCommand = {
|
||||
UI.empty()
|
||||
prompts.intro("Uninstall OpenCode")
|
||||
|
||||
const method = await Installation.method()
|
||||
const method = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method()))
|
||||
prompts.log.info(`Installation method: ${method}`)
|
||||
|
||||
const targets = await collectRemovalTargets(args, method)
|
||||
|
||||
@@ -2,7 +2,9 @@ import type { Argv } from "yargs"
|
||||
import { UI } from "../ui"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { Installation } from "../../installation"
|
||||
import { AppRuntime } from "../../effect/app-runtime"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export const UpgradeCommand = {
|
||||
command: "upgrade [target]",
|
||||
@@ -25,7 +27,7 @@ export const UpgradeCommand = {
|
||||
UI.println(UI.logo(" "))
|
||||
UI.empty()
|
||||
prompts.intro("Upgrade")
|
||||
const detectedMethod = await Installation.method()
|
||||
const detectedMethod = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method()))
|
||||
const method = (args.method as Installation.Method) ?? detectedMethod
|
||||
if (method === "unknown") {
|
||||
prompts.log.error(`opencode is installed to ${process.execPath} and may be managed by a package manager`)
|
||||
@@ -43,7 +45,9 @@ export const UpgradeCommand = {
|
||||
}
|
||||
}
|
||||
prompts.log.info("Using method: " + method)
|
||||
const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest()
|
||||
const target = args.target
|
||||
? args.target.replace(/^v/, "")
|
||||
: await AppRuntime.runPromise(Installation.Service.use((svc) => svc.latest()))
|
||||
|
||||
if (InstallationVersion === target) {
|
||||
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
||||
@@ -54,7 +58,15 @@ export const UpgradeCommand = {
|
||||
prompts.log.info(`From ${InstallationVersion} → ${target}`)
|
||||
const spinner = prompts.spinner()
|
||||
spinner.start("Upgrading...")
|
||||
const err = await Installation.upgrade(method, target).catch((err) => err)
|
||||
const err = await AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const installation = yield* Installation.Service
|
||||
return yield* installation.upgrade(method, target).pipe(
|
||||
Effect.map(() => undefined as Installation.UpgradeFailedError | Error | undefined),
|
||||
Effect.catch((error) => Effect.succeed(error as Installation.UpgradeFailedError | Error | undefined)),
|
||||
)
|
||||
}),
|
||||
).catch((err: Error) => err)
|
||||
if (err) {
|
||||
spinner.stop("Upgrade failed", 1)
|
||||
if (err instanceof Installation.UpgradeFailedError) {
|
||||
|
||||
@@ -4,30 +4,40 @@ import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Installation } from "@/installation"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export async function upgrade() {
|
||||
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.getGlobal()))
|
||||
if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return
|
||||
const method = await Installation.method()
|
||||
const latest = await Installation.latest(method).catch(() => {})
|
||||
if (!latest) return
|
||||
await AppRuntime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const cfg = yield* Config.Service
|
||||
const installation = yield* Installation.Service
|
||||
const bus = yield* Bus.Service
|
||||
|
||||
if (Flag.OPENCODE_ALWAYS_NOTIFY_UPDATE) {
|
||||
await Bus.publish(Installation.Event.UpdateAvailable, { version: latest })
|
||||
return
|
||||
}
|
||||
const config = yield* cfg.getGlobal()
|
||||
if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return
|
||||
const method = yield* installation.method()
|
||||
const latest = yield* installation.latest(method).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
if (!latest) return
|
||||
|
||||
if (InstallationVersion === latest) return
|
||||
if (Flag.OPENCODE_ALWAYS_NOTIFY_UPDATE) {
|
||||
yield* bus.publish(Installation.Event.UpdateAvailable, { version: latest })
|
||||
return
|
||||
}
|
||||
|
||||
const kind = Installation.getReleaseType(InstallationVersion, latest)
|
||||
if (InstallationVersion === latest) return
|
||||
|
||||
if (config.autoupdate === "notify" || kind !== "patch") {
|
||||
await Bus.publish(Installation.Event.UpdateAvailable, { version: latest })
|
||||
return
|
||||
}
|
||||
const kind = Installation.getReleaseType(InstallationVersion, latest)
|
||||
|
||||
if (method === "unknown") return
|
||||
await Installation.upgrade(method, latest)
|
||||
.then(() => Bus.publish(Installation.Event.Updated, { version: latest }))
|
||||
.catch(() => {})
|
||||
if (config.autoupdate === "notify" || kind !== "patch") {
|
||||
yield* bus.publish(Installation.Event.UpdateAvailable, { version: latest })
|
||||
return
|
||||
}
|
||||
|
||||
if (method === "unknown") return
|
||||
yield* installation.upgrade(method, latest).pipe(
|
||||
Effect.flatMap(() => bus.publish(Installation.Event.Updated, { version: latest })),
|
||||
Effect.catch(() => Effect.void),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import path from "path"
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
import semver from "semver"
|
||||
import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { NpmConfig } from "@opencode-ai/core/npm-config"
|
||||
@@ -325,10 +324,4 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
)
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
|
||||
export const latest = (...args: Parameters<Interface["latest"]>) => runPromise((s) => s.latest(...args))
|
||||
export const method = () => runPromise((s) => s.method())
|
||||
export const upgrade = (...args: Parameters<Interface["upgrade"]>) => runPromise((s) => s.upgrade(...args))
|
||||
|
||||
export * as Installation from "."
|
||||
|
||||
@@ -16,7 +16,6 @@ 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 { makeRuntime } from "@/effect/run-service"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { SyncEvent } from "@/sync"
|
||||
import { SessionEvent } from "@/v2/session-event"
|
||||
@@ -649,14 +648,4 @@ export const defaultLayer = Layer.suspend(() =>
|
||||
),
|
||||
)
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
|
||||
export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) {
|
||||
return runPromise((svc) => svc.isOverflow(input))
|
||||
}
|
||||
|
||||
export async function prune(input: { sessionID: SessionID }) {
|
||||
return runPromise((svc) => svc.prune(input))
|
||||
}
|
||||
|
||||
export * as SessionCompaction from "./compaction"
|
||||
|
||||
@@ -3,10 +3,15 @@ import type { KeyEvent, Renderable } from "@opentui/core"
|
||||
import type { Binding } from "@opentui/keymap"
|
||||
import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { OpencodeClient, type Provider } from "@opencode-ai/sdk/v2"
|
||||
import { TuiConfig, type Resolved } from "@/cli/cmd/tui/config/tui"
|
||||
import { type Resolved } from "@/cli/cmd/tui/config/tui"
|
||||
import { formatBindings } from "@/cli/cmd/run/keymap.shared"
|
||||
import { TuiKeybind } from "@/cli/cmd/tui/config/keybind"
|
||||
import { resolveDiffStyle, resolveFooterKeybinds, resolveModelInfo } from "@/cli/cmd/run/runtime.boot"
|
||||
import {
|
||||
TuiConfigLoader,
|
||||
resolveDiffStyle,
|
||||
resolveFooterKeybinds,
|
||||
resolveModelInfo,
|
||||
} from "@/cli/cmd/run/runtime.boot"
|
||||
|
||||
type RunBinding = Binding<Renderable, KeyEvent>
|
||||
|
||||
@@ -108,7 +113,7 @@ describe("run runtime boot", () => {
|
||||
})
|
||||
|
||||
test("reads footer keybinds from resolved keybind config", async () => {
|
||||
spyOn(TuiConfig, "get").mockResolvedValue(
|
||||
spyOn(TuiConfigLoader, "get").mockResolvedValue(
|
||||
config({
|
||||
leader: "ctrl+g",
|
||||
bindings: {
|
||||
@@ -139,7 +144,7 @@ describe("run runtime boot", () => {
|
||||
})
|
||||
|
||||
test("falls back to default keybinds when config load fails", async () => {
|
||||
spyOn(TuiConfig, "get").mockRejectedValue(new Error("boom"))
|
||||
spyOn(TuiConfigLoader, "get").mockRejectedValue(new Error("boom"))
|
||||
|
||||
const result = await resolveFooterKeybinds()
|
||||
|
||||
@@ -156,11 +161,11 @@ describe("run runtime boot", () => {
|
||||
})
|
||||
|
||||
test("reads diff style and falls back to auto", async () => {
|
||||
spyOn(TuiConfig, "get").mockResolvedValue(config({ diff_style: "stacked" }))
|
||||
spyOn(TuiConfigLoader, "get").mockResolvedValue(config({ diff_style: "stacked" }))
|
||||
await expect(resolveDiffStyle()).resolves.toBe("stacked")
|
||||
|
||||
mock.restore()
|
||||
spyOn(TuiConfig, "get").mockRejectedValue(new Error("boom"))
|
||||
spyOn(TuiConfigLoader, "get").mockRejectedValue(new Error("boom"))
|
||||
await expect(resolveDiffStyle()).resolves.toBe("auto")
|
||||
})
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import { pathToFileURL } from "url"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
import { createTuiPluginApi } from "../../fixture/tui-plugin"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
|
||||
|
||||
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
|
||||
|
||||
test("adds tui plugin at runtime from spec", async () => {
|
||||
@@ -35,7 +33,7 @@ test("adds tui plugin at runtime from spec", async () => {
|
||||
const config = createTuiResolvedConfig({
|
||||
plugin: [],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
@@ -77,7 +75,7 @@ test("retries runtime add for file plugins after dependency wait", async () => {
|
||||
const config = createTuiResolvedConfig({
|
||||
plugin: [],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockImplementation(async () => {
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockImplementation(async () => {
|
||||
await Bun.write(
|
||||
path.join(tmp.extra.mod, "index.ts"),
|
||||
`export default {
|
||||
|
||||
@@ -5,8 +5,6 @@ import { pathToFileURL } from "url"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
import { createTuiPluginApi } from "../../fixture/tui-plugin"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
|
||||
|
||||
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
|
||||
|
||||
test("installs plugin without loading it", async () => {
|
||||
@@ -54,7 +52,7 @@ test("installs plugin without loading it", async () => {
|
||||
const config = createTuiResolvedConfig({
|
||||
plugin: [],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const api = createTuiPluginApi({
|
||||
state: {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { pathToFileURL } from "url"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
import { createTuiPluginApi } from "../../fixture/tui-plugin"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
|
||||
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
|
||||
@@ -55,7 +54,7 @@ test("loads npm tui plugin from package ./tui export", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined })
|
||||
|
||||
@@ -116,7 +115,7 @@ test("does not use npm package exports dot for tui entry", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined })
|
||||
|
||||
@@ -178,7 +177,7 @@ test("rejects npm tui export that resolves outside plugin directory", async () =
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined })
|
||||
|
||||
@@ -240,7 +239,7 @@ test("rejects npm tui plugin that exports server and tui together", async () =>
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined })
|
||||
|
||||
@@ -298,7 +297,7 @@ test("does not use npm package main for tui entry", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined })
|
||||
const warn = spyOn(console, "warn").mockImplementation(() => {})
|
||||
@@ -363,7 +362,7 @@ test("does not use directory package main for tui entry", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
@@ -410,7 +409,7 @@ test("uses directory index fallback for tui when package.json is missing", async
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
@@ -467,7 +466,7 @@ test("uses npm package name when tui plugin id is omitted", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const install = spyOn(Npm, "add").mockResolvedValue({ directory: tmp.extra.mod, entrypoint: undefined })
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import { pathToFileURL } from "url"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
import { createTuiPluginApi } from "../../fixture/tui-plugin"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
|
||||
|
||||
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
|
||||
|
||||
test("skips external tui plugins in pure mode", async () => {
|
||||
@@ -48,7 +46,7 @@ test("skips external tui plugins in pure mode", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { tmpdir } from "../../fixture/fixture"
|
||||
import { createTuiPluginApi } from "../../fixture/tui-plugin"
|
||||
import { createTuiResolvedConfig, mockTuiRuntime } from "../../fixture/tui-runtime"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
|
||||
const { allThemes, addTheme } = await import("../../../src/cli/cmd/tui/context/theme")
|
||||
@@ -339,7 +338,7 @@ export default {
|
||||
},
|
||||
})
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
|
||||
try {
|
||||
expect(addTheme(tmp.extra.preloadedThemeName, { theme: { primary: "#303030" } })).toBe(true)
|
||||
@@ -539,7 +538,7 @@ test("continues loading when a plugin is missing config metadata", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
@@ -784,7 +783,7 @@ test("auto-disposes plugin keymap layers", async () => {
|
||||
}
|
||||
},
|
||||
} as NonNullable<Parameters<typeof createTuiPluginApi>[0]>["keymap"]
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
@@ -830,7 +829,7 @@ test("plugin keymap proxy preserves real keymap receiver", async () => {
|
||||
})
|
||||
|
||||
const harness = createTestKeymap({ defaultKeys: true })
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
@@ -893,7 +892,7 @@ test("auto-disposes plugin keymap transformers", async () => {
|
||||
prependCommandTransformer: track,
|
||||
appendCommandTransformer: track,
|
||||
} as unknown as NonNullable<Parameters<typeof createTuiPluginApi>[0]>["keymap"]
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
@@ -950,7 +949,7 @@ test("manual onDispose for plugin keymap layers stays idempotent", async () => {
|
||||
}
|
||||
},
|
||||
} as NonNullable<Parameters<typeof createTuiPluginApi>[0]>["keymap"]
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
|
||||
try {
|
||||
@@ -1021,7 +1020,7 @@ test("updates installed theme when plugin metadata changes", async () => {
|
||||
|
||||
process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json")
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
|
||||
const mkApi = () =>
|
||||
createTuiPluginApi({
|
||||
|
||||
@@ -5,8 +5,6 @@ import { pathToFileURL } from "url"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
import { createTuiPluginApi } from "../../fixture/tui-plugin"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui"
|
||||
|
||||
const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime")
|
||||
|
||||
test("toggles plugin runtime state by exported id", async () => {
|
||||
@@ -53,7 +51,7 @@ test("toggles plugin runtime state by exported id", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const api = createTuiPluginApi()
|
||||
|
||||
@@ -130,7 +128,7 @@ test("kv plugin_enabled overrides tui config on startup", async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const api = createTuiPluginApi()
|
||||
api.kv.set("plugin_enabled", {
|
||||
@@ -160,7 +158,7 @@ test("kv plugin_enabled overrides tui config on startup", async () => {
|
||||
test("loads disabled-by-default internal plugin inactive and activates on demand", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const config = createTuiResolvedConfig()
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
|
||||
const api = createTuiPluginApi()
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { spyOn } from "bun:test"
|
||||
import path from "path"
|
||||
import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { TuiConfig } from "../../src/cli/cmd/tui/config/tui"
|
||||
import { TuiPluginRuntime } from "../../src/cli/cmd/tui/plugin/runtime"
|
||||
import { TuiKeybind } from "../../src/cli/cmd/tui/config/keybind"
|
||||
|
||||
type PluginSpec = string | [string, Record<string, unknown>]
|
||||
@@ -34,7 +35,7 @@ export function mockTuiRuntime(dir: string, plugin: PluginSpec[], opts?: { plugi
|
||||
scope: "local" as const,
|
||||
source: path.join(dir, "tui.json"),
|
||||
}))
|
||||
const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue()
|
||||
const wait = spyOn(TuiPluginRuntime, "waitForDependencies").mockResolvedValue()
|
||||
const cwd = spyOn(process, "cwd").mockImplementation(() => dir)
|
||||
|
||||
const config = createTuiResolvedConfig({
|
||||
|
||||
Reference in New Issue
Block a user