loadNpmTasks fails to find task plugins when working with monorepo symlinks
- Dominant language
- JavaScript
- Stars
- 12.2k
- Forks
- 1.5k
- PR merge metrics
- No merged PRs in 30d
Description
When a dependency has been symlinked into a monorepo application, and this dependency is hoisted to the monorepo root, the dependency's `tasks/` folder cannot be located by `grunt.loadNpmTasks` due to an incorrect assumption made when determining the root filepath.
The core issue is that the `name` parameter that `loadNpmTasks` is invoked with is not required to be a valid path part due to symlinks. Consider the example directory layout:
```
dev/
corp-utils-monorepo/
applications/
neato-task-plugins/
tasks/
cool-task.js
package.json
// other stuff from the company
my-team-monorepo/
packages/
team-app-1/
Gruntfile.js
package.json
// other monorepo projects that also use the neato-task-plugins package
```
As a Good Company, scopes are used to guard internal packages from dependency squatting, and thus `neato-task-plugins` is published internally with the `@mycorp` scope. Its tasks are loaded from the `team-app-1` package Gruntfile with `grunt.loadNpmTasks('@mycorp/neato-task-plugins');`
All is well, until one tries to develop a new corp-wide task and symlinks the task package by running e.g. `yarn link '@mycorp/neato-task-plugins'` from the `my-team-monorepo` directory.
Now, running the previously working grunt tasks fail with an error logged from here:
https://github.com/gruntjs/grunt/blob/ee722d15ed214c824d2925d04afef10f217338c3/lib/grunt/task.js#L412-L417
e.g. `>> Local Npm module "@mycorp/neato-task-plugins" not found. Is it installed?`
And caused by the setting of `root` here:
https://github.com/gruntjs/grunt/blob/ee722d15ed214c824d2925d04afef10f217338c3/lib/grunt/task.js#L384
Where the variables have values like:
```js
pkgfile === '/Users/me/dev/corp-utils-monorepo/applications/neato-task-plugins/package.json';
normailzedName === '@mycorp/neato-task-plugins';
// root === '/Users/me/dev/corp-utils-monorepo/appli'
```
However, we even after we fix the calculation of `root`, e.g. by doing something like slicing off 2 (or 3 for scoped packages) path elements, the code still fails to compute `tasksdir` correctly, looking for
`/Users/me/dev/corp-utils-monorepo/applications/@mycorp/neato-task-plugins/tasks` instead of
`/Users/me/dev/corp-utils-monorepo/applications/neato-task-plugins/tasks`.
I think the best solution is to use relative paths from the resolved `pkgfile`, rather than attempt to use `path.join` with varied arguments of `root`, `name`, or `node_modules`:
```js
var tasksdir = path.join(path.dirname(pkgfile), 'tasks');
```
I'm not sure what the behavior should be for "collection plugins" that are symlinked, as I'm not familiar with those.
Contributor guide
Research direction
Start in lib/grunt/task.js around the root calculation at lines 384 and the missing-module error at lines 412-417. Reproduce loading a scoped, symlinked plugin and trace the resolved pkgfile; done means its tasks/ directory is found without breaking collection-plugin behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100