mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-15 00:41:54 +00:00
Compare commits
2 Commits
dev
...
effect/def
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e26c3a00d | ||
|
|
9f290d4381 |
@@ -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
|
||||
|
||||
@@ -40,7 +40,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 +60,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 the TuiConfig per-service runtime.
|
||||
export const TuiConfigLoader = {
|
||||
get: () => TuiConfig.runPromise((svc) => svc.get()),
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
return reusePendingTask(configTask, () => TuiConfig.get())
|
||||
return reusePendingTask(configTask, () => TuiConfigLoader.get())
|
||||
}
|
||||
|
||||
function emptyModelInfo(): ModelInfo {
|
||||
|
||||
@@ -66,7 +66,7 @@ export const AttachCommand = cmd({
|
||||
}
|
||||
})()
|
||||
const headers = ServerAuth.headers({ password: args.password, username: args.username })
|
||||
const config = await TuiConfig.get()
|
||||
const config = await TuiConfig.runPromise((svc) => svc.get())
|
||||
const { tui } = await import("./app")
|
||||
|
||||
try {
|
||||
|
||||
@@ -14,9 +14,9 @@ import { Global } from "@opencode-ai/core/global"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { CurrentWorkingDirectory } from "./cwd"
|
||||
import { ConfigPlugin } from "@/config/plugin"
|
||||
import { defineService } from "@/effect/run-service"
|
||||
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"
|
||||
@@ -246,12 +246,4 @@ 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())
|
||||
}
|
||||
export const { runPromise, runFork, runCallback } = defineService(Service, defaultLayer)
|
||||
|
||||
@@ -42,6 +42,11 @@ 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 the
|
||||
// TuiConfig per-service runtime; tests replace this with a no-op.
|
||||
export const waitForDependencies = () => TuiConfig.runPromise((svc) => svc.waitForDependencies())
|
||||
|
||||
type PluginLoad = {
|
||||
options: ConfigPlugin.Options | undefined
|
||||
spec: string
|
||||
@@ -837,7 +842,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 +1054,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)
|
||||
|
||||
@@ -187,7 +187,7 @@ export const TuiThreadCommand = cmd({
|
||||
}
|
||||
|
||||
const prompt = await input(args.prompt)
|
||||
const config = await TuiConfig.get()
|
||||
const config = await TuiConfig.runPromise((svc) => svc.get())
|
||||
|
||||
const network = resolveNetworkOptionsNoConfig(args)
|
||||
const external =
|
||||
|
||||
@@ -57,7 +57,7 @@ export const UninstallCommand = {
|
||||
UI.empty()
|
||||
prompts.intro("Uninstall OpenCode")
|
||||
|
||||
const method = await Installation.method()
|
||||
const method = await Installation.runPromise((svc) => svc.method())
|
||||
prompts.log.info(`Installation method: ${method}`)
|
||||
|
||||
const targets = await collectRemovalTargets(args, method)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { UI } from "../ui"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import { Installation } from "../../installation"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export const UpgradeCommand = {
|
||||
command: "upgrade [target]",
|
||||
@@ -25,7 +26,7 @@ export const UpgradeCommand = {
|
||||
UI.println(UI.logo(" "))
|
||||
UI.empty()
|
||||
prompts.intro("Upgrade")
|
||||
const detectedMethod = await Installation.method()
|
||||
const detectedMethod = await Installation.runPromise((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 +44,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 Installation.runPromise((svc) => svc.latest())
|
||||
|
||||
if (InstallationVersion === target) {
|
||||
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
||||
@@ -54,7 +57,12 @@ 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 Installation.runPromise((installation) =>
|
||||
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),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -55,3 +55,16 @@ export function makeRuntime<I, S, E>(service: Context.Service<I, S>, layer: Laye
|
||||
getRuntime().runCallback(attach(service.use(fn))),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composition of `makeRuntime` for services that own their own runtime. Returns
|
||||
* the service class, the supplied layer, and the per-service runtime methods
|
||||
* (`runPromise`, `runFork`, etc.). Each runtime uses the shared `memoMap`, so
|
||||
* dependencies are built once across all `defineService` runtimes.
|
||||
*
|
||||
* Use for services that don't belong in `AppLayer` (TUI-specific config, etc.)
|
||||
* or for services where call sites want to bypass `AppRuntime` provisioning.
|
||||
*/
|
||||
export function defineService<I, S, E>(service: Context.Service<I, S>, layer: Layer.Layer<I, E>) {
|
||||
return { Service: service, layer, ...makeRuntime(service, layer) }
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ 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"
|
||||
import { defineService } from "@/effect/run-service"
|
||||
|
||||
const log = Log.create({ service: "installation" })
|
||||
|
||||
@@ -325,10 +325,6 @@ 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 const { runPromise, runFork, runCallback } = defineService(Service, defaultLayer)
|
||||
|
||||
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