Skip to content

Commit 2ba3a0f

Browse files
authored
install: add --before date support for time traveling~ (#90)
PR-URL: #90 Credit: @zkat Reviewed-By: @aeschright Reviewed-By: @iarna
1 parent baaedbc commit 2ba3a0f

File tree

4 files changed

+127
-2
lines changed

4 files changed

+127
-2
lines changed

doc/misc/npm-config.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,22 @@ a non-zero exit code.
179179

180180
What authentication strategy to use with `adduser`/`login`.
181181

182+
### before
183+
184+
* Alias: enjoy-by
185+
* Default: null
186+
* Type: Date
187+
188+
If passed to `npm install`, will rebuild the npm tree such that only versions
189+
that were available **on or before** the `--before` time get installed.
190+
If there's no versions available for the current set of direct dependencies, the
191+
command will error.
192+
193+
If the requested version is a `dist-tag` and the given tag does not pass the
194+
`--before` filter, the most recent version less than or equal to that tag will
195+
be used. For example, `foo@latest` might install `[email protected]` even though `latest`
196+
is `2.0`.
197+
182198
### bin-links
183199

184200
* Default: `true`

lib/config/defaults.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ Object.defineProperty(exports, 'defaults', {get: function () {
113113
'audit-level': 'low',
114114
'auth-type': 'legacy',
115115

116+
'before': null,
116117
'bin-links': true,
117118
browser: null,
118119

@@ -260,6 +261,7 @@ exports.types = {
260261
audit: Boolean,
261262
'audit-level': ['low', 'moderate', 'high', 'critical'],
262263
'auth-type': ['legacy', 'sso', 'saml', 'oauth'],
264+
'before': [null, Date],
263265
'bin-links': Boolean,
264266
browser: [null, String],
265267
ca: [null, String, Array],
@@ -394,6 +396,7 @@ function getLocalAddresses () {
394396
}
395397

396398
exports.shorthands = {
399+
before: ['--enjoy-by'],
397400
s: ['--loglevel', 'silent'],
398401
d: ['--loglevel', 'info'],
399402
dd: ['--loglevel', 'verbose'],

lib/install.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -703,8 +703,25 @@ Installer.prototype.cloneCurrentTreeToIdealTree = function (cb) {
703703
validate('F', arguments)
704704
log.silly('install', 'cloneCurrentTreeToIdealTree')
705705

706-
this.idealTree = copyTree(this.currentTree)
707-
this.idealTree.warnings = []
706+
if (npm.config.get('before')) {
707+
this.idealTree = {
708+
package: this.currentTree.package,
709+
path: this.currentTree.path,
710+
realpath: this.currentTree.realpath,
711+
children: [],
712+
requires: [],
713+
missingDeps: {},
714+
missingDevDeps: {},
715+
requiredBy: [],
716+
error: this.currentTree.error,
717+
warnings: [],
718+
isTop: true
719+
}
720+
} else {
721+
this.idealTree = copyTree(this.currentTree)
722+
this.idealTree.warnings = []
723+
}
724+
708725
cb()
709726
}
710727

test/tap/install-before.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict'
2+
3+
const BB = require('bluebird')
4+
5+
const common = require('../common-tap.js')
6+
const mockTar = require('../util/mock-tarball.js')
7+
const mr = common.fakeRegistry.compat
8+
const path = require('path')
9+
const rimraf = BB.promisify(require('rimraf'))
10+
const Tacks = require('tacks')
11+
const { test } = require('tap')
12+
13+
const { Dir, File } = Tacks
14+
15+
const testDir = path.join(__dirname, path.basename(__filename, '.js'))
16+
17+
let server
18+
test('setup', t => {
19+
mr({}, (err, s) => {
20+
t.ifError(err, 'registry mocked successfully')
21+
server = s
22+
t.end()
23+
})
24+
})
25+
26+
test('installs an npm package before a certain date', t => {
27+
const fixture = new Tacks(Dir({
28+
'package.json': File({})
29+
}))
30+
fixture.create(testDir)
31+
const packument = {
32+
name: 'foo',
33+
'dist-tags': { latest: '1.2.4' },
34+
versions: {
35+
'1.2.3': {
36+
name: 'foo',
37+
version: '1.2.3',
38+
dist: {
39+
tarball: `${server.registry}/foo/-/foo-1.2.3.tgz`
40+
}
41+
},
42+
'1.2.4': {
43+
name: 'foo',
44+
version: '1.2.4',
45+
dist: {
46+
tarball: `${server.registry}/foo/-/foo-1.2.4.tgz`
47+
}
48+
}
49+
},
50+
time: {
51+
created: '2017-01-01T00:00:01.000Z',
52+
modified: '2018-01-01T00:00:01.000Z',
53+
'1.2.3': '2017-01-01T00:00:01.000Z',
54+
'1.2.4': '2018-01-01T00:00:01.000Z'
55+
}
56+
}
57+
server.get('/foo').reply(200, packument)
58+
return mockTar({
59+
'package.json': JSON.stringify({
60+
name: 'foo',
61+
version: '1.2.3'
62+
})
63+
}).then(tarball => {
64+
server.get('/foo/-/foo-1.2.3.tgz').reply(200, tarball)
65+
server.get('/foo/-/foo-1.2.4.tgz').reply(500)
66+
return common.npm([
67+
'install', 'foo',
68+
'--before', '2018',
69+
'--json',
70+
'--cache', path.join(testDir, 'npmcache'),
71+
'--registry', server.registry
72+
], { cwd: testDir })
73+
}).then(([code, stdout, stderr]) => {
74+
t.comment(stdout)
75+
t.comment(stderr)
76+
t.like(JSON.parse(stdout), {
77+
added: [{
78+
action: 'add',
79+
name: 'foo',
80+
version: '1.2.3'
81+
}]
82+
}, 'installed the 2017 version of the package')
83+
})
84+
})
85+
86+
test('cleanup', t => {
87+
server.close()
88+
return rimraf(testDir)
89+
})

0 commit comments

Comments
 (0)