fix(cli): missing plugin deps cause TUI to black screen (#14432)

This commit is contained in:
Matt Silverlock
2026-02-20 07:39:15 -05:00
committed by GitHub
parent a04e4e81fb
commit 93615bef28
2 changed files with 29 additions and 22 deletions

View File

@@ -292,7 +292,9 @@ export namespace Config {
...(proxied() ? ["--no-cache"] : []), ...(proxied() ? ["--no-cache"] : []),
], ],
{ cwd: dir }, { cwd: dir },
).catch(() => {}) ).catch((err) => {
log.warn("failed to install dependencies", { dir, error: err })
})
} }
async function isWritable(dir: string) { async function isWritable(dir: string) {

View File

@@ -41,8 +41,10 @@ export namespace Plugin {
for (const plugin of INTERNAL_PLUGINS) { for (const plugin of INTERNAL_PLUGINS) {
log.info("loading internal plugin", { name: plugin.name }) log.info("loading internal plugin", { name: plugin.name })
const init = await plugin(input) const init = await plugin(input).catch((err) => {
hooks.push(init) log.error("failed to load internal plugin", { name: plugin.name, error: err })
})
if (init) hooks.push(init)
} }
let plugins = config.plugin ?? [] let plugins = config.plugin ?? []
@@ -59,37 +61,40 @@ export namespace Plugin {
const lastAtIndex = plugin.lastIndexOf("@") const lastAtIndex = plugin.lastIndexOf("@")
const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin
const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest" const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest"
const builtin = BUILTIN.some((x) => x.startsWith(pkg + "@"))
plugin = await BunProc.install(pkg, version).catch((err) => { plugin = await BunProc.install(pkg, version).catch((err) => {
if (!builtin) throw err const cause = err instanceof Error ? err.cause : err
const detail = cause instanceof Error ? cause.message : String(cause ?? err)
const message = err instanceof Error ? err.message : String(err) log.error("failed to install plugin", { pkg, version, error: detail })
log.error("failed to install builtin plugin", {
pkg,
version,
error: message,
})
Bus.publish(Session.Event.Error, { Bus.publish(Session.Event.Error, {
error: new NamedError.Unknown({ error: new NamedError.Unknown({
message: `Failed to install built-in plugin ${pkg}@${version}: ${message}`, message: `Failed to install plugin ${pkg}@${version}: ${detail}`,
}).toObject(), }).toObject(),
}) })
return "" return ""
}) })
if (!plugin) continue if (!plugin) continue
} }
const mod = await import(plugin)
// Prevent duplicate initialization when plugins export the same function // Prevent duplicate initialization when plugins export the same function
// as both a named export and default export (e.g., `export const X` and `export default X`). // as both a named export and default export (e.g., `export const X` and `export default X`).
// Object.entries(mod) would return both entries pointing to the same function reference. // Object.entries(mod) would return both entries pointing to the same function reference.
const seen = new Set<PluginInstance>() await import(plugin)
for (const [_name, fn] of Object.entries<PluginInstance>(mod)) { .then(async (mod) => {
if (seen.has(fn)) continue const seen = new Set<PluginInstance>()
seen.add(fn) for (const [_name, fn] of Object.entries<PluginInstance>(mod)) {
const init = await fn(input) if (seen.has(fn)) continue
hooks.push(init) seen.add(fn)
} hooks.push(await fn(input))
}
})
.catch((err) => {
const message = err instanceof Error ? err.message : String(err)
log.error("failed to load plugin", { path: plugin, error: message })
Bus.publish(Session.Event.Error, {
error: new NamedError.Unknown({
message: `Failed to load plugin ${plugin}: ${message}`,
}).toObject(),
})
})
} }
return { return {