Skip to content

Commit 3b305ee

Browse files
committed
install: Only autoprune on install with lock file
1 parent 8c73405 commit 3b305ee

File tree

7 files changed

+185
-5
lines changed

7 files changed

+185
-5
lines changed

doc/cli/npm-install.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,8 @@ The `--no-shrinkwrap` argument, which will ignore an available
347347
package lock or shrinkwrap file and use the package.json instead.
348348

349349
The `--no-package-lock` argument will prevent npm from creating a
350-
`package-lock.json` file.
350+
`package-lock.json` file. When running with package-lock's disabled npm
351+
will not automatically prune your node modules when installing.
351352

352353
The `--nodedir=/path/to/node/source` argument will allow npm to find the
353354
node source code so that npm can compile native modules.

doc/cli/npm-prune.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ npm-prune(1) -- Remove extraneous packages
33

44
## SYNOPSIS
55

6-
npm prune [[<@scope>/]<pkg>...] [--production]
6+
npm prune [[<@scope>/]<pkg>...] [--production] [--dry-run] [--json]
77

88
## DESCRIPTION
99

@@ -16,9 +16,21 @@ package's dependencies list.
1616

1717
If the `--production` flag is specified or the `NODE_ENV` environment
1818
variable is set to `production`, this command will remove the packages
19-
specified in your `devDependencies`. Setting `--production=false` will
19+
specified in your `devDependencies`. Setting `--no-production` will
2020
negate `NODE_ENV` being set to `production`.
2121

22+
If the `--dry-run` flag is used then no changes will actually be made.
23+
24+
If the `--json` flag is used then the changes `npm prune` made (or would
25+
have made with `--dry-run`) are printed as a JSON object.
26+
27+
In normal operation with package-locks enabled, extraneous modules are
28+
pruned automatically when modules are installed and you'll only need
29+
this command with the `--production` flag.
30+
31+
If you've disabled package-locks then extraneous modules will not be removed
32+
and it's up to you to run `npm prune` from time-to-time to remove them.
33+
2234
## SEE ALSO
2335

2436
* npm-uninstall(1)

doc/misc/npm-config.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,10 @@ when publishing or changing package permissions with `npm access`.
731731
If set to false, then ignore `package-lock.json` files when installing. This
732732
will also prevent _writing_ `package-lock.json` if `save` is true.
733733

734+
When package package-locks are disabled, automatic pruning of extraneous
735+
modules will also be disabled. To remove extraneous modules with
736+
package-locks disabled use `npm prune`.
737+
734738
This option is an alias for `--shrinkwrap`.
735739

736740
### package-lock-only

lib/install.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ function Installer (where, dryrun, args, opts) {
220220
this.noPackageJsonOk = !!args.length
221221
this.topLevelLifecycles = !args.length
222222

223+
this.autoPrune = npm.config.get('package-lock')
224+
223225
const dev = npm.config.get('dev')
224226
const only = npm.config.get('only')
225227
const onlyProd = /^prod(uction)?$/.test(only)
@@ -436,8 +438,8 @@ Installer.prototype.pruneIdealTree = function (cb) {
436438
// if our lock file didn't have the requires field and there
437439
// are any fake children then forgo pruning until we have more info.
438440
if (!this.idealTree.hasRequiresFromLock && this.idealTree.children.some((n) => n.fakeChild)) return cb()
439-
var toPrune = this.idealTree.children
440-
.filter(isExtraneous)
441+
const toPrune = this.idealTree.children
442+
.filter((child) => isExtraneous(child) && (this.autoPrune || child.removing))
441443
.map((n) => ({name: moduleName(n)}))
442444
return removeExtraneous(toPrune, this.idealTree, cb)
443445
}

lib/install/deps.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,21 @@ exports.removeDeps = function (args, tree, saveToDependencies, next) {
332332
parent.requires = parent.requires.filter((child) => child !== pkgToRemove)
333333
}
334334
pkgToRemove.requiredBy = pkgToRemove.requiredBy.filter((parent) => parent !== tree)
335+
flagAsRemoving(pkgToRemove)
335336
}
336337
next()
337338
}
339+
340+
function flagAsRemoving (toRemove, seen) {
341+
if (!seen) seen = new Set()
342+
if (seen.has(toRemove)) return
343+
seen.add(toRemove)
344+
toRemove.removing = true
345+
toRemove.requires.forEach((required) => {
346+
flagAsRemoving(required, seen)
347+
})
348+
}
349+
338350
exports.removeExtraneous = function (args, tree, next) {
339351
for (let pkg of args) {
340352
var pkgName = moduleName(pkg)

lib/prune.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ function prune (args, cb) {
2626

2727
function Pruner (where, dryrun, args) {
2828
Installer.call(this, where, dryrun, args)
29+
this.autoPrune = true
2930
}
3031
util.inherits(Pruner, Installer)
3132

test/tap/auto-prune.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
'use strict'
2+
const path = require('path')
3+
const test = require('tap').test
4+
const mr = require('npm-registry-mock')
5+
const Tacks = require('tacks')
6+
const File = Tacks.File
7+
const Dir = Tacks.Dir
8+
const extend = Object.assign || require('util')._extend
9+
const common = require('../common-tap.js')
10+
11+
const basedir = path.join(__dirname, path.basename(__filename, '.js'))
12+
const testdir = path.join(basedir, 'testdir')
13+
const cachedir = path.join(basedir, 'cache')
14+
const globaldir = path.join(basedir, 'global')
15+
const tmpdir = path.join(basedir, 'tmp')
16+
17+
const conf = {
18+
cwd: testdir,
19+
env: extend(extend({}, process.env), {
20+
npm_config_cache: cachedir,
21+
npm_config_tmp: tmpdir,
22+
npm_config_prefix: globaldir,
23+
npm_config_registry: common.registry,
24+
npm_config_loglevel: 'warn'
25+
})
26+
}
27+
28+
let server
29+
const fixture = new Tacks(Dir({
30+
cache: Dir(),
31+
global: Dir(),
32+
tmp: Dir(),
33+
testdir: Dir({
34+
node_modules: Dir({
35+
minimist: Dir({
36+
'package.json': File({
37+
_integrity: 'sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=',
38+
_resolved: 'https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz',
39+
name: 'minimist',
40+
version: '0.0.8'
41+
})
42+
}),
43+
mkdirp: Dir({
44+
'package.json': File({
45+
_integrity: 'sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=',
46+
_resolved: 'https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz',
47+
dependencies: {
48+
minimist: '0.0.8'
49+
},
50+
name: 'mkdirp',
51+
version: '0.5.1'
52+
})
53+
}),
54+
null: Dir({
55+
'package.json': File({
56+
_integrity: 'sha1-WoIdUnAxMlyG06AasQFzKgkfoew=',
57+
_resolved: 'https://registry.npmjs.org/null/-/null-1.0.1.tgz',
58+
_spec: 'null',
59+
name: 'null',
60+
version: '1.0.1'
61+
})
62+
})
63+
}),
64+
'package-lock.json': File({
65+
name: 'with-lock',
66+
version: '1.0.0',
67+
lockfileVersion: 1,
68+
requires: true,
69+
dependencies: {
70+
minimist: {
71+
version: '0.0.8',
72+
resolved: 'https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz',
73+
integrity: 'sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0='
74+
},
75+
mkdirp: {
76+
version: '0.5.1',
77+
resolved: 'https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz',
78+
integrity: 'sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=',
79+
requires: {
80+
minimist: '0.0.8'
81+
}
82+
}
83+
}
84+
}),
85+
'package.json': File({
86+
name: 'with-lock',
87+
version: '1.0.0',
88+
dependencies: {
89+
mkdirp: '^0.5.1'
90+
}
91+
})
92+
})
93+
}))
94+
95+
function setup () {
96+
cleanup()
97+
fixture.create(basedir)
98+
}
99+
100+
function cleanup () {
101+
fixture.remove(basedir)
102+
}
103+
104+
test('setup', function (t) {
105+
setup()
106+
mr({port: common.port, throwOnUnmatched: true}, function (err, s) {
107+
if (err) throw err
108+
server = s
109+
t.done()
110+
})
111+
})
112+
113+
test('auto-prune w/ package-lock', function (t) {
114+
common.npm(['install', '--dry-run', '--json'], conf, function (err, code, stdout, stderr) {
115+
if (err) throw err
116+
t.is(code, 0, 'command ran ok')
117+
t.comment(stderr.trim())
118+
const result = JSON.parse(stdout)
119+
t.is(result.added.length, 0, 'nothing added')
120+
t.is(result.updated.length, 0, 'nothing updated')
121+
t.is(result.moved.length, 0, 'nothing moved')
122+
t.is(result.failed.length, 0, 'nothing failed')
123+
t.is(result.removed.length, 1, 'pruned 1')
124+
t.like(result, {'removed': [{'name': 'null'}]}, 'pruned the right one')
125+
t.done()
126+
})
127+
})
128+
129+
test('auto-prune w/ --no-package-lock', function (t) {
130+
common.npm(['install', '--dry-run', '--json', '--no-package-lock'], conf, function (err, code, stdout, stderr) {
131+
if (err) throw err
132+
t.is(code, 0, 'command ran ok')
133+
t.comment(stderr.trim())
134+
const result = JSON.parse(stdout)
135+
t.is(result.added.length, 0, 'nothing added')
136+
t.is(result.updated.length, 0, 'nothing updated')
137+
t.is(result.moved.length, 0, 'nothing moved')
138+
t.is(result.failed.length, 0, 'nothing failed')
139+
t.is(result.removed.length, 0, 'nothing pruned')
140+
t.done()
141+
})
142+
})
143+
144+
test('cleanup', function (t) {
145+
server.close()
146+
cleanup()
147+
t.done()
148+
})

0 commit comments

Comments
 (0)