diff --git a/README.md b/README.md index bb17b761..6fdb482f 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ $ ncu "/^(?!gulp-).*$/" # windows semver. -n, --newest DEPRECATED. Renamed to "--target newest". -p, --packageManager npm, yarn (default: "npm") +-o, --ownerChanged Check if the package owner changed between + current and upgraded version. --packageData Include stringified package file (you can also send to stdin). --packageFile Package file location (default: ./package.json). diff --git a/lib/constants.js b/lib/constants.js index 783b981c..79bdbf99 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -80,6 +80,10 @@ const cliOptions = [ description: 'npm, yarn', default: 'npm' }, + { + name: '-o, --ownerChanged', + description: 'Check if the package owner changed between current and upgraded version.', + }, { name: '--packageData', description: 'Include stringified package file (you can also send to stdin).' diff --git a/lib/logging.js b/lib/logging.js index fe4d0bfa..ff3f2b22 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -67,6 +67,7 @@ function createDependencyTable() { * @param args * @param args.from * @param args.to + * @param args.ownersChangedDeps * @returns */ function toDependencyTable(args) { @@ -77,8 +78,13 @@ function toDependencyTable(args) { const to = isGithubUrl(toRaw) ? getGithubUrlTag(toRaw) : isNpmAlias(toRaw) ? parseNpmAlias(toRaw)[1] : toRaw + const ownerChanged = args.ownersChangedDeps + ? dep in args.ownersChangedDeps + ? args.ownersChangedDeps[dep] ? '*owner changed*' : '' + : '*unknown*' + : '' const toColorized = colorizeDiff(from, to) - return [dep, from, '→', toColorized] + return [dep, from, '→', toColorized, ownerChanged] }) rows.forEach(row => table.push(row)) // eslint-disable-line fp/no-mutating-methods return table @@ -91,8 +97,9 @@ function toDependencyTable(args) { * @param args.upgraded - The packages that should be upgraded. * @param args.numUpgraded - The number of upgraded packages * @param args.total - The total number of all possible upgrades + * @param args.ownersChangedDeps - Boolean flag per dependency which announces if package owner changed. */ -function printUpgrades(options, { current, upgraded, numUpgraded, total }) { +function printUpgrades(options, { current, upgraded, numUpgraded, total, ownersChangedDeps }) { print(options, '') // print everything is up-to-date @@ -116,7 +123,8 @@ function printUpgrades(options, { current, upgraded, numUpgraded, total }) { if (numUpgraded > 0) { const table = toDependencyTable({ from: current, - to: upgraded + to: upgraded, + ownersChangedDeps }) print(options, table.toString()) } diff --git a/lib/npm-check-updates.d.ts b/lib/npm-check-updates.d.ts index b1daf0d1..f445aae7 100644 --- a/lib/npm-check-updates.d.ts +++ b/lib/npm-check-updates.d.ts @@ -56,6 +56,9 @@ declare namespace ncu { /** npm, yarn (default: "npm") */ packageManager?: string; + /** Check if the package owner changed between current and upgraded version. */ + ownerChanged?: boolean; + /** Include stringified package file (you can also send to stdin). */ packageData?: boolean; diff --git a/lib/npm-check-updates.js b/lib/npm-check-updates.js index b075feda..8548bb0d 100644 --- a/lib/npm-check-updates.js +++ b/lib/npm-check-updates.js @@ -167,6 +167,10 @@ function analyzeProjectDependencies(options, pkgData, pkgFile) { const filteredUpgraded = options.minimal ? cint.filterObject(selectedNewDependencies, dep => !isSatisfied(dep)) : selectedNewDependencies const numUpgraded = Object.keys(filteredUpgraded).length + const ownersChangedDeps = options.ownerChanged + ? await vm.getOwnerPerDependency(current, filteredUpgraded, options) + : null + // print if (options.json) { // use the selectedNewDependencies dependencies data to generate new package data @@ -179,7 +183,8 @@ function analyzeProjectDependencies(options, pkgData, pkgFile) { upgraded: filteredUpgraded, latest, numUpgraded, - total: Object.keys(upgraded).length + total: Object.keys(upgraded).length, + ownersChangedDeps }) } diff --git a/lib/package-managers/npm.js b/lib/package-managers/npm.js index 87efdf72..34c9909b 100644 --- a/lib/package-managers/npm.js +++ b/lib/package-managers/npm.js @@ -77,6 +77,32 @@ function parseJson(result, data) { return json } +/** + * Check if package author changed between current and upgraded version. + * + * @param packageName Name of the package + * @param currentVersion Current version declaration (may be range) + * @param upgradedVersion Upgraded version declaration (may be range) + * @returns A promise that fullfills with boolean value. + */ +async function packageAuthorChanged(packageName, currentVersion, upgradedVersion) { + npmConfig.fullMetadata = true + + const result = await pacote.packument(packageName, npmConfig) + if (result.versions) { + const pkgVersions = Object.keys(result.versions) + const current = semver.minSatisfying(pkgVersions, currentVersion) + const upgraded = semver.maxSatisfying(pkgVersions, upgradedVersion) + if (current && upgraded && result.versions[current]._npmUser && result.versions[upgraded]._npmUser) { + const currentAuthor = result.versions[current]._npmUser.name + const latestAuthor = result.versions[upgraded]._npmUser.name + return !_.isEqual(currentAuthor, latestAuthor) + } + } + + return null +} + /** * Returns the value of one of the properties retrieved by npm view. * @@ -334,5 +360,6 @@ module.exports = { ) }, - defaultPrefix + defaultPrefix, + packageAuthorChanged } diff --git a/lib/versionmanager.js b/lib/versionmanager.js index bc7aa61e..8ad6f36a 100644 --- a/lib/versionmanager.js +++ b/lib/versionmanager.js @@ -272,6 +272,27 @@ function filterAndReject(filter, reject) { ) } +/** + * Return a promise which resolves to object storing package owner changed status for each dependency. + * + * @param fromVersion current packages version. + * @param toVersion target packages version. + * @param options + * @returns + */ +async function getOwnerPerDependency(fromVersion, toVersion, options) { + const packageManager = getPackageManager(options.packageManager) + return await Object.keys(toVersion).reduce(async (accum, dep) => { + const from = fromVersion[dep] || null + const to = toVersion[dep] || null + const ownerChanged = await packageManager.packageAuthorChanged(dep, from, to) + return { + ...accum, + [dep]: ownerChanged, + } + }, {}) +} + /** * Returns an 2-tuple of upgradedDependencies and their latest versions. * @@ -582,6 +603,7 @@ module.exports = { isSatisfied, upgradePackageData, upgradePackageDefinitions, + getOwnerPerDependency, // exposed for testing getPreferredWildcard, @@ -589,5 +611,5 @@ module.exports = { queryVersions, upgradeDependencies, upgradeDependencyDeclaration, - getPackageManager + getPackageManager, } diff --git a/test/package-managers/npm/index.js b/test/package-managers/npm/index.js index 9b4ca77a..11a8ecfb 100644 --- a/test/package-managers/npm/index.js +++ b/test/package-managers/npm/index.js @@ -26,4 +26,10 @@ describe('npm', function () { await packageManagers.npm.greatest('ncu-test-greatest-not-newest', null, { cwd: __dirname }) .should.eventually.equal('2.0.0-beta') ) + + it('ownerChanged', async () => { + await packageManagers.npm.packageAuthorChanged('mocha', '^7.1.0', '8.0.1').should.eventually.equal(true) + await packageManagers.npm.packageAuthorChanged('htmlparser2', '^3.10.1', '^4.0.0').should.eventually.equal(false) + await packageManagers.npm.packageAuthorChanged('ncu-test-v2', '^1.0.0', '2.2.0').should.eventually.be.null + }) })