fix(opencode): ship @opencode-ai/plugin as a runtime dependency

The compiled OpenCode tools import `tool` from `@opencode-ai/plugin/tool`
at module top level, and the hooks plugin imports those tools statically.
The package was only a devDependency, so production installs (OpenCode's
npm plugin cache, `npm install --omit=dev`) could not resolve it and the
whole plugin failed to load.

Move it to `dependencies` and add a build-opencode regression test that
fails when the built bundle imports a package not declared as a runtime
dependency.

Fixes #3543

(cherry picked from commit 106024070e)
This commit is contained in:
Gaurav Dubey authored and haelyra committed 2026-10-20 17:38:25 -04:00
1 parent 88871370c5
commit e4666abb01
3 files changed
+33 -34

No files matched your search

+31
View File
@@ -4,6 +4,7 @@
const assert = require("assert")
const fs = require("fs")
const { builtinModules } = require("module")
const os = require("os")
const path = require("path")
const { spawnSync } = require("child_process")
@@ -116,6 +117,36 @@ function main() {
assert.strictEqual(result.status, 0, result.stderr)
assert.ok(fs.existsSync(distEntry), ".opencode/dist/index.js should exist after build")
}],
["built OpenCode bundle only imports packages declared as runtime dependencies", () => {
// OpenCode installs npm plugins without devDependencies, so any package
// the compiled bundle imports at runtime must ship as a dependency.
const runtimeDeps = new Set([
...Object.keys(packageJson.dependencies || {}),
...Object.keys(packageJson.peerDependencies || {}),
])
const distDir = path.dirname(distEntry)
const imported = new Set()
const walk = (dir) => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const entryPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
walk(entryPath)
} else if (entry.name.endsWith(".js")) {
const source = fs.readFileSync(entryPath, "utf8")
for (const match of source.matchAll(/(?:\bfrom|\bimport)\s*\(?\s*["']([^"'.][^"']*)["']/g)) {
const specifier = match[1]
if (specifier.startsWith("node:") || builtinModules.includes(specifier)) continue
const parts = specifier.split("/")
imported.add(specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0])
}
}
}
}
walk(distDir)
assert.ok(imported.size > 0, "expected the OpenCode bundle to import at least one package")
const undeclared = [...imported].filter((name) => !runtimeDeps.has(name)).sort()
assert.deepStrictEqual(undeclared, [], `undeclared runtime imports: ${undeclared.join(", ")}`)
}],
["package.json declares a resolvable OpenCode plugin entry", () => {
assert.strictEqual(packageJson.main, ".opencode/dist/index.js")
assert.ok(packageJson.exports, "package.json must declare an exports map")