Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
beff163
fix: slow plugins
neriousy Jan 20, 2026
9a84dba
Merge branch 'dev' into fix/slow-plugins
neriousy Jan 20, 2026
be79d7c
feat(core): add Npm module for version management and update BunProc …
neriousy Jan 20, 2026
71a274c
revert(desktop): undo slow server guard
neriousy Jan 20, 2026
1d954eb
refactor: streamline dependency installation logic and improve versio…
neriousy Jan 20, 2026
1038fc1
feat: handle custom registries
neriousy Jan 20, 2026
4d4c02c
feat(core): add semver dependency and enhance version retrieval npm m…
neriousy Jan 20, 2026
11a5976
fix: semver version
neriousy Jan 20, 2026
0f282dc
refactor: npm -> package-registry
neriousy Jan 20, 2026
d04f4d8
fix: types semver ver
neriousy Jan 20, 2026
9ce1240
wip
neriousy Jan 20, 2026
b9a2ff4
refactor
neriousy Jan 20, 2026
9d0a325
clean up
neriousy Jan 20, 2026
c37becb
Merge branch 'dev' into fix/slow-plugins
neriousy Jan 20, 2026
e6030de
mock test
neriousy Jan 21, 2026
caeaa38
fix: last one
neriousy Jan 21, 2026
0b783cd
new lock file
neriousy Jan 21, 2026
19c2aa4
remove semver dependency
neriousy Jan 21, 2026
87e0c43
Merge branch 'dev' into fix/slow-plugins
neriousy Jan 21, 2026
3170262
Merge branch 'dev' into fix/slow-plugins
neriousy Jan 21, 2026
7bcf7ef
Merge branch 'dev' into fix/slow-plugins
neriousy Jan 21, 2026
0609a92
restore lock file from dev
neriousy Jan 21, 2026
b57788b
remove mocks
neriousy Jan 26, 2026
3169695
revert changes
neriousy Jan 26, 2026
3fb7de1
fix: plugin package reinstall
neriousy Jan 26, 2026
ef0767e
Merge branch 'dev' into fix/slow-plugins
neriousy Jan 26, 2026
ac56535
remove dead codE
neriousy Jan 26, 2026
83f833c
review: refactor
neriousy Jan 26, 2026
efd1649
tests: increase time-out
neriousy Feb 3, 2026
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
18 changes: 17 additions & 1 deletion packages/opencode/src/bun/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { NamedError } from "@opencode-ai/util/error"
import { readableStreamToText } from "bun"
import { createRequire } from "module"
import { Lock } from "../util/lock"
import { Npm } from "../npm"

export namespace BunProc {
const log = Log.create({ service: "bun" })
Expand Down Expand Up @@ -75,7 +76,22 @@ export namespace BunProc {
const dependencies = parsed.dependencies ?? {}
if (!parsed.dependencies) parsed.dependencies = dependencies
const modExists = await Filesystem.exists(mod)
if (dependencies[pkg] === version && modExists) return mod
const cachedVersion = dependencies[pkg]

if (modExists && cachedVersion) {
Comment thread
neriousy marked this conversation as resolved.
Outdated
if (version !== "latest") {
if (cachedVersion === version) return mod
}

if (version === "latest") {
const isOutdated = await Npm.isOutdated(pkg, cachedVersion)
if (!isOutdated) return mod
log.info("Cached version is outdated, proceeding with install", {
pkg,
cachedVersion,
})
}
}

const proxied = !!(
process.env.HTTP_PROXY ||
Expand Down
46 changes: 37 additions & 9 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { existsSync } from "fs"
import { Bus } from "@/bus"
import { GlobalBus } from "@/bus/global"
import { Event } from "../server/event"
import { Npm } from "../npm"

export namespace Config {
const log = Log.create({ service: "config" })
Expand Down Expand Up @@ -132,9 +133,10 @@ export namespace Config {
}
}

const exists = existsSync(path.join(dir, "node_modules"))
const installing = installDependencies(dir)
if (!exists) await installing
const shouldInstall = await needsInstall(dir)
if (shouldInstall) {
await installDependencies(dir)
}

result.command = mergeDeep(result.command ?? {}, await loadCommand(dir))
result.agent = mergeDeep(result.agent, await loadAgent(dir))
Expand Down Expand Up @@ -197,6 +199,7 @@ export namespace Config {

export async function installDependencies(dir: string) {
const pkg = path.join(dir, "package.json")
const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION

if (!(await Bun.file(pkg).exists())) {
await Bun.write(pkg, "{}")
Expand All @@ -206,18 +209,43 @@ export namespace Config {
const hasGitIgnore = await Bun.file(gitignore).exists()
if (!hasGitIgnore) await Bun.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n"))

await BunProc.run(
["add", "@opencode-ai/plugin@" + (Installation.isLocal() ? "latest" : Installation.VERSION), "--exact"],
{
cwd: dir,
},
).catch(() => {})
await BunProc.run(["add", `@opencode-ai/plugin@${targetVersion}`, "--exact"], {
cwd: dir,
}).catch(() => {})

// Install any additional dependencies defined in the package.json
// This allows local plugins and custom tools to use external packages
await BunProc.run(["install"], { cwd: dir }).catch(() => {})
}

async function needsInstall(dir: string) {
const nodeModules = path.join(dir, "node_modules")
if (!existsSync(nodeModules)) return true

const pkg = path.join(dir, "package.json")
const pkgFile = Bun.file(pkg)
const pkgExists = await pkgFile.exists()
if (!pkgExists) return true

const parsed = await pkgFile.json().catch(() => null)
const dependencies = parsed?.dependencies ?? {}
const depVersion = dependencies["@opencode-ai/plugin"]
if (!depVersion) return true

const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION
if (targetVersion === "latest") {
const isOutdated = await Npm.isOutdated("@opencode-ai/plugin", depVersion)
if (!isOutdated) return false
log.info("Cached version is outdated, proceeding with install", {
pkg: "@opencode-ai/plugin",
cachedVersion: depVersion,
})
return true
}
if (depVersion === targetVersion) return false
return true
}

function rel(item: string, patterns: string[]) {
for (const pattern of patterns) {
const index = item.indexOf(pattern)
Expand Down
53 changes: 53 additions & 0 deletions packages/opencode/src/npm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Log } from "../util/log"

export namespace Npm {
const log = Log.create({ service: "npm" })

export async function getLatestVersion(pkg: string): Promise<string | null> {
const encoded = pkg.replace("/", "%2F")
const url = `https://registry.npmjs.org/${encoded}/latest`
Comment thread
neriousy marked this conversation as resolved.
Outdated
const response = await fetch(url, {
headers: {
Accept: "application/json",
},
}).catch(() => null)

if (!response) {
log.warn("Failed to fetch latest version from registry", { pkg })
return null
}

if (!response.ok) {
log.warn("Failed to fetch latest version from registry", { pkg, status: response.status })
return null
}

const data = (await response.json().catch(() => null)) as { version?: string } | null
if (!data?.version) return null
return data.version
}

export function compareVersions(left: string, right: string): number {
const leftParts = left.split(".").map((part) => parseInt(part, 10) || 0)
const rightParts = right.split(".").map((part) => parseInt(part, 10) || 0)
const length = Math.max(leftParts.length, rightParts.length)

for (let i = 0; i < length; i++) {
const leftValue = leftParts[i] ?? 0
const rightValue = rightParts[i] ?? 0
if (leftValue < rightValue) return -1
if (leftValue > rightValue) return 1
}

return 0
}

export async function isOutdated(pkg: string, cachedVersion: string): Promise<boolean> {
const latestVersion = await getLatestVersion(pkg)
if (!latestVersion) {
log.warn("Failed to resolve latest version, using cached", { pkg, cachedVersion })
return false
}
return compareVersions(cachedVersion, latestVersion) < 0
}
}
Loading