mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-09 15:54:46 +00:00
fix(core): preserve unchanged plugin prefix (#46857)
This commit is contained in:
parent
33dd4e3ba8
commit
36095decd7
2 changed files with 98 additions and 27 deletions
|
|
@ -98,17 +98,17 @@ const layer = Layer.effect(
|
|||
() =>
|
||||
lock.withPermit(
|
||||
Effect.gen(function* () {
|
||||
if (
|
||||
active.size === definitions.length &&
|
||||
Array.from(active.values()).every((entry, index) => {
|
||||
const definition = definitions[index]
|
||||
return entry.plugin.id === definition?.id && entry.plugin.revision === definition.revision
|
||||
})
|
||||
) {
|
||||
for (const definition of definitions) {
|
||||
const entry = active.get(definition.id)
|
||||
if (entry) active.set(definition.id, { ...entry, plugin: definition })
|
||||
}
|
||||
const current = Array.from(active.values())
|
||||
const changed = definitions.findIndex((definition, index) => {
|
||||
const entry = current[index]
|
||||
return entry?.plugin.id !== definition.id || entry.plugin.revision !== definition.revision
|
||||
})
|
||||
const prefix = changed === -1 ? definitions.length : changed
|
||||
for (const definition of definitions.slice(0, prefix)) {
|
||||
const entry = active.get(definition.id)
|
||||
if (entry) active.set(definition.id, { ...entry, plugin: definition })
|
||||
}
|
||||
if (prefix === definitions.length && active.size === definitions.length) {
|
||||
const nextInventory = [...definitions.map(activeInfo), ...failures]
|
||||
if (JSON.stringify(inventory) === JSON.stringify(nextInventory)) return
|
||||
inventory = nextInventory
|
||||
|
|
@ -118,12 +118,19 @@ const layer = Layer.effect(
|
|||
|
||||
yield* State.batch(
|
||||
Effect.gen(function* () {
|
||||
const nextInventory: Plugin.Info[] = []
|
||||
for (const definition of definitions) {
|
||||
const previous = active.get(definition.id)
|
||||
active.delete(definition.id)
|
||||
if (previous) yield* Scope.close(previous.scope, Exit.void)
|
||||
|
||||
// Registrations are ordered by setup, so only the unchanged prefix can stay alive.
|
||||
const previous = new Map(Array.from(active.entries()).slice(prefix))
|
||||
yield* Effect.forEach(
|
||||
Array.from(previous.entries()).toReversed(),
|
||||
([id, entry]) =>
|
||||
Effect.gen(function* () {
|
||||
active.delete(id)
|
||||
yield* Scope.close(entry.scope, Exit.void)
|
||||
}),
|
||||
{ discard: true },
|
||||
)
|
||||
const nextInventory = definitions.slice(0, prefix).map(activeInfo)
|
||||
for (const definition of definitions.slice(prefix)) {
|
||||
const loaded = yield* load(definition)
|
||||
if (loaded.scope !== undefined) {
|
||||
active.set(definition.id, { plugin: definition, scope: loaded.scope })
|
||||
|
|
@ -137,10 +144,11 @@ const layer = Layer.effect(
|
|||
features: { server: true, ...definition.features },
|
||||
})
|
||||
|
||||
if (!previous) continue
|
||||
const restored = yield* load(previous.plugin)
|
||||
const fallback = previous.get(definition.id)
|
||||
if (!fallback) continue
|
||||
const restored = yield* load(fallback.plugin)
|
||||
if (restored.scope !== undefined) {
|
||||
active.set(definition.id, { plugin: previous.plugin, scope: restored.scope })
|
||||
active.set(definition.id, { plugin: fallback.plugin, scope: restored.scope })
|
||||
continue
|
||||
}
|
||||
yield* Effect.logError("failed to restore plugin; deactivating", {
|
||||
|
|
@ -148,13 +156,6 @@ const layer = Layer.effect(
|
|||
})
|
||||
}
|
||||
|
||||
const removed = Array.from(active.entries())
|
||||
.filter(([id]) => !ids.has(id))
|
||||
.toReversed()
|
||||
removed.forEach(([id]) => active.delete(id))
|
||||
yield* Effect.forEach(removed, ([, entry]) => Scope.close(entry.scope, Exit.void), {
|
||||
discard: true,
|
||||
})
|
||||
inventory = [...nextInventory, ...failures]
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,76 @@ import { PluginTestLayer } from "./plugin/fixture"
|
|||
|
||||
const it = testEffect(PluginTestLayer)
|
||||
|
||||
for (const scenario of [
|
||||
{
|
||||
name: "starts only the appended plugin",
|
||||
before: ["a", "b"],
|
||||
after: ["a", "b", "c"],
|
||||
expected: ["start:c:1"],
|
||||
},
|
||||
{
|
||||
name: "restarts the suffix after an insertion",
|
||||
before: ["a", "b", "c"],
|
||||
after: ["a", "x", "b", "c"],
|
||||
expected: ["stop:c:1", "stop:b:1", "start:x:1", "start:b:1", "start:c:1"],
|
||||
},
|
||||
{
|
||||
name: "restarts the suffix after a revision changes",
|
||||
before: ["a", "b", "c"],
|
||||
after: ["a", "b", "c"],
|
||||
updated: "b",
|
||||
expected: ["stop:c:1", "stop:b:1", "start:b:2", "start:c:1"],
|
||||
},
|
||||
{
|
||||
name: "stops only the removed trailing plugin",
|
||||
before: ["a", "b", "c"],
|
||||
after: ["a", "b"],
|
||||
expected: ["stop:c:1"],
|
||||
},
|
||||
]) {
|
||||
it.effect(scenario.name, () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
const events: string[] = []
|
||||
const plugin = (id: string, revision = "1"): Plugin.Generation => ({
|
||||
id,
|
||||
revision,
|
||||
effect: () =>
|
||||
Effect.gen(function* () {
|
||||
events.push(`start:${id}:${revision}`)
|
||||
yield* Effect.addFinalizer(() => Effect.sync(() => events.push(`stop:${id}:${revision}`)))
|
||||
}),
|
||||
})
|
||||
|
||||
yield* plugins.activate(scenario.before.map((id) => plugin(id)))
|
||||
events.length = 0
|
||||
yield* plugins.activate(scenario.after.map((id) => plugin(id, id === scenario.updated ? "2" : "1")))
|
||||
|
||||
expect(events).toEqual(scenario.expected)
|
||||
expect((yield* plugins.list()).map((plugin) => plugin.id)).toEqual(scenario.after.map((id) => Plugin.ID.make(id)))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
it.effect("updates inventory metadata without restarting an unchanged generation", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
let loads = 0
|
||||
const plugin = {
|
||||
id: "metadata",
|
||||
revision: "1",
|
||||
source: { type: "package" as const, target: "fixture" },
|
||||
effect: () => Effect.sync(() => loads++),
|
||||
}
|
||||
|
||||
yield* plugins.activate([plugin])
|
||||
yield* plugins.activate([{ ...plugin, source: { ...plugin.source, outdated: true } }])
|
||||
|
||||
expect(loads).toBe(1)
|
||||
expect((yield* plugins.list())[0]?.source).toEqual({ type: "package", target: "fixture", outdated: true })
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("loads a local plugin with its configured options", () =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* Plugin.Service
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue