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
26 changes: 16 additions & 10 deletions packages/opencode/src/bun/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,24 @@ export namespace BunProc {
}),
)

export async function install(pkg: string, version = "latest") {
export async function install(pkg: string, version?: string) {
// Use lock to ensure only one install at a time
using _ = await Lock.write("bun-install")

const isGithub = pkg.startsWith("github:")
// GitHub: default to "main" when no version; npm: default to "latest"
const pkgVersion = version ?? (isGithub ? "main" : "latest")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const pkgVersion = version ?? (isGithub ? "main" : "latest")

Can u actually do main?

It looks like bun does supports version being empty for the git ones

// Use # for GitHub, @ for npm
const pkgArg = isGithub ? `${pkg}#${pkgVersion}` : `${pkg}@${pkgVersion}`

const mod = path.join(Global.Path.cache, "node_modules", pkg)
const pkgjson = Bun.file(path.join(Global.Path.cache, "package.json"))
const parsed = await pkgjson.json().catch(async () => {
const result = { dependencies: {} }
await Bun.write(pkgjson.name!, JSON.stringify(result, null, 2))
return result
})
if (parsed.dependencies[pkg] === version) return mod
if (parsed.dependencies[pkg] === pkgVersion) return mod

const proxied = !!(
process.env.HTTP_PROXY ||
Expand All @@ -89,7 +95,7 @@ export namespace BunProc {
...(proxied ? ["--no-cache"] : []),
"--cwd",
Global.Path.cache,
pkg + "@" + version,
pkgArg,
]

// Let Bun handle registry resolution:
Expand All @@ -98,32 +104,32 @@ export namespace BunProc {
// - No need to pass --registry flag
log.info("installing package using Bun's default registry resolution", {
pkg,
version,
version: pkgVersion,
})

await BunProc.run(args, {
cwd: Global.Path.cache,
}).catch((e) => {
throw new InstallFailedError(
{ pkg, version },
{ pkg, version: pkgVersion },
{
cause: e,
},
)
})

// Resolve actual version from installed package when using "latest"
// For npm packages with "latest", resolve to actual version from installed package
// This ensures subsequent starts use the cached version until explicitly updated
let resolvedVersion = version
if (version === "latest") {
let finalVersion = pkgVersion
if (version === undefined && !isGithub) {
const installedPkgJson = Bun.file(path.join(mod, "package.json"))
const installedPkg = await installedPkgJson.json().catch(() => null)
if (installedPkg?.version) {
resolvedVersion = installedPkg.version
finalVersion = installedPkg.version
}
}

parsed.dependencies[pkg] = resolvedVersion
parsed.dependencies[pkg] = finalVersion
await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2))
return mod
}
Expand Down
43 changes: 42 additions & 1 deletion packages/opencode/test/bun.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,52 @@ describe("BunProc registry configuration", () => {
expect(installFunction).toContain('"--exact"')
expect(installFunction).toContain('"--cwd"')
expect(installFunction).toContain("Global.Path.cache")
expect(installFunction).toContain('pkg + "@" + version')

// Verify package argument structure - supports both regular and GitHub URLs
// Regular packages: pkg@version
// GitHub URLs: github:user/repo#version
expect(installFunction).toContain("isGithub")
expect(installFunction).toContain("#")
expect(installFunction).toContain("github:")

// Verify no registry argument is added
expect(installFunction).not.toContain('"--registry"')
expect(installFunction).not.toContain('args.push("--registry')
}
})

test("should support GitHub URLs with # version separator", async () => {
// Read the bun/index.ts file
const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
const content = await fs.readFile(bunIndexPath, "utf-8")

// Extract the install function
const installFunctionMatch = content.match(/export async function install[\s\S]*?^ }/m)
expect(installFunctionMatch).toBeTruthy()

if (installFunctionMatch) {
const installFunction = installFunctionMatch[0]

// Verify GitHub URLs use # for version separator
expect(installFunction).toContain("github:")
expect(installFunction).toContain("#${pkgVersion}")
}
})

test("should default to main branch for GitHub, latest for npm when no version specified", async () => {
// Read the bun/index.ts file
const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
const content = await fs.readFile(bunIndexPath, "utf-8")

// Extract the install function
const installFunctionMatch = content.match(/export async function install[\s\S]*?^ }/m)
expect(installFunctionMatch).toBeTruthy()

if (installFunctionMatch) {
const installFunction = installFunctionMatch[0]

// Verify version resolution
expect(installFunction).toContain('version ?? (isGithub ? "main" : "latest")')
}
})
})
25 changes: 24 additions & 1 deletion packages/web/src/content/docs/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ For examples, check out the [plugins](/docs/ecosystem#plugins) created by the co

## Use a plugin

There are two ways to load plugins.
There are three ways to load plugins.

---

Expand Down Expand Up @@ -39,6 +39,29 @@ Specify npm packages in your config file.

Both regular and scoped npm packages are supported.

---

### From GitHub

Install plugins directly from GitHub repositories using the `github:` prefix.

```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["github:eznix86/myfancyplugin#main"]
}
```

The format is `github:owner/repo#ref` where `ref` can be:

- A branch (e.g., `#main`, `#develop`)
- A tag (e.g., `#v1.0.0`)
- A commit hash (e.g., `#abc123def`)

If no ref is specified, it defaults to `#main`.

GitHub plugins are installed using Bun's [add-git](https://bun.com/docs/guides/install/add-git) command.

Browse available plugins in the [ecosystem](/docs/ecosystem#plugins).

---
Expand Down