npm-upgrade
npm-upgrade copied to clipboard
Respect peer dependencies
This PR improves the upgrade of dependencies which are peer dependencies of other dependencies.
Scenario
Your app has a dependency to the package ncu-test-peer. ncu-test-peer has a peer dependency to the package ncu-test-return-version and requires version 1.x of this package. I.e., ncu-test-peer has the following package.json:
{
"peerDependencies": {
"ncu-test-return-version": "1.x"
}
}
Hence, your app has the following package.json:
{
"dependencies": {
"ncu-test-peer": "1.0.0",
"ncu-test-return-version": "1.0.0"
}
}
Let us assume, that ncu-test-return-version has published versions 1.0.0, 1.1.0 and 2.0.0.
Current Situation (without this PR)
If you run the current release of npm-upgrade (3.0.0) for your app, you get an upgrade suggestion for ncu-test-return-version version 2.0.0. If you follow this suggestion, your app is broken, since ncu-test-peer's peer dependency requirement is not met (2.0.0 does not satisfy 1.x). If you deny this suggestion, you will stay at version 1.0.0, although there is a newer version 1.1.0 that satisfies the requirement 1.x.
Situation with this PR
This PR adds the new peer dependency check of npm-check-updates which was introduced in version 11.4.0 (see https://github.com/raineorshine/npm-check-updates/pull/869). Now, npm-upgrade provides an upgrade suggestion for ncu-test-return-version version 1.1.0 -- which is the expected result for this scenario.
It's much better now, but it's still not clear what package is actually blocking other packages from updating. E.g. in my case I see the following:
Ignored incompatible updates (peer dependencies):
@angular/animations 11.2.7 → 11.2.10
@angular/common 11.2.7 → 11.2.10
@angular/core 11.2.7 → 11.2.10
@angular/platform-browser 11.2.7 → 11.2.10
tslib 2.1.0 → 2.2.0
@angular/compiler 11.2.7 → 11.2.10
typescript 4.1.5 → 4.2.4
webpack 4.44.1 → 5.33.2
So I want to know what package prevents @angular packages from updating? Is it possible to get this information somehow?
Another issue is I had to run npm-upgrade '@angular/*' 3 times in my project in order to make a minor Angular upgrade.
First time I was shown the following:
New versions of active modules available:
@angular/cdk 11.2.6 → 11.2.9
@angular/forms 11.2.7 → 11.2.10
@angular/platform-browser-dynamic 11.2.7 → 11.2.10
@angular/router 11.2.7 → 11.2.10
@angular/cli 11.2.6 → 11.2.9
@angular/compiler-cli 11.2.7 → 11.2.10
Ignored incompatible updates (peer dependencies):
@angular/animations 11.2.7 → 11.2.10
@angular/common 11.2.7 → 11.2.10
@angular/core 11.2.7 → 11.2.10
@angular/platform-browser 11.2.7 → 11.2.10
@angular/compiler 11.2.7 → 11.2.10
After I have agreed with all updates and ran npm install and npm-upgrade again it showed the following:
New versions of active modules available:
@angular/platform-browser 11.2.7 → 11.2.10
@angular/compiler 11.2.7 → 11.2.10
Ignored incompatible updates (peer dependencies):
@angular/animations 11.2.7 → 11.2.10
@angular/common 11.2.7 → 11.2.10
@angular/core 11.2.7 → 11.2.10
After doing same steps again:
New versions of active modules available:
@angular/animations 11.2.7 → 11.2.10
@angular/common 11.2.7 → 11.2.10
Ignored incompatible updates (peer dependencies):
@angular/core 11.2.7 → 11.2.10
And only after 3rd time I've upgraded all the Angular packages.
It happens because some packages has other packages in their peer dependencies e.g. @angular/[email protected] has @angular/[email protected] and @angular/[email protected] in its peer dependencies list so these packages just can't be updated until platform-browser is updated. On the next step updated [email protected] has [email protected] and [email protected] in its peer deps so they can be updated now.
So the issue is current logic doesn't take into account that version of the peer dependency may change after the actual upgrade - it only takes current peer versions into account.
Another issue is only immediate peer deps are tracked which means an update to C@2 will be improperly suggested in the following situation:
<project>
|-- C@^1 (update to v2 will be suggested but it's invalid as A -> B has C@^1 in its peer deps)
|-- A
| |-- B
| | |---C@^1 (peer)
You mentioned three aspects:
1. Show source of peer incompatibility
npm-check-updates doesn't provide this information, currently.
2. Multiple calls of npm-upgrade are necessary for complex dependencies
This is caused by your approach of splitting npm-upgrade and npm install. The only solution I can see is to combine both actions and run them repeatedly.
3. Consider deep peer dependencies
I'm not sure if this is required, because I think that A should have declared C as it's own peer dependency. In my point of view, A is broken if it does not do this. And as far as I understand the npm 7 approach correctly, the npm people have changed their mind and now have this view, too (see --strict-peer-deps flag). If you nevertheless want to consider deep peer dependencies, this should be fixed in npm-check-updates.
This is caused by your approach of splitting
npm-upgradeandnpm install.
So what approach would work in your opinion?
An approach would be to automatically call npm install after updating package.json and then automatically check for updates again.
Another approach would be to execute npm view with the updated package version and get the new list of peer dependencies. Then, a check for updates can be done again without installing the packages. But there are two ways for user interaction:
Approach 1
The above actions are executed before the list of upgradable packages are shown to the user. This means, the user gets the following list in your example:
New versions of active modules available:
@angular/cdk 11.2.6 → 11.2.9
@angular/forms 11.2.7 → 11.2.10
@angular/platform-browser-dynamic 11.2.7 → 11.2.10
@angular/router 11.2.7 → 11.2.10
@angular/cli 11.2.6 → 11.2.9
@angular/compiler-cli 11.2.7 → 11.2.10
@angular/platform-browser 11.2.7 → 11.2.10
@angular/compiler 11.2.7 → 11.2.10
@angular/animations 11.2.7 → 11.2.10
@angular/common 11.2.7 → 11.2.10
However, if the user says no to @angular/platform-browser but yes to @angular/animation, the resulting list of upgradable packages will be broken (i.e. not all peer dependencies are satisfied).
Approach 2
In this approach, we do this iteratively. When executing npm-upgrade, the following list is shown:
New versions of active modules available:
@angular/cdk 11.2.6 → 11.2.9
@angular/forms 11.2.7 → 11.2.10
@angular/platform-browser-dynamic 11.2.7 → 11.2.10
@angular/router 11.2.7 → 11.2.10
@angular/cli 11.2.6 → 11.2.9
@angular/compiler-cli 11.2.7 → 11.2.10
Ignored incompatible updates (peer dependencies):
@angular/animations 11.2.7 → 11.2.10
@angular/common 11.2.7 → 11.2.10
@angular/core 11.2.7 → 11.2.10
@angular/platform-browser 11.2.7 → 11.2.10
@angular/compiler 11.2.7 → 11.2.10
But after the user has answered all questions, but before asking for updating package.json, npm view is executed for getting the new list of peer dependencies and the check for updates is done again with this list. This means, after answering the last update question, there will appear a status bar (every updated package has to be checked with npm view) and the user sees the next upgrade iteration and the next question appears. But the user just have to wait and does not need to call npm-upgrade multiple times.
I've made a PR for npm-check-updates in order to implement approach 1 directly over there: https://github.com/raineorshine/npm-check-updates/pull/874
With that PR, it will be possible to add the reason to the list of incompatible updates. I will update this PR after the above PR has been merged and released.
Teaser 1:
New versions of active modules available:
ncu-test-peer-update 1.0.0 → 1.1.0
ncu-test-return-version 1.0.0 → 1.1.0
Ignored incompatible updates (peer dependencies):
ncu-test-return-version 1.0.0 → 2.0.0 reason: ncu-test-peer-update requires 1.1.x
? Update "ncu-test-peer-update" in package.json from 1.0.0 to 1.1.0? (Use arrow keys)
[...]
Teaser 2:
New versions of active modules available:
@nextcloud/dialogs ^3.1.1 → ^3.1.2
@nextcloud/router ^1.2.0 → ^2.0.0
@nextcloud/vue ^3.8.0 → ^3.9.0
@nextcloud/webpack-vue-config ^4.0.1 → ^4.0.3
easymde ^2.14.0 → ^2.15.0
markdown-it ^12.0.4 → ^12.0.6
@babel/core ^7.13.14 → ^7.13.16
@babel/preset-env ^7.13.12 → ^7.13.15
core-js ^3.10.0 → ^3.11.0
eslint ^7.23.0 → ^7.25.0
eslint-plugin-vue ^7.8.0 → ^7.9.0
eslint-webpack-plugin ^2.5.3 → ^2.5.4
sass ^1.32.8 → ^1.32.11
stylelint ^13.12.0 → ^13.13.0
webpack ^5.28.0 → ^5.35.1
Ignored incompatible updates (peer dependencies):
css-loader ^4.3.0 → ^5.2.4 reason: @nextcloud/webpack-vue-config requires ^4.3.0
eslint-plugin-promise ^4.3.1 → ^5.1.0 reason: @nextcloud/eslint-config requires ^4.2.1, eslint-config-standard requires ^4.2.1
sass-loader ^10.1.1 → ^11.0.1 reason: @nextcloud/webpack-vue-config requires ^10.0.5
Ignored updates:
From To Ignored versions Reason
vue-fragment 1.5.1 → 1.5.2 1.5.2 buggy version
? Update "@nextcloud/dialogs" in package.json from ^3.1.1 to ^3.1.2? (Use arrow keys)
[...]
I'm moving some more code to npm-check-updates: https://github.com/raineorshine/npm-check-updates/pull/876
After that was merged, we can call getIgnoredUpgrades from ncu and reduce the code in npm-upgrade.
Final update with much simplification was done. Ready for review and for merge 😁
@th0r Is there anything open that I should do?