Skip to content

Commit 742dbce

Browse files
authored
Merge pull request #602 from webdevium/master
Added --engines-node functionality
2 parents 3598bdf + a67d8a3 commit 742dbce

File tree

5 files changed

+195
-53
lines changed

5 files changed

+195
-53
lines changed

bin/ncu

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ program
2323
.option('--cwd <path>', 'Used as current working directory for `spawn` in npm listing')
2424
.option('--dep <dep>', 'check only a specific section(s) of dependencies: prod|dev|peer|optional|bundle (comma-delimited)')
2525
.option('-e, --error-level <n>', 'set the error-level. 1: exits with error code 0 if no errors occur. 2: exits with error code 0 if no packages need updating (useful for continuous integration). Default is 1.', cint.partialAt(parseInt, 1, 10), 1)
26+
.option('--engines-node', 'upgrade to version which satisfies engines.node range')
2627
.option('-f, --filter <matches>', 'include only package names matching the given string, comma-or-space-delimited list, or /regex/')
2728
.option('-g, --global', 'check global packages instead of in the current project')
2829
// program.json is set to true in programInit if any options that begin with 'json' are true

lib/npm-check-updates.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ function analyzeProjectDependencies(options, pkgData, pkgFile) {
188188

189189
print(options, `Fetching ${vm.getVersionTarget(options)} versions...`, 'verbose');
190190

191+
if (options.enginesNode) {
192+
options.enginesNode = _.get(pkg, 'engines.node');
193+
}
194+
191195
return vm.upgradePackageDefinitions(current, options).then(async ([upgraded, latest]) => {
192196
const {newPkgData, selectedNewDependencies} = await vm.upgradePackageData(pkgData, current, upgraded, latest, options);
193197

lib/package-managers/npm.js

Lines changed: 106 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const versionUtil = require('../version-util.js');
66
const spawn = require('spawn-please');
77
const pacote = require('pacote');
88

9+
const TIME_FIELDS = ['modified', 'created'];
10+
911
// needed until pacote supports full npm config compatibility
1012
// See: https://github.com/zkat/pacote/issues/156
1113
const npmConfig = {};
@@ -46,45 +48,81 @@ function parseJson(result, data) {
4648
* @param {string} packageName Name of the package
4749
* @param {string} field Field such as "versions" or "dist-tags.latest" are parsed from the pacote result (https://www.npmjs.com/package/pacote#packument)
4850
* @param {string} currentVersion
49-
* @returns {Promise} Promised result
51+
* @returns {Promise} Promised result
5052
*/
51-
function view(packageName, field, currentVersion) {
53+
function viewOne(packageName, field, currentVersion) {
54+
return viewMany(packageName, [field], currentVersion)
55+
.then(result => {
56+
return result && result[field];
57+
});
58+
}
59+
60+
/**
61+
* @param {string} packageName Name of the package
62+
* @param {string[]} fields Array of fields like versions, time, version
63+
* @param {string} currentVersion
64+
* @returns {Promise} Promised result
65+
*/
66+
function viewMany(packageName, fields, currentVersion) {
5267
if (currentVersion && (!semver.validRange(currentVersion) || versionUtil.isWildCard(currentVersion))) {
5368
return Promise.resolve();
5469
}
5570

56-
npmConfig['full-metadata'] = field === 'time';
71+
npmConfig['full-metadata'] = _.includes(fields, 'time');
5772

5873
return pacote.packument(packageName, npmConfig).then(result => {
59-
if (field.startsWith('dist-tags.')) {
60-
const [tagName, version] = field.split('.');
61-
if (result[tagName]) {
62-
return result[tagName][version];
74+
const resultObject = {};
75+
_.each(fields, (field) => {
76+
if (field.startsWith('dist-tags.')) {
77+
const [tagName, version] = field.split('.');
78+
if (result[tagName]) {
79+
resultObject[field] = result.versions[result[tagName][version]];
80+
}
81+
} else {
82+
resultObject[field] = result[field];
6383
}
64-
} else if (field === 'versions') {
65-
return Object.keys(result[field]);
66-
} else {
67-
return result[field];
68-
}
84+
});
85+
return resultObject;
6986
});
7087
}
7188

7289
/**
7390
* @param {Array} versions Array of all available versions
91+
* @param {Boolean} pre Enabled prerelease?
7492
* @returns {Array} An array of versions with the release versions filtered out
7593
*/
76-
function filterOutPrereleaseVersions(versions) {
77-
return _.filter(versions, _.negate(isPre));
94+
function filterOutPrereleaseVersions(versions, pre) {
95+
return _.filter(versions, (version) => {
96+
return pre || _.negate(isPre)(version);
97+
});
7898
}
7999

80100
/**
81-
* @param version
82-
* @returns {boolean} True if the version is any kind of prerelease: alpha, beta, rc, pre
101+
* @param {String} version
102+
* @returns {boolean} True if the version is any kind of prerelease: alpha, beta, rc, pre
83103
*/
84104
function isPre(version) {
85105
return versionUtil.getPrecision(version) === 'release';
86106
}
87107

108+
/**
109+
* @param {{}} versions Object with all versions
110+
* @param {String} enginesNode Package engines.node range
111+
* @returns {Array} An array of versions which satisfies engines.node range
112+
*/
113+
function doesSatisfyEnginesNode(versions, enginesNode) {
114+
if (!enginesNode) {
115+
return _.keys(versions);
116+
}
117+
const minVersion = _.get(semver.minVersion(enginesNode), 'version');
118+
if (!minVersion) {
119+
return _.keys(versions);
120+
}
121+
return _.keys(versions).filter((version) => {
122+
let versionEnginesNode = _.get(versions[version], 'engines.node');
123+
return versionEnginesNode && semver.satisfies(minVersion, versionEnginesNode);
124+
});
125+
}
88126

89127
/**
90128
* Spawn npm requires a different command on Windows.
@@ -159,78 +197,97 @@ module.exports = {
159197
/**
160198
* @param {string} packageName
161199
* @param {string} currentVersion
162-
* @param {boolean} pre
200+
* @param {{}} options
163201
* @returns {Promise}
164202
*/
165-
latest(packageName, currentVersion, pre) {
166-
return view(packageName, 'dist-tags.latest', currentVersion)
167-
.then(version => {
168-
// if latest is not a prerelease version, return it
169-
// if latest is a prerelease version and --pre is specified, return it
170-
if (!isPre(version) || pre) {
171-
return version;
203+
latest(packageName, currentVersion, options) {
204+
return viewOne(packageName, 'dist-tags.latest', currentVersion)
205+
.then(latest => {
206+
// if latest exists and latest not satisfies min version of engines.node, set null to it
207+
if (latest && !doesSatisfyEnginesNode({[latest.version]: latest}, options.enginesNode).length) {
208+
latest = null;
209+
}
210+
// if latest exists and latest is not a prerelease version, return it
211+
// if latest exists and latest is a prerelease version and --pre is specified, return it
212+
if (latest && (!isPre(latest.version) || options.pre)) {
213+
return latest.version;
172214
// if latest is a prerelease version and --pre is not specified, find the next
173215
// version that is not a prerelease
174216
} else {
175-
return view(packageName, 'versions', currentVersion)
176-
.then(filterOutPrereleaseVersions)
177-
.then(_.last);
217+
return viewOne(packageName, 'versions', currentVersion)
218+
.then(versions => {
219+
versions = doesSatisfyEnginesNode(versions, options.enginesNode);
220+
return _.last(filterOutPrereleaseVersions(versions, options.pre));
221+
});
178222
}
179223
});
180224
},
181225

182226
/**
183227
* @param {string} packageName
184228
* @param {string} currentVersion
185-
* @param {boolean} pre
229+
* @param {{}} options
186230
* @returns {Promise}
187231
*/
188-
newest(packageName, currentVersion, pre) {
189-
return view(packageName, 'time', currentVersion)
190-
.then(_.keys)
191-
.then(_.partialRight(_.pullAll, ['modified', 'created']))
232+
newest(packageName, currentVersion, options) {
233+
return viewMany(packageName, ['time', 'versions'], currentVersion)
234+
.then((result) => {
235+
const versions = doesSatisfyEnginesNode(result.versions, options.enginesNode);
236+
_.keys(result.time).forEach((key) => {
237+
if (!_.includes(TIME_FIELDS, key) && !_.includes(versions, key)) {
238+
delete result.time[key];
239+
}
240+
});
241+
return _.keys(result.time);
242+
})
243+
.then(_.partialRight(_.pullAll, TIME_FIELDS))
192244
.then(versions => {
193-
return _.last(pre ? versions : filterOutPrereleaseVersions(versions));
245+
return _.last(filterOutPrereleaseVersions(versions, options.pre));
194246
});
195247
},
196248

197249
/**
198250
* @param {string} packageName
199251
* @param {string} currentVersion
200-
* @param {boolean} pre
252+
* @param {{}} options
201253
* @returns {Promise}
202254
*/
203-
greatest(packageName, currentVersion, pre) {
204-
return view(packageName, 'versions', currentVersion)
255+
greatest(packageName, currentVersion, options) {
256+
return viewOne(packageName, 'versions', currentVersion)
205257
.then(versions => {
206-
return _.last(pre ? versions : filterOutPrereleaseVersions(versions));
258+
versions = doesSatisfyEnginesNode(versions, options.enginesNode);
259+
return _.last(filterOutPrereleaseVersions(versions, options.pre));
207260
});
208261
},
209262

210263
/**
211264
* @param {string} packageName
212265
* @param {string} currentVersion
213-
* @param {boolean} pre
266+
* @param {{}} options
214267
* @returns {Promise}
215268
*/
216-
greatestMajor(packageName, currentVersion, pre) {
217-
return view(packageName, 'versions', currentVersion).then(versions => {
218-
const resultVersions = pre ? versions : filterOutPrereleaseVersions(versions);
219-
return versionUtil.findGreatestByLevel(resultVersions, currentVersion, 'major');
220-
});
269+
greatestMajor(packageName, currentVersion, options) {
270+
return viewOne(packageName, 'versions', currentVersion)
271+
.then(versions => {
272+
versions = doesSatisfyEnginesNode(versions, options.enginesNode);
273+
versions = filterOutPrereleaseVersions(versions, options.pre);
274+
return versionUtil.findGreatestByLevel(versions, currentVersion, 'major');
275+
});
221276
},
222277

223278
/**
224279
* @param {string} packageName
225280
* @param {string} currentVersion
226-
* @param {boolean} pre
281+
* @param {{}} options
227282
* @returns {Promise}
228283
*/
229-
greatestMinor(packageName, currentVersion, pre) {
230-
return view(packageName, 'versions', currentVersion).then(versions => {
231-
const resultVersions = pre ? versions : filterOutPrereleaseVersions(versions);
232-
return versionUtil.findGreatestByLevel(resultVersions, currentVersion, 'minor');
233-
});
284+
greatestMinor(packageName, currentVersion, options) {
285+
return viewOne(packageName, 'versions', currentVersion)
286+
.then(versions => {
287+
versions = doesSatisfyEnginesNode(versions, options.enginesNode);
288+
versions = filterOutPrereleaseVersions(versions, options.pre);
289+
return versionUtil.findGreatestByLevel(versions, currentVersion, 'minor');
290+
});
234291
},
235292

236293
defaultPrefix

lib/versionmanager.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ function upgradePackageDefinitions(currentDependencies, options) {
253253
pre: options.pre,
254254
packageManager: options.packageManager,
255255
json: options.json,
256-
loglevel: options.loglevel
256+
loglevel: options.loglevel,
257+
enginesNode: options.enginesNode
257258
}).then(latestVersions => {
258259

259260
const upgradedDependencies = upgradeDependencies(currentDependencies, latestVersions, {
@@ -422,7 +423,7 @@ function queryVersions(packageMap, options = {}) {
422423
* @returns {Promise}
423424
*/
424425
function getPackageVersionProtected(dep) {
425-
return getPackageVersion(dep, packageMap[dep], options.pre).catch(err => {
426+
return getPackageVersion(dep, packageMap[dep], options).catch(err => {
426427
if (err && (err.message || err).toString().match(/E404|ENOTFOUND|404 Not Found/i)) {
427428
return null;
428429
} else {

test/test-ncu.js

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('npm-check-updates', function () {
4646
]);
4747
});
4848

49-
it('should suggest upgrades to versions within the specified version range if jsonUpraded is true', () => {
49+
it('should suggest upgrades to versions within the specified version range if jsonUpgraded is true', () => {
5050
const upgraded = ncu.run({
5151
// juggernaut has been deprecated at v2.1.1 so it is unlikely to invalidate this test
5252
packageData: '{ "dependencies": { "juggernaut": "^2.1.0" } }',
@@ -61,7 +61,7 @@ describe('npm-check-updates', function () {
6161
]);
6262
});
6363

64-
it('should not suggest upgrades to versions within the specified version range if jsonUpraded is true and minimial is true', () => {
64+
it('should not suggest upgrades to versions within the specified version range if jsonUpgraded is true and minimial is true', () => {
6565
const upgraded = ncu.run({
6666
// juggernaut has been deprecated at v2.1.1 so it is unlikely to invalidate this test
6767
packageData: '{ "dependencies": { "juggernaut": "^2.1.0" } }',
@@ -226,6 +226,85 @@ describe('npm-check-updates', function () {
226226
});
227227
});
228228

229+
it('should enable --engines-node matching ', () => {
230+
return ncu.run({
231+
jsonAll: true,
232+
packageData: JSON.stringify({
233+
dependencies: {
234+
'del': '3.0.0'
235+
},
236+
engines: {
237+
'node': '>=6'
238+
}
239+
}),
240+
enginesNode: true
241+
}).then(data => {
242+
return data.should.eql({
243+
dependencies: {
244+
'del': '4.1.1'
245+
},
246+
engines: {
247+
'node': '>=6'
248+
}
249+
});
250+
});
251+
});
252+
253+
it('should enable engines matching if --engines-node', () => {
254+
return ncu.run({
255+
jsonAll: true,
256+
packageData: JSON.stringify({
257+
dependencies: {
258+
'del': '3.0.0'
259+
},
260+
engines: {
261+
'node': '>=6'
262+
}
263+
}),
264+
enginesNode: true
265+
}).then(upgradedPkg => {
266+
upgradedPkg.should.have.property('dependencies');
267+
upgradedPkg.dependencies.should.have.property('del');
268+
upgradedPkg.dependencies.del.should.equal('4.1.1');
269+
});
270+
});
271+
272+
it('should enable engines matching if --engines-node, not update if matches not exists', () => {
273+
return ncu.run({
274+
jsonAll: true,
275+
packageData: JSON.stringify({
276+
dependencies: {
277+
'del': '3.0.0'
278+
},
279+
engines: {
280+
'node': '>=1'
281+
}
282+
}),
283+
enginesNode: true
284+
}).then(upgradedPkg => {
285+
upgradedPkg.should.have.property('dependencies');
286+
upgradedPkg.dependencies.should.have.property('del');
287+
upgradedPkg.dependencies.del.should.equal('3.0.0');
288+
});
289+
});
290+
291+
it('should enable engines matching if --engines-node, update to latest version if engines.node not exists', () => {
292+
return ncu.run({
293+
jsonAll: true,
294+
packageData: JSON.stringify({
295+
dependencies: {
296+
'del': '3.0.0'
297+
}
298+
}),
299+
enginesNode: true
300+
}).then(upgradedPkg => {
301+
upgradedPkg.should.have.property('dependencies');
302+
upgradedPkg.dependencies.should.have.property('del');
303+
upgradedPkg.dependencies.del.should.not.equal('3.0.0');
304+
upgradedPkg.dependencies.del.should.not.equal('4.1.1');
305+
});
306+
});
307+
229308
});
230309

231310
describe('cli', () => {

0 commit comments

Comments
 (0)