mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-22 02:23:25 +00:00
fix(tui): avoid retrying broken plugin setup (#43441)
Co-authored-by: nexxeln <95541290+nexxeln@users.noreply.github.com>
This commit is contained in:
parent
bfe9917ee7
commit
728053b645
2 changed files with 40 additions and 0 deletions
|
|
@ -239,6 +239,9 @@ export function PluginProvider(props: ParentProps<{ packages: PackageResolver; d
|
|||
// Package resolution failures would otherwise retry a full npm install on
|
||||
// every watch event; remember them until the configuration changes.
|
||||
const npmFailures = new Map<string, string>()
|
||||
// A source that imports but fails setup must not tear down and restore its
|
||||
// last-good generation again for every event in the same filesystem burst.
|
||||
const setupFailures = new Map<string, { version: string; options: Desired["options"]; error: string }>()
|
||||
const reconcile = async () => {
|
||||
await Promise.all(props.directories.map(watcher.wait))
|
||||
const entries = [
|
||||
|
|
@ -303,6 +306,29 @@ export function PluginProvider(props: ParentProps<{ packages: PackageResolver; d
|
|||
})
|
||||
continue
|
||||
}
|
||||
const setupFailure = setupFailures.get(target)
|
||||
if (
|
||||
previous &&
|
||||
setupFailure?.version === resolved.version &&
|
||||
sameOptions(setupFailure.options, options)
|
||||
) {
|
||||
failures.push({
|
||||
target,
|
||||
id: previous.plugin.id,
|
||||
status: "failed",
|
||||
error: previous.active ? `${setupFailure.error} (previous version still active)` : setupFailure.error,
|
||||
})
|
||||
desired.set(previous.plugin.id, {
|
||||
plugin: previous.plugin,
|
||||
source: previous.source,
|
||||
target,
|
||||
version: previous.version,
|
||||
options: previous.options,
|
||||
enabled: previous.active,
|
||||
})
|
||||
continue
|
||||
}
|
||||
setupFailures.delete(target)
|
||||
desired.set(resolved.plugin.id, {
|
||||
plugin: resolved.plugin,
|
||||
source: "external",
|
||||
|
|
@ -377,6 +403,8 @@ export function PluginProvider(props: ParentProps<{ packages: PackageResolver; d
|
|||
const error = await activate(id).then(() => undefined, errorMessage)
|
||||
if (!error) continue
|
||||
errors.set(id, error)
|
||||
if (item.target)
|
||||
setupFailures.set(item.target, { version: item.version, options: item.options, error })
|
||||
if (!fallback) continue
|
||||
setStore("registrations", id, toRegistration(fallback))
|
||||
if (!fallback.enabled) continue
|
||||
|
|
|
|||
|
|
@ -271,12 +271,17 @@ test("a save whose setup throws restores the previous version", async () => {
|
|||
const directory = path.join(tmp.path, ".opencode", "plugins", "tui")
|
||||
await mkdir(directory, { recursive: true })
|
||||
const marker = path.join(tmp.path, "a.txt")
|
||||
const markerB = path.join(tmp.path, "b.txt")
|
||||
const source = path.join(directory, "a.ts")
|
||||
const sourceB = path.join(directory, "b.ts")
|
||||
await writeFile(source, lifecycleSource(marker, "test.a", "a1"))
|
||||
await writeFile(sourceB, lifecycleSource(markerB, "test.b", "b1"))
|
||||
|
||||
await using app = await bootApp(tmp.path)
|
||||
const read = () => readFile(marker, "utf8")
|
||||
const readB = () => readFile(markerB, "utf8")
|
||||
expect(await until(read, (value) => value === "a1:setup\n")).toBe("a1:setup\n")
|
||||
expect(await until(readB, (value) => value === "b1:setup\n")).toBe("b1:setup\n")
|
||||
|
||||
// The module imports fine but its setup throws — unlike an import failure,
|
||||
// the swap has already torn down a1, so keep-last-good means restoring it.
|
||||
|
|
@ -295,6 +300,13 @@ export default {
|
|||
"a1:setup\na1:cleanup\na1:setup\n",
|
||||
)
|
||||
|
||||
// A later reconcile must not retry the same broken generation.
|
||||
await writeFile(sourceB, lifecycleSource(markerB, "test.b", "b2"))
|
||||
expect(await until(readB, (value) => value?.includes("b2:setup") ?? false)).toBe(
|
||||
"b1:setup\nb1:cleanup\nb2:setup\n",
|
||||
)
|
||||
expect(await read()).toBe("a1:setup\na1:cleanup\na1:setup\n")
|
||||
|
||||
// Fixing the file swaps out the restored version normally.
|
||||
await writeFile(source, lifecycleSource(marker, "test.a", "a2"))
|
||||
expect(await until(read, (value) => value?.includes("a2:setup") ?? false)).toBe(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue