From d06ca51cf53d678ec95c7436704895eb3098598d Mon Sep 17 00:00:00 2001 From: Vadim Tomnikov Date: Mon, 29 Apr 2019 17:53:52 +0300 Subject: [PATCH 1/4] support monorepos allow usage of grunt plugins that are located in any location that is visible to Node.js and NPM, instead of `node_modules` directly inside package that have a dev dependency to these plugins. --- lib/grunt/task.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/grunt/task.js b/lib/grunt/task.js index 48aec3abf..8b116e1f9 100644 --- a/lib/grunt/task.js +++ b/lib/grunt/task.js @@ -369,8 +369,8 @@ task.loadTasks = function(tasksdir) { // relative to the base dir). task.loadNpmTasks = function(name) { loadTasksMessage('"' + name + '" local Npm module'); - var root = path.resolve('node_modules'); - var pkgfile = path.join(root, name, 'package.json'); + var pkgfile = require.resolve(path.join(name, 'package.json')); + var root = pkgfile.substr(0, pkgfile.length - name.length - path.sep.length - 'package.json'.length); var pkg = grunt.file.exists(pkgfile) ? grunt.file.readJSON(pkgfile) : {keywords: []}; // Process collection plugins. From f2b118aebd4be08ed5912b8188f48293662f5696 Mon Sep 17 00:00:00 2001 From: Vadim Tomnikov Date: Mon, 29 Apr 2019 23:38:54 +0300 Subject: [PATCH 2/4] support monorepo (as fallback) In `loadNpmTasks()`` try to resolve package location. In case package not found, try to resolve it's location using `require.resolve()` --- lib/grunt/task.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/grunt/task.js b/lib/grunt/task.js index 8b116e1f9..51cfae6c4 100644 --- a/lib/grunt/task.js +++ b/lib/grunt/task.js @@ -369,8 +369,12 @@ task.loadTasks = function(tasksdir) { // relative to the base dir). task.loadNpmTasks = function(name) { loadTasksMessage('"' + name + '" local Npm module'); - var pkgfile = require.resolve(path.join(name, 'package.json')); - var root = pkgfile.substr(0, pkgfile.length - name.length - path.sep.length - 'package.json'.length); + var root = path.resolve('node_modules'); + var pkgfile = path.join(root, name, 'package.json'); + if (!grunt.file.exists(pkgfile)) { + pkgfile = require.resolve(path.join(name, 'package.json')); + root = pkgfile.substr(0, pkgfile.length - name.length - path.sep.length - 'package.json'.length); + } var pkg = grunt.file.exists(pkgfile) ? grunt.file.readJSON(pkgfile) : {keywords: []}; // Process collection plugins. From 66fc8fa4409a1c46f3ff8ff60782eaa827c94328 Mon Sep 17 00:00:00 2001 From: Vadim Tomnikov Date: Mon, 29 Apr 2019 23:57:20 +0300 Subject: [PATCH 3/4] support monorepo (test case) add integration test for monorepo case, where grunt plugin hoisted to the `node_modules` above package having dev dependency to it --- .../fixtures/load-npm-tasks/test-package/package.json | 7 +++++++ test/gruntfile/load-npm-tasks.js | 11 +++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/load-npm-tasks/test-package/package.json diff --git a/test/fixtures/load-npm-tasks/test-package/package.json b/test/fixtures/load-npm-tasks/test-package/package.json new file mode 100644 index 000000000..8de3ba72d --- /dev/null +++ b/test/fixtures/load-npm-tasks/test-package/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "name": "test-package", + "devDependencies": { + "grunt-foo-plugin": "1.0.0" + } +} \ No newline at end of file diff --git a/test/gruntfile/load-npm-tasks.js b/test/gruntfile/load-npm-tasks.js index 078b3155a..c8e3c04d4 100644 --- a/test/gruntfile/load-npm-tasks.js +++ b/test/gruntfile/load-npm-tasks.js @@ -4,8 +4,8 @@ var Log = require('grunt-legacy-log').Log; var assert = require('assert'); var through = require('through2'); -module.exports = function(grunt) { - grunt.file.setBase('../fixtures/load-npm-tasks'); +function test(grunt, fixture) { + grunt.file.setBase('../fixtures/' + fixture); // Create a custom log to assert output var stdout = []; @@ -39,4 +39,11 @@ module.exports = function(grunt) { throw err; } }); +} + +module.exports = function(grunt) { + // NPM task package is inside $CWD/node_modules + test(grunt, 'load-npm-tasks'); + // NPM task package hoisted to $CWD/../node_modules + test(grunt, 'load-npm-tasks/test-package'); }; From 5a0a02b6a4d4728d67a0e7bffa1868741c0ba17f Mon Sep 17 00:00:00 2001 From: Vadim Tomnikov Date: Sun, 26 May 2019 09:53:15 +0300 Subject: [PATCH 4/4] support monorepo (relative path) In case `loadNpmTasks()` is called with relative path instead of just module name, try to resolve module name and find relevant package.json, but use tasks from specified relative path. --- lib/grunt/task.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/grunt/task.js b/lib/grunt/task.js index 51cfae6c4..ffd119eb0 100644 --- a/lib/grunt/task.js +++ b/lib/grunt/task.js @@ -370,10 +370,22 @@ task.loadTasks = function(tasksdir) { task.loadNpmTasks = function(name) { loadTasksMessage('"' + name + '" local Npm module'); var root = path.resolve('node_modules'); - var pkgfile = path.join(root, name, 'package.json'); - if (!grunt.file.exists(pkgfile)) { - pkgfile = require.resolve(path.join(name, 'package.json')); - root = pkgfile.substr(0, pkgfile.length - name.length - path.sep.length - 'package.json'.length); + var pkgpath = path.join(root, name); + var pkgfile = path.join(pkgpath, 'package.json'); + // If package does not exist where grunt expects it to be, + // try to find it using Node's package path resolution mechanism + if (!grunt.file.exists(pkgpath)) { + var nameParts = name.split('/'); + // In case name points to directory inside module, + // get real name of the module with respect to scope (if any) + var normailzedName = (name[0] === '@' ? nameParts.slice(0,2).join('/') : nameParts[0]); + try { + pkgfile = require.resolve(normailzedName + '/package.json'); + root = pkgfile.substr(0, pkgfile.length - normailzedName.length - '/package.json'.length); + } catch (err) { + grunt.log.error('Local Npm module "' + normailzedName + '" not found. Is it installed?'); + return; + } } var pkg = grunt.file.exists(pkgfile) ? grunt.file.readJSON(pkgfile) : {keywords: []};