Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: match npm semantics exactly for installs and updates #296

Merged
merged 11 commits into from
Apr 29, 2023

Conversation

Bubblyworld
Copy link
Contributor

Currently some tests are failing due to the new behaviour, I'm still working
my way through them. There's a script you can run at test/npm-compatibility/
that will test the behaviour of npm for updates/installs in a bunch of different
scenarios, and tests that make sure the behaviour of the generator is aligned.

  1. npm install <pkg> bumps the version of <pkg> to latest, and only bumps
    transitive dependencies if they're out of range.
  2. npm update <pkg> has exactly the same behaviour as 1 in all tested cases.
  3. npm install bumps the versions of anything that is out of range, but keeps
    versions of in-range primaries and secondaries.
  4. npm update bumps the versions of everthing to latest.

The changes to latest that I've made:

  1. the latest and freeze generator options have both been deprecated in
    favour of a ResolutionOptions object that you can optionally pass into
    install/link/update.
  2. ResolutionsOptions contains mode, latestPrimaries, latestSecondaries
    and freeze fields. I had to split into the two cases for latest to make
    sure that npm install <pkg> doesn't bump secondaries, but does bump
    primaries.
  3. When none of the latest/freeze resolution options are set, the default
    is to take existing locks whenever they are in range.

@Bubblyworld
Copy link
Contributor Author

So I've replaced all of the free/latest/mode options and replaced them with a single mode parameter that controls existing locks:

  1. "default" bumps out of range locks, but keeps in-range locks.
  2. "latest-primaries" bumps primary locks, same behaviour as "default" for secondaries.
  3. "latest-all" bumps all touched locks.
  4. "freeze" uses existing locks whenever possible, even if they are out-of-range.

Copy link
Member

@guybedford guybedford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work, I'd really like to land this soon, but would value some feedback on the review first when you can.

@@ -345,7 +345,7 @@ export default class TraceMap {
dynamics.map(async ([specifier, parent]) => {
await this.visit(
specifier,
{ visitor, installOpts: { mode: "existing" }, toplevel: false },
{ visitor, installMode: "freeze", toplevel: false },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This internal unification is really nice to see!!

Comment on lines 1011 to +1017
async install(
install?: string | Install | (string | Install)[]
): Promise<void | { staticDeps: string[]; dynamicDeps: string[] }> {
if (arguments.length > 1)
throw new JspmError(
"Install takes no arguments, a single install target, or a list of install targets."
);
return this._install(install);
}

private async _install(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I fully understand the reason for this private _install function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on whether install is given an argument or not, two different modes are used:

  1. No argument - all top-level locks installed in default mode, i.e. keep all in-range locks.
  2. Arguments - all arguments installed in latest-primaries mode, i.e. bump those arguments to latest but keep in-range secondaries.

This just lets us handle those cases without exposing the mode argument in the public API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed that we were keeping mode part of the private API. Should we perhaps just expose it though with defaults? Is there a risk in doing that?


// If there are no arguments, then we reinstall all the top-level locks:
if (!install) {
if (install === null || install === undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was previously handled via generator.reinstall. I believe this change means that reinstall is now deprecated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reinstall() method is equivalent to freeze installing all the top-level locks (or equivalently doing an argumentless link, since link uses freeze by default). So I think it can be deprecated now in favour of either of those, yeah.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's do that then, either with this PR or via a follow-up after it lands.

test/npm-compatibility/npm-behaviour.sh Show resolved Hide resolved
@guybedford guybedford merged commit f45e7ed into main Apr 29, 2023
@guybedford guybedford deleted the guy-npm_semantics branch April 29, 2023 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants