fix(plugin): install monorepo sub-plugin deps when not hoisted#1007
fix(plugin): install monorepo sub-plugin deps when not hoisted#1007Benjamin-eecs wants to merge 1 commit intojackwener:mainfrom
Conversation
7e553f8 to
c447cb1
Compare
There was a problem hiding this comment.
Pull request overview
Fixes monorepo sub-plugin runtime failures when dependencies aren’t hoisted by ensuring sub-plugins with production dependencies get their own install step during the monorepo post-install lifecycle.
Changes:
- Added
hasOwnDependencies(dir)to detect whether a sub-plugin declares productiondependencies. - Updated
postInstallMonorepoLifecycleto runnpm install --omit=devper sub-plugin when needed. - Updated/added tests for skipping sub-plugins without deps and installing for sub-plugins with deps.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/plugin.ts |
Adds dependency detection and conditionally installs deps per sub-plugin in monorepo installs. |
src/plugin.test.ts |
Updates the monorepo lifecycle tests and adds coverage for sub-plugins that declare deps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } catch { | ||
| return false; |
There was a problem hiding this comment.
hasOwnDependencies swallows JSON parse/read errors and returns false, which can cause sub-plugin installs to silently skip npm install even when a package.json exists but is malformed. That can lead to a successful install followed by a runtime "Cannot find package" error instead of failing early with a clear message. Consider surfacing a PluginError for invalid package.json, or conservatively treating parse failures as "has deps" so installDependencies runs and produces an actionable failure.
| } catch { | |
| return false; | |
| } catch (err) { | |
| throw new PluginError( | |
| `Invalid package.json in ${dir}: ${getErrorMessage(err)}`, | |
| 'Fix the plugin package.json so dependencies can be determined and installed correctly.', | |
| ); |
| function postInstallMonorepoLifecycle(repoDir: string, pluginDirs: string[]): void { | ||
| installDependencies(repoDir); | ||
| for (const pluginDir of pluginDirs) { | ||
| if (pluginDir !== repoDir && hasOwnDependencies(pluginDir)) { | ||
| installDependencies(pluginDir); | ||
| } | ||
| finalizePluginRuntime(pluginDir); |
There was a problem hiding this comment.
This will run npm install --omit=dev inside every sub-plugin that has a non-empty dependencies object. In large monorepos this can multiply install time and may repeatedly touch the workspace/root lockfile even when the root install already covers dependency resolution (e.g., npm workspaces/hoisting). Consider detecting a workspace/hoisting setup at the repo root and skipping per-sub-plugin installs in that case, or otherwise limiting redundant installs (e.g., only when the root has no workspaces and the sub-plugin lacks a usable node_modules).
| @@ -650,6 +650,26 @@ describe('postInstallMonorepoLifecycle', () => { | |||
| expect(npmCalls[0][2]).toMatchObject({ cwd: repoDir }); | |||
| expect(npmCalls.some(([, , opts]) => opts?.cwd === subDir)).toBe(false); | |||
| }); | |||
There was a problem hiding this comment.
The "skips sub-plugins without own dependencies" test currently doesn't create a package.json in subDir, so it can pass because the file is missing rather than because hasOwnDependencies correctly returns false for an existing package.json with empty/missing dependencies. To cover the intended behavior, write a minimal package.json for subDir that has no dependencies (or an empty object) and assert that no npm install runs in subDir.
Summary
Fixes #722.
Monorepo sub-plugins that declare their own production
dependencies(e.g.undici) fail at runtime when the monorepo root doesn't hoist those packages (no npm workspaces configured).This adds a per-sub-plugin
npm installfor any sub-plugin whosepackage.jsonhas non-emptydependencies. When the root already satisfies everything, this is a fast no-op.Changes
hasOwnDependencies(dir)to check if a sub-plugin has production depspostInstallMonorepoLifecyclenow callsinstallDependenciesper sub-plugin when neededTest plan
dependencies: { undici: "^8.0.0" }gets its ownnpm install