diff --git a/src/servers/plugins/npm-deps.ts b/src/servers/plugins/npm-deps.ts index 6f1abfea..7d474bc2 100644 --- a/src/servers/plugins/npm-deps.ts +++ b/src/servers/plugins/npm-deps.ts @@ -181,12 +181,31 @@ const body = { export function unresolvableDependencies(plugin: DiscoveredPlugin): string[] { const names = Object.keys(plugin.manifest.npmDependencies ?? {}); return names.filter((name) => { + // The strong check: it imports. This is what catches a package that installed but cannot be loaded + // on this machine — `sharp` resolving to a platform-specific binary is the shape it was written for. try { Bun.resolveSync(name, plugin.dir); return false; } catch { - return true; + // fall through } + + /* + Some packages have NO root entry point, and that is not a fault. + + `@tiptap/pm` declares no `main`, no `module`, and an `exports` map with no `"."` — it exists only as + subpaths, and you import `@tiptap/pm/state`, never `@tiptap/pm`. Asked to resolve the bare name, + Bun correctly says it cannot, and the check above read that as a broken install. + + It cost a deploy: fofos-admin declares `@tiptap/pm` because tiptap needs it present, the install + was perfect — the directory, its dist and thirteen prosemirror packages all there — and the plugin + was reported as failed on every host that had it. + + So a package that does not resolve is asked the weaker question instead: is it THERE. Present but + unimportable still fails, because the strong check ran first and only a package with no root entry + reaches this line. + */ + return !existsSync(join(PLUGINS_DIR, 'node_modules', name, 'package.json')); }); }