Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/core/src/config/plugin/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,10 @@ const scan = Effect.fn("ConfigPluginSource.scan")(function* (
(entry.info.plugins ?? []).map(parse).map((operation) => {
if (operation.type === "remove") return operation
const directory = entry.path ? path.dirname(entry.path) : location.directory
// Absolute paths are normalised too, so `C:/x/` matches the discovered `C:\x` target.
const target = operation.target.startsWith("file://")
? fileURLToPath(operation.target)
: operation.target.startsWith("./") || operation.target.startsWith("../")
: operation.target.startsWith("./") || operation.target.startsWith("../") || path.isAbsolute(operation.target)
? path.resolve(directory, operation.target)
: operation.target
return { ...operation, target }
Expand Down
38 changes: 38 additions & 0 deletions packages/core/test/plugin/supervisor-reload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,44 @@ describe("PluginSupervisor reload", () => {
)
})

it.live("applies configured options to a discovered plugin spelled with different separators", () =>
Effect.gen(function* () {
const directory = yield* tmpdirScoped()
const root = path.join(directory.path, ".opencode/plugins/greeter")
yield* Effect.promise(async () => {
await Bun.write(
path.join(root, "index.ts"),
`export default {
id: "greeter",
async setup(ctx) {
await ctx.command.transform((editor) => editor.add({ name: "greet-" + ctx.options.name, execute: async () => {} }))
},
}`,
)
// Forward slashes and a trailing separator spell the discovered directory differently on every platform.
await Bun.write(
path.join(directory.path, ".opencode/opencode.json"),
JSON.stringify({
plugins: [{ package: root.replaceAll(path.sep, "/") + "/", options: { name: "configured" } }],
}),
)
})
const locations = yield* LocationServiceMap.Service
yield* Effect.gen(function* () {
const plugins = yield* Plugin.Service
const commands = yield* Command.Service
yield* plugins.awaitActivation

expect(yield* commands.get("greet-configured")).toBeDefined()
expect(yield* commands.get("greet-undefined")).toBeUndefined()
expect((yield* plugins.list()).filter((plugin) => plugin.state.status === "failed")).toEqual([])
}).pipe(
Effect.scoped,
Effect.provide(locations.get(Location.Ref.make({ directory: AbsolutePath.make(directory.path) }))),
)
}),
)

it.live("keeps the running generation when an updated local plugin fails to import", () =>
Effect.gen(function* () {
const directory = yield* tmpdirScoped()
Expand Down
Loading