Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ Update "@angular/common" in package.json from 2.4.8 to 2.4.10? (Use arrow keys)
* `Ignore` will add this module to the ignored list (see details in [`Ignoring module`](#ignoring-module) section below).
* `Finish update process` will ...hm... finish update process and save all the changes to `package.json`.

> [!NOTE]
> By default, `npm-upgrade` will show warnings for modules that have recent updates (by default, within the last 3 days). You can change this behavior by modifying `config.recentUpdates` in your `~/.npm-upgrade/config.json` file. For example, you can set it to `{"info": "1d", "warning": "2d", "caution": "3d"}` to show warnings for modules updated within the last 1 day, 2 days, and 3 days respectively.

A note on saving changes to `package.json`: when you choose `Yes` to update some module's version, `package.json` won't be immediately updated. It will be updated only after you will process all the outdated modules and confirm update **or** when you choose `Finish update process`. So if in the middle of the update process you've changed your mind just press `Ctrl+C` and `package.json` will remain untouched.

If you want to check only some deps, you can use `filter` argument:
Expand Down
6 changes: 6 additions & 0 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,9 @@ function cleanDeep(obj) {

return obj;
}

export const RECENT_UPDATES_DEFAULT = {
info: '3d',
warning: '2d',
caution: '1d'
};
56 changes: 35 additions & 21 deletions src/commands/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import {colorizeDiff} from 'npm-check-updates/lib/version-util';
import catchAsyncError from '../catchAsyncError';
import {makeFilterFunction} from '../filterUtils';
import {DEPS_GROUPS, loadGlobalPackages, loadPackageJson, setModuleVersion,
getModuleInfo, getModuleHomepage, getVersionPublicationDate} from '../packageUtils';
getModuleInfo, getModuleHomepage, getVersionPublicationDate,
getModuleVersions} from '../packageUtils';
import {fetchRemoteDb, findModuleChangelogUrl} from '../changelogUtils';
import {createSimpleTable} from '../cliTable';
import {strong, success, attention, upgradeCaution, upgradeWarning, upgradeInfo} from '../cliStyles';
import askUser from '../askUser';
import {toSentence, toTimespan} from '../stringUtils';
import {askIgnoreFields} from './ignore';
import Config from '../Config';
import Config, {RECENT_UPDATES_DEFAULT} from '../Config';

const pkg = require('../../package.json');

Expand Down Expand Up @@ -140,6 +141,31 @@ export const handler = catchAsyncError(async opts => {
console.log(`\n${strong('Ignored updates:')}\n\n${createSimpleTable(rows)}`);
}

let infoTime = toTimespan(config.recentUpdates?.info ?? RECENT_UPDATES_DEFAULT.info);
let warningTime = toTimespan(config.recentUpdates?.warning ?? RECENT_UPDATES_DEFAULT.warning);
let cautionTime = toTimespan(config.recentUpdates?.caution ?? RECENT_UPDATES_DEFAULT.caution);

// If timespan are not valid, print an error and set to default values
if (infoTime < warningTime || infoTime < cautionTime || warningTime < cautionTime) {
console.error('Invalid timespan values in config.recentUpdates. Using default values.');
infoTime = toTimespan(RECENT_UPDATES_DEFAULT.info);
warningTime = toTimespan(RECENT_UPDATES_DEFAULT.warning);
cautionTime = toTimespan(RECENT_UPDATES_DEFAULT.caution);
}

// Preload published dates in the background before the loop
if (!handler.publishedDatesCache) {
handler.publishedDatesCache = {};
await Promise.all(modulesToUpdate.map(async ({name, to}) => {
handler.publishedDatesCache[`${name}@${to}`] = new Date(await getVersionPublicationDate(name, to));
}));
}

// Wait for at least one published date to be fetched
while (Object.keys(handler.publishedDatesCache).length < modulesToUpdate.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}

const updatedModules = [];
let isUpdateFinished = false;
while (modulesToUpdate.length && !isUpdateFinished) {
Expand All @@ -150,21 +176,9 @@ export const handler = catchAsyncError(async opts => {
// Adds new line
console.log('');

let infoTime = toTimespan(config.recentUpdates?.info ?? '3d');
let warningTime = toTimespan(config.recentUpdates?.warning ?? '2d');
let cautionTime = toTimespan(config.recentUpdates?.caution ?? '1d');

// If timespan are not valid, print an error and set to default values
if (infoTime < warningTime || infoTime < cautionTime || warningTime < cautionTime) {
console.error('Invalid timespan values in config.recentUpdates. Using default values.');
infoTime = toTimespan('3d');
warningTime = toTimespan('2d');
cautionTime = toTimespan('1d');
}

// This checks if the package was released less than 3 days ago, throws a warning if true
const publishedDate = new Date(await getVersionPublicationDate(name, to));
// This is 3 days prior to execution time.
// This checks if the package was released less than N days ago, throws a warning if true
const publishedDate = handler.publishedDatesCache[`${name}@${to}`];
// This is N days prior to execution time.
const recommendedDatePrior = new Date(Date.now() - infoTime);
const isRecent = publishedDate.getTime() > recommendedDatePrior.getTime();
if (isRecent) {
Expand All @@ -176,10 +190,10 @@ export const handler = catchAsyncError(async opts => {
let message = (warningLevel === 'caution')
? upgradeCaution('CAUTION') : (warningLevel === 'warning')
? upgradeWarning('WARN') : upgradeInfo('INFO');
message += ` ${name}@${to.replace(
/[~^]/,
''
)} was released less than ${Math.ceil(

const versions = await getModuleVersions(name);
const resolvedVersion = semver.maxSatisfying(Object.keys(versions), to);
message += ` ${name}@${resolvedVersion} was released less than ${Math.ceil(
timeSincePublication / toTimespan('1d')
)} days ago, be careful when upgrading.`;
console.log(message);
Expand Down
13 changes: 10 additions & 3 deletions src/packageUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import shell from 'shelljs';

import _ from 'lodash';
import got from 'got';
import {maxSatisfying, validRange} from 'semver';

export const DEPS_GROUPS = [
{name: 'global', field: 'dependencies', flag: 'g', ncuValue: 'prod'},
Expand Down Expand Up @@ -110,8 +111,14 @@ export const getModuleInfo = _.memoize(async moduleName =>
})
);

// This function returns the publication date of a specific module version.
export const getVersionPublicationDate = _.memoize(async (moduleName, version) => {
export const getModuleVersions = _.memoize(async moduleName => {
const moduleData = await got(`https://registry.npmjs.org/${moduleName}/`).json();
return moduleData.time[version.replace("^", "")];
return moduleData.time;
});

// This function returns the publication date of a specific module version.
export const getVersionPublicationDate = _.memoize(async (moduleName, version) => {
const versions = await getModuleVersions(moduleName);
const resolvedVersion = maxSatisfying(Object.keys(versions), validRange(version));
return versions[resolvedVersion] || null;
}, (moduleName, version) => `${moduleName}@${version}`);
2 changes: 1 addition & 1 deletion src/stringUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function toSentence(items) {
}

export function toTimespan(string) {
const match = string.match(/(\d+)([smhd])/);
const match = string.match(/^(\d+)([smhd])$/);
if (!match) {
return null;
}
Expand Down