From 231c58a74a75b80409a5ca84b0750da86d40419d Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Thu, 15 Oct 2015 14:48:30 -0700 Subject: [PATCH] deps: Allow partial and full disabling of flat trees PR-URL: https://github.com/npm/npm/pull/10337 Credit: @iarna Reviewed By: @othiym23 --- doc/cli/npm-install.md | 10 ++++ doc/misc/npm-config.md | 22 ++++++++ lib/config/defaults.js | 4 ++ lib/install/deps.js | 3 ++ test/tap/tree-style.js | 116 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 test/tap/tree-style.js diff --git a/doc/cli/npm-install.md b/doc/cli/npm-install.md index 75e9e13fd93..aeb2b23a316 100644 --- a/doc/cli/npm-install.md +++ b/doc/cli/npm-install.md @@ -257,6 +257,16 @@ local copy exists on disk. The `-g` or `--global` argument will cause npm to install the package globally rather than locally. See `npm-folders(5)`. +The `--global-style` argument will cause npm to install the package into +your local `node_modules` folder with the same layout it uses with the +global `node_modules` folder. Only your direct dependencies will show in +`node_modules` and everything they depend on will be flattened in their +`node_modules` folders. This obviously will elminate some deduping. + +The `--legacy-bundling` argument will cause npm to install the package such +that versions of npm prior to 1.4, such as the one included with node 0.8, +can install the package. This eliminates all automatic deduping. + The `--link` argument will cause npm to link global installs into the local space in some cases. diff --git a/doc/misc/npm-config.md b/doc/misc/npm-config.md index f88bf9f8a04..f4e24d76f82 100644 --- a/doc/misc/npm-config.md +++ b/doc/misc/npm-config.md @@ -385,6 +385,18 @@ Operates in "global" mode, so that packages are installed into the The config file to read for global config options. +### global-style + +* Default: false +* Type: Boolean + +Causes npm to install the package into your local `node_modules` folder with +the same layout it uses with the global `node_modules` folder. Only your +direct dependencies will show in `node_modules` and everything they depend +on will be flattened in their `node_modules` folders. This obviously will +elminate some deduping. If used with `legacy-bundling`, `legacy-bundling` will be +preferred. + ### group * Default: GID of the current process @@ -491,6 +503,16 @@ change. Only the output from `npm ls --json` is currently valid. A client key to pass when accessing the registry. +### legacy-bundling + +* Default: false +* Type: Boolean + +Causes npm to install the package such that versions of npm prior to 1.4, +such as the one included with node 0.8, can install the package. This +eliminates all automatic deduping. If used with `global-style` this option +will be preferred. + ### link * Default: false diff --git a/lib/config/defaults.js b/lib/config/defaults.js index ff56cb8e4e7..ee1e73851eb 100644 --- a/lib/config/defaults.js +++ b/lib/config/defaults.js @@ -145,6 +145,7 @@ Object.defineProperty(exports, 'defaults', {get: function () { global: false, globalconfig: path.resolve(globalPrefix, 'etc', 'npmrc'), + 'global-style': false, group: process.platform === 'win32' ? 0 : process.env.SUDO_GID || (process.getgid && process.getgid()), heading: 'npm', @@ -158,6 +159,7 @@ Object.defineProperty(exports, 'defaults', {get: function () { 'init-license': 'ISC', json: false, key: null, + 'legacy-bundling': false, link: false, 'local-address': undefined, loglevel: 'warn', @@ -251,6 +253,7 @@ exports.types = { 'git-tag-version': Boolean, global: Boolean, globalconfig: path, + 'global-style': Boolean, group: [Number, String], 'https-proxy': [null, url], 'user-agent': String, @@ -265,6 +268,7 @@ exports.types = { 'init-version': semver, json: Boolean, key: [null, String], + 'legacy-bundling': Boolean, link: Boolean, // local-address must be listed as an IP for a local network interface // must be IPv4 due to node bug diff --git a/lib/install/deps.js b/lib/install/deps.js index 5e8c33274d5..beb108e680f 100644 --- a/lib/install/deps.js +++ b/lib/install/deps.js @@ -577,5 +577,8 @@ var earliestInstallable = exports.earliestInstallable = function (requiredBy, tr if (!tree.parent) return tree if (tree.isGlobal) return tree + if (npm.config.get('global-style') && !tree.parent.parent) return tree + if (npm.config.get('legacy-bundling')) return tree + return (earliestInstallable(requiredBy, tree.parent, pkg) || tree) } diff --git a/test/tap/tree-style.js b/test/tap/tree-style.js new file mode 100644 index 00000000000..b2bf0ce709f --- /dev/null +++ b/test/tap/tree-style.js @@ -0,0 +1,116 @@ +'use strict' +var test = require('tap').test +var path = require('path') +var mkdirp = require('mkdirp') +var rimraf = require('rimraf') +var fs = require('graceful-fs') +var common = require('../common-tap') + +var base = path.resolve(__dirname, path.basename(__filename, '.js')) +var modA = path.resolve(base, 'modA') +var modB = path.resolve(base, 'modB') +var modC = path.resolve(base, 'modC') +var testNormal = path.resolve(base, 'testNormal') +var testGlobal = path.resolve(base, 'testGlobal') +var testLegacy = path.resolve(base, 'testLegacy') + +var json = { + 'name': 'test-tree-style', + 'version': '1.0.0', + 'dependencies': { + 'modA': modA + } +} + +var modAJson = { + 'name': 'modA', + 'version': '1.0.0', + 'dependencies': { + 'modB': modB + } +} + +var modBJson = { + 'name': 'modB', + 'version': '1.0.0', + 'dependencies': { + 'modC': modC + } +} + +var modCJson = { + 'name': 'modC', + 'version': '1.0.0' +} + +function modJoin () { + var modules = Array.prototype.slice.call(arguments) + return modules.reduce(function (a, b) { + return path.resolve(a, 'node_modules', b) + }) +} + +function writeJson (mod, data) { + fs.writeFileSync(path.resolve(mod, 'package.json'), JSON.stringify(data)) +} + +function setup () { + cleanup() + ;[modA, modB, modC, testNormal, testGlobal, testLegacy].forEach(function (mod) { + mkdirp.sync(mod) + }) + writeJson(modA, modAJson) + writeJson(modB, modBJson) + writeJson(modC, modCJson) + ;[testNormal, testGlobal, testLegacy].forEach(function (mod) { writeJson(mod, json) }) +} + +function cleanup () { + rimraf.sync(base) +} + +test('setup', function (t) { + setup() + t.end() +}) + +function exists (t, filepath, msg) { + try { + fs.statSync(filepath) + t.pass(msg) + return true + } catch (ex) { + t.fail(msg, {found: null, wanted: 'exists', compare: 'fs.stat(' + filepath + ')'}) + return false + } +} + +test('tree-style', function (t) { + t.plan(12) + common.npm(['install'], {cwd: testNormal}, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'normal install; result code') + t.is(stderr, '', 'normal install; no errors') + exists(t, modJoin(testNormal, 'modA'), 'normal install; module A') + exists(t, modJoin(testNormal, 'modB'), 'normal install; module B') + exists(t, modJoin(testNormal, 'modC'), 'normal install; module C') + }) + common.npm(['install', '--global-style'], {cwd: testGlobal}, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'global-style install; result code') + t.is(stderr, '', 'global-style install; no errors') + exists(t, modJoin(testGlobal, 'modA', 'modB'), 'global-style install; module B') + exists(t, modJoin(testGlobal, 'modA', 'modC'), 'global-style install; module C') + }) + common.npm(['install', '--legacy-bundling'], {cwd: testLegacy}, function (err, code, stdout, stderr) { + if (err) throw err + t.is(code, 0, 'legacy-bundling install; result code') + t.is(stderr, '', 'legacy-bundling install; no errors') + exists(t, modJoin(testLegacy, 'modA', 'modB', 'modC'), 'legacy-bundling install; module C') + }) +}) + +test('cleanup', function (t) { + cleanup() + t.end() +})