diff --git a/packages/core/src/npm.ts b/packages/core/src/npm.ts index a52e0a9a51ff..c73ae52f17f5 100644 --- a/packages/core/src/npm.ts +++ b/packages/core/src/npm.ts @@ -186,16 +186,43 @@ export const layer = Layer.effect( const add = Effect.fn("Npm.add")(function* (pkg: string) { const dir = directory(pkg) - const name = (() => { + const parsed = (() => { try { - return npa(pkg).name ?? pkg - } catch { - return pkg - } + return npa(pkg) + } catch {} })() + const name = parsed?.name ?? pkg + const target = path.join(dir, "node_modules", name) if (yield* afs.existsSafe(dir)) { - return resolveEntryPoint(name, path.join(dir, "node_modules", name)) + if (parsed?.type !== "tag" || parsed.rawSpec !== "latest") return resolveEntryPoint(name, target) + const json = yield* afs.readJson(path.join(target, "package.json")).pipe(Effect.option) + if (Option.isSome(json) && json.value && typeof json.value === "object" && "version" in json.value) { + const version = json.value.version + if (typeof version === "string" && !(yield* outdated(name, version))) return resolveEntryPoint(name, target) + } + const backup = `${dir}.backup-${Date.now()}-${Math.random().toString(36).slice(2)}` + yield* fs.rename(dir, backup).pipe( + Effect.mapError((cause) => new InstallFailedError({ cause, add: [pkg], dir })), + ) + const tree = yield* reify({ dir, add: [pkg] }).pipe( + Effect.catch((error) => + fs.remove(dir, { recursive: true, force: true }).pipe( + Effect.catch(() => Effect.void), + Effect.flatMap(() => fs.rename(backup, dir)), + Effect.catch(() => Effect.void), + Effect.flatMap(() => Effect.fail(error)), + ), + ), + ) + const first = tree.edgesOut.values().next().value?.to + if (!first) { + yield* fs.remove(dir, { recursive: true, force: true }).pipe(Effect.catch(() => Effect.void)) + yield* fs.rename(backup, dir).pipe(Effect.catch(() => Effect.void)) + return yield* new InstallFailedError({ add: [pkg], dir }) + } + yield* fs.remove(backup, { recursive: true, force: true }).pipe(Effect.orElseSucceed(() => {})) + return resolveEntryPoint(first.name, first.path) } const tree = yield* reify({ dir, add: [pkg] }) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx index 3f8533124419..9a0f857544c8 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx @@ -9,7 +9,7 @@ import { useCommandDialog } from "@tui/component/dialog-command" import { useKeybind } from "../../context/keybind" import { useKV } from "../../context/kv" import { useProject } from "../../context/project" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { useTerminalDimensions } from "@opentui/solid" const Title = (props: { session: Accessor }) => { diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 030ae995baf4..1091c83453f3 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -28,7 +28,7 @@ import { registerAdaptor } from "@/control-plane/adaptors" import type { WorkspaceAdaptor } from "@/control-plane/types" const log = Log.create({ service: "plugin" }) -const BUILTIN = ["op-anthropic-auth@0.1.2"] +const BUILTIN = ["op-anthropic-auth"] const BUILTIN_ORIGINS = BUILTIN.map((spec) => ({ spec, scope: "global" as const, diff --git a/packages/opencode/test/plugin/index.test.ts b/packages/opencode/test/plugin/index.test.ts index 62c9a7ee2e06..4de6f775607f 100644 --- a/packages/opencode/test/plugin/index.test.ts +++ b/packages/opencode/test/plugin/index.test.ts @@ -7,10 +7,10 @@ describe("plugin builtin auth loading", () => { test("includes builtin anthropic auth plugin in runtime plugin list", async () => { const src = await Bun.file(file).text() - expect(src).toContain('const BUILTIN = ["op-anthropic-auth@0.1.0"]') + expect(src).toContain('const BUILTIN = ["op-anthropic-auth"]') expect(src).toContain("Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS") expect(src).toContain("const BUILTIN_ORIGINS = BUILTIN.map") - expect(src).toContain("return Config.deduplicatePluginOrigins([...BUILTIN_ORIGINS, ...(list ?? [])])") - expect(src).toContain("const plugins = Plugin.plugins(cfg.plugin_origins, Flag.OPENCODE_PURE)") + expect(src).toContain("return ConfigPlugin.deduplicatePluginOrigins([...BUILTIN_ORIGINS, ...(list ?? [])])") + expect(src).toContain("const items = plugins(cfg.plugin_origins, Flag.OPENCODE_PURE)") }) })