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

[FR] --target minor or patch should exclude 0.x.x release #958

Closed
3 tasks done
ashidaharo opened this issue Oct 21, 2021 · 75 comments
Closed
3 tasks done

[FR] --target minor or patch should exclude 0.x.x release #958

ashidaharo opened this issue Oct 21, 2021 · 75 comments

Comments

@ashidaharo
Copy link

  • I have searched for similar issues
  • I am using the latest version of npm-check-updates
  • I am using node >= 10.17

Steps to Reproduce

ncu --target minor

or

ncu --target patch

Current Behavior

Both results include the new release of 0.x.x.
like

ncu --target minor
 axios                              ^0.22.0  →   ^0.23.0

Expected Behavior

0.x.x means that all release MAY contain BREAKING CHANGES.
So, release of 0.x.x should be treated as a MAJOR release.
This should be a feasible feature, since it is correctly displayed as such when --color is set.

@ashidaharo
Copy link
Author

Well... of course, I think npm update would be better than ncu --target minor... but it doesn't work well, does it?

@ashidaharo
Copy link
Author

ashidaharo commented Oct 21, 2021

const versionsSorted = [...versions] // eslint-disable-line fp/no-mutating-methods
.sort(compareVersions)
.filter(v => {
const parsed = semver.parse(v)
return (level === 'major' || parsed.major === cur.major) &&
(level === 'major' || level === 'minor' || parsed.minor === cur.minor)

I think we can solve this problem by adding a condition to check for 'minor' and 'patch' when parsed.major === 0.

@ashidaharo
Copy link
Author

But... this fix seems to me to be a breaking change.
I can't decide whether to include it in the patch version as a bug fix or in the next major version update as a breaking change, so I guess I can't send you a PR for it...

@kokushkin
Copy link

@ashidaharo I've just discovered it too) It should be something that was left intentionally or a new bug (but I think it was always like this). I should not think nobody hasn't noticed it during all this time, for it's a very important behavior. But what was the intension?

@kokushkin
Copy link

I can't decide whether to include it in the patch version as a bug fix or in the next major version update as a breaking change, so I guess I can't send you a PR for it...

I think it should depend on how clear it was communicated from the description of the interface of the library, though it's a very interesting question by itself;)

@ashidaharo
Copy link
Author

ashidaharo commented Oct 24, 2021

@ashidaharo I've just discovered it too) It should be something that was left intentionally or a new bug (but I think it was always like this). I should not think nobody hasn't noticed it during all this time, for it's a very important behavior. But what was the intension?

When I found the source of the problem, I confirmed that this was a behavior that had always existed, whether it was intentional or not. I think it's probably just that the person who originally wrote it didn't pay attention to exceptions like '0.x.x'.

However, the current behavior has been confirmed as a specification by the following tests.

it('sort version list', () => {
const versions = ['0.1.0', '0.3.0', '0.2.0']
versionUtil.findGreatestByLevel(versions, '0.1.0', 'minor').should.equal('0.3.0')
})

For that reason, I considered the current behavior of this library to be as per the specification of this package, although it does not follow the SemVer specification. Therefore, I thought that fixing this problem should be treated as a breaking change, not a bug.

@ashidaharo
Copy link
Author

ashidaharo commented Oct 24, 2021

Well, it's just that... there's no consistency between the color coding when ncu --color and the actual process, so whether the current behavior is a bug or a breaking change, it's not better and should be fixed...

@raineorshine
Copy link
Owner

Thanks for reporting!

0.x.x means that all release MAY contain BREAKING CHANGES.
So, release of 0.x.x should be treated as a MAJOR release.
This should be a feasible feature, since it is correctly displayed as such when --color is set.

That makes sense. We should change it.

We may have to add a --target-strict option if anyone wants the original behavior.

But... this fix seems to me to be a breaking change.
I can't decide whether to include it in the patch version as a bug fix or in the next major version update as a breaking change, so I guess I can't send you a PR for it...

I would consider it a breaking change, since developers could be counting on the existing behavior (even if it is wrong from a semver perspective). npm-check-updates takes a conservative approach to version numbers since it is part of the npm infrastructure.

@ashidaharo
Copy link
Author

Uh... then I'm sorry, but I'm afraid I can't help you...
The typescript branch that seems to be being developed as the next version hasn't been updated since May, and CI has marked the last commit of that branch as a test failure, so I'm not sure if I can create a PR on that branch.
In addition, to be honest, I am inexperienced in programming (although I was able to find the cause this time by accident) and have only minimal knowledge of JS, only dealing with TS, and in particular, I have little experience in creating my own CLI or reading CLI source code.
So, it's very difficult for me to not only fix the problem and the test, but also to add a new CLI option. I'm really sorry.

@kokushkin
Copy link

so is it 2 issues here?
One with the color. They (0.x.x) show up in a red color even if --target minor . So, to move it back to consistency should color be changed according to the rest of the "existing behavior" without the major update?
The second one that --target minor shouldn't actually return them according to semver standards. This one should be major, right?

It might sound crazy but is it possible that people got used to this inconsistency of "existing behavior" and either of the changes should be a major update?)) I think I might get to use to see red colors in --target minor too, maybe it makes sense after all?) They are special - https://semver.org/#spec-item-4

@kokushkin
Copy link

@raineorshine
Copy link
Owner

raineorshine commented Oct 25, 2021

One with the color. They (0.x.x) show up in a red color even if --target minor . So, to move it back to consistency should color be changed according to the rest of the "existing behavior" without the major update?

This could be done for completeness, but it's low priority.

The second one that --target minor shouldn't actually return them according to semver standards.

Correct.

This one should be major, right?

Yes, though it is called --target latest for historical reasons, and is the default behavior when --target is omitted.

It might sound crazy but is it possible that people got used to this inconsistency of "existing behavior" and either of the changes should be a major update?)) I think I might get to use to see red colors in --target minor too, maybe it makes sense after all?) They are special - https://semver.org/#spec-item-4

Only the change in functionality would be a breaking change. The color change is not breaking since it is not part of the official API.

@kokushkin
Copy link

kokushkin commented Oct 25, 2021

So, we want --target minor not to return them not because of the
https://semver.org/#spec-item-4

Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable."

for they don't have a notion of minor change for (0.y.z) like

but because of the https://github.com/npm/node-semver#caret-ranges-123-025-004

Caret Ranges ^1.2.3 ^0.2.5 ^0.0.4
Allows changes that do not modify the left-most non-zero element in the [major, minor, patch] tuple. In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for versions 0.X >=0.1.0, and no updates for versions 0.0.X.

But wait a minute..

minor updates for versions 1.0.0 and above, patch updates for versions 0.X >=0.1.0

patch updates for versions 0.X >=0.1.0

so they call them patch updates. So it should not be in --target minor then.. but not in major either. Is that what's needed?

and major !== breaking change btw

@kokushkin
Copy link

I'd say we should stick with the definitions of major , minor, patch from https://semver.org and https://github.com/npm/node-semver which look consistent with each other, just 1st 2nd 3rd digit, that's it. And a red color for everything that might contain a breaking change. If it's ok, then I think I understand what should be done)

@kokushkin
Copy link

kokushkin commented Oct 25, 2021

And..it seems to me now that nothing should be done, because it already works like this ))
@raineorshine @ashidaharo

@raineorshine
Copy link
Owner

raineorshine commented Oct 25, 2021

Yes, it's only --target minor and --target patch that are broken. They should not include 0.x.x upgrades.

@kokushkin
Copy link

sorry, I don't really see why they are broken. 0.x.x are paches (according to npm docs). I thought your "minor" should include them, like minor and all less than minor. May we have a quick conference (I prefer google meet but zoom should be fine as well) so we can sort it out eventually, cause chatting tends to be excessively long because of the long feedback time ? 🙂

@raineorshine
Copy link
Owner

sorry, I don't really see why they are broken. 0.x.x are paches (according to npm docs). I thought your "minor" should include them, like minor and all less than minor.

A patch version is defined in the spec as follows:

"Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced."

The where-clause clearly excludes cases where the major version is 0. That may be unambiguous for semver, but it doesn't answer the question of what exactly --target patch does. Does it include upgrades strictly based on the third component changing? Or does it include only non-breaking upgrades? This is a decision we have to make independently from semver. I tend to agree with the OP that the current behavior is wrong, but perhaps others have a more literal understanding of --target patch.

I can hold off on the change until more people upvote this issue. Nobody has complained about this behavior for the few years that it has existed, so maybe I should listen to that.

May we have a quick conference (I prefer google meet but zoom should be fine as well) so we can sort it out eventually, cause chatting tends to be excessively long because of the long feedback time ? 🙂

No, but thank you for offering. I'm of the (possibly unpopular) opinion that technical discussions benefit from being in written form since it forces those involved to be precise in their language, and automatically provides a "change log" of the discussion.

@kokushkin
Copy link

A patch version is defined in the spec as follows:

"Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced."

it's fine and I don't see any inconsistency with the current behavior

Does it include upgrades strictly based on the third component changing?

Yeah, I think so. And that's consistent with the definition of patch. Minor would be minor or patch. Patch is just the third digit. that's it. No more and no less. It might be a breaking change even in case of 0.X.Y because of

Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.

and MUST not be in case of Z (x.y.Z | x > 0)

No, but thank you for offering. I'm of the (possibly unpopular) opinion that technical discussions benefit from being in written form since it forces those involved to be precise in their language, and automatically provides a "change log" of the discussion.

usually I do some meetings and then write down the decisions and why they were made.....anyway)

Kinda funny I'm convincing your back from what I was standing for, must be very confusing for everybody who might read this)

All this major, bracking, ^ are very confusing but I feel like I've got it eventually))

Probably you want your "minor" to be ^ ? )

@ashidaharo
Copy link
Author

ashidaharo commented Oct 26, 2021

I have some thoughts after reading this discussion, so let me give my new opinion with some modifications, although it may confuse the discussion further.

I would first argue that, as before: The difference between the color coding when using ncu --color and the actual behavior when using --target is a problem that should be fixed.
And here's what I've changed my opinion on: If you want to make breaking changes in the behavior of this software, don't just make the behavior match the color coding... Not only that, let's change both the color coding and the behavior.

https://github.com/npm/node-semver#tilde-ranges-123-12-1
https://github.com/npm/node-semver#caret-ranges-123-025-004
Apparently, node-semver clearly defines the behavior for versions such as 0.y.z when the version was declared with tildes and carets.

In contrast, the current color-coding rules are defined as follows.

// major = red (or any change before 1.0.0)
// minor = cyan
// patch = green
const color = i === 0 || partsToColor[0] === '0' ? 'red' :
i === 1 ? 'cyan' :
'green'

This is clearly different from the behavior of node-semver when either tildes or carets are given to a version.

And... this is more of a personal bias than following the spec, but I think most people use Caret to declare SemVer anyway...

Therefore, in my biased opinion, if we are going to change the behavior, we should add part of the behavior definition of caret-ranges in node-semver to the SemVer definition as a basis for defining the new behavior.

  • If the version was 0.0.z, then the change in the z number should be treated as a full major, and the version should not be changed by --target patch or --target minor.
  • If the version is 0.y.z | y>0, then the change in the y number should be treated as a full major, and the version should not be changed by --target patch or --target minor. z is... debatable.

That's all. I know this will confuse the discussion even more, but it seemed to me that if we were going to make a change, it would be better to base it on this approach.

@ashidaharo
Copy link
Author

Well, of course, node-semver also says this.

Many authors treat a 0.x version as if the x were the major "breaking-change" indicator.
Caret ranges are ideal when an author may make breaking changes between 0.2.4 and 0.3.0 releases, which is a common practice. However, it presumes that there will not be breaking changes between 0.2.4 and 0.2.5. It allows for changes that are presumed to be additive (but non-breaking), according to commonly observed practices.

This behavior is only based on "commonly observed practices" and is not quite specifics.

@ashidaharo
Copy link
Author

ashidaharo commented Oct 26, 2021

Oh, I forgot.
My personal opinion about "the debatably z" is that even if it's not really breaking, during development it's not possible to distinguish whether it's a feature addition or a bug fix anyway, so there's no point in keeping track of it anymore, so I'd be happy to update it even if it's a --target patch or --target minor.
Well, the conclusion will be one of two extremes, either z will also be treated as a major at all, or this. Maybe.

@kokushkin
Copy link

There are 3 things: minor, caret (^) and breaking change and they are not the same and not the opposite of each other. It's like Venn diagram.

Minor it's just the second digit and I haven't seen nowhere in the standards that it's something different, except maybe this small hint

Many authors treat a 0.x version as if the x were the major "breaking-change" indicator.

but I'd put it all in the ""

Many authors treat a 0.x version as if the x were "the major breaking-change" indicator.

The current red color, as I can see from the source code, reflects that it might be a breaking change.

@ljharb
Copy link

ljharb commented Oct 26, 2021

In npm, in x.y.z or 0.x.y or 0.0.x, the x is semver-major, the y is semver-minor, and the z is semver-patch.

You can check this out yourself by using https://npmjs.com/semver.

@kokushkin
Copy link

sorry, what exactly I should call to see that in 0.x.y x is semver-major let's say? Are you saying it's semver-major? @ljharb

@ljharb
Copy link

ljharb commented Oct 26, 2021

@kokushkin 0.2.0 to 0.3.0 is semver-major in the npm ecosystem, yes. You can see that by using the semver package and seeing that the ^0.2.0 range does not include 0.3.0.

@kokushkin
Copy link

but caret ^ and major/minor are different things.Can you show us in the documentation that minor = ^ ?

@ljharb
Copy link

ljharb commented Oct 26, 2021

No, because a) there's no documentation needed, the implementation is what matters, and b) minor doesn't equal ^.

Go to http://semver.npmjs.com. Type in ^0.2.0. See how only the 0.2.x versions highlight? That's because 0.3.0 is a breaking change from 0.2.x. That's how npm works, and has always worked.

@ashidaharo
Copy link
Author

ashidaharo commented Jan 18, 2022

In my opinion, semver definition says

Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable."

So, even if the 0.0.x update is treated as a non-breaking update by commonly practices, i think it should be treated as a breaking change by definition, since it is only practices.
Of course, this is only the definition of the original semver, so if the npm documentation clearly states that it must be handled as such, that's a different story.
However, it only says this

"However, it presumes that there will not be breaking changes between 0.2.4 and 0.2.5. It allows for changes that are presumed to be additive (but non-breaking), according to commonly observed practices".

@ljharb
Copy link

ljharb commented Jan 18, 2022

@ashidaharo That's v2 of the semver spec. npm follows v1 of the spec, and "what npm does" is the arbiter of truth for the npm ecosystem. npm's rules are #958 (comment) - there's no room for debate.

@ashidaharo
Copy link
Author

@ljharb

  1. This definition has not changed since semver v1.
  2. npm document says that it follows semver v2.

@ljharb
Copy link

ljharb commented Jan 18, 2022

none the less, semver.npmjs.com and the semver package treats it the way i've described, so all that means is the docs are wrong.

@ashidaharo
Copy link
Author

none the less, semver.npmjs.com and the semver package treats it the way i've described, so all that means is the docs are wrong.

Yes, that is mentioned.

Many authors treat a 0.x version as if the x were the major "breaking-change" indicator.

However, this is not a definition, but an implementation of npm's Semver.
As I quoted earlier, the actual npm packages also mention that this is not always expected, even though some people certainly treat it as a common practice.

@ljharb
Copy link

ljharb commented Jan 18, 2022

The implementation is the only thing that matters, since that's how ^ actually behaves and has always behaved in the npm ecosystem since its introduction.

@ashidaharo
Copy link
Author

It looks like we will have to wait for the response of the maintainer and others who are interested in this issue, as we cannot seem to reach a mutual agreement on this.

@ljharb
Copy link

ljharb commented Jan 18, 2022

I mean sure, but what would the point be of "npm check updates" if it wasn't checking "what npm will actually update"

@ashidaharo
Copy link
Author

npm-check-updates upgrades your package.json dependencies to the latest versions, ignoring specified versions.

https://github.com/raineorshine/npm-check-updates#npm-check-updates

@ashidaharo
Copy link
Author

If you want to update safely according to npm's behavior, use npm update instead of this tool, which is sufficient for minor updates, and if you want to do major updates, you should check with npm outdated and then update manually.

@ashidaharo
Copy link
Author

And in that way, you can use not only Caret but also Tilde, X-Ranges, and various other version control methods provided by npm.

@ashidaharo
Copy link
Author

From the beginning, this tool was a tool that worked independently of the npm implementation, or rather, actively ignored it, even before the target option was added.

@raineorshine
Copy link
Owner

It's not a major, neither from my understanding of it as just the 3rd number (should be green then), nor from other's understanding of it as a minor of 0.x (should be blue then).

Therefore, my suggestion is to screw the blue color entirely)

I take your point. It's not a binary breaking-nonbreaking distinction, and it's not strictly 1st 2nd 3rd, although it's close to that. As described in the README:

  • Red = major upgrade (and all major version zero)
  • Cyan = minor upgrade
  • Green = patch upgrade

I actually think this is still the best representation. Showing 0.x upgrades in green is dangerous, and defeats the purpose of red as a distinctive color for breaking changes. Blue could be removed I suppose, but that seems like an unnecessary loss of functionality. Blue adds an extra layer of distinction that can be ignored by most at no cost. Though I see your point that it may not be no cost for those who are confused by the blue color.

npm-check-updates upgrades your package.json dependencies to the latest versions, ignoring specified versions.

raineorshine/npm-check-updates#npm-check-updates

That's right. npm-check-updates has primarily existed to forcefully break versions, since this functionality is not in npm. Over time, developers have come to use npm-check-updates as an alternative to npm outdated and for more fine-grained control of package upgrades. There's a real need for --target semver, we just need a contributor.

@kokushkin
Copy link

so --target semver would show what?
let's say I have 1.2.3 dependency (not ^1.2.3 or ~1.2.3) , and there're 1.2.6, 1.3.0, 2.0.1 , what should --target semver show me?

@raineorshine
Copy link
Owner

so --target semver would show what?
let's say I have 1.2.3 dependency (not ^1.2.3 or ~1.2.3) , and there're 1.2.6, 1.3.0, 2.0.1 , what should --target semver show me?

Well, I suppose I would want --target semver to match npm update. Then I would want --target caret to upgrade all dependencies as if they started with ^.

@kokushkin
Copy link

I'm not sure if npm update does anything about dependencies without ^ or ~ (rather nothing). --target semver also doesn't reflect what is written in semver in case of 0.x.y. Would it be better to have just --target caret (updates everything as if it has ^), and --target tilde (updates everything as if it has ~)?
I've just recalled me adding ^ before every dependency and calling npm update after ;)
But why it shouldn't be added in npm update itself then?

@raineorshine
Copy link
Owner

raineorshine commented Jan 21, 2022

Would it be better to have just --target caret (updates everything as if it has ^), and --target tilde (updates everything as if it has ~)?

Yes, I like that!

But why it shouldn't be added in npm update itself then?

Faster releases and better support :)

@Primajin
Copy link
Contributor

Primajin commented Jan 21, 2022

I am currently using the --filterVersion option in order to run the checks only on non-pinned versions.
However I need to run it twice since

> npm-check-updates --filterVersion "/^[~^]/" -t minor

Checking C:\mypath\package.json
[====================] 130/130 100%

 @types/node                       ^17.0.9  →  ^17.0.10
 @types/react-router               ^5.1.17  →   ^5.1.18
 @typescript-eslint/eslint-plugin   ~5.9.0  →   ~5.10.0
 @typescript-eslint/parser          ~5.9.0  →   ~5.10.0
 eslint-plugin-jest                ~25.3.4  →   ~25.7.0
 npm-check-updates                 ^12.1.0  →   ^12.2.0
 typescript                         ^4.5.4  →    ^4.5.5

gives me not exactly want I want.
I actually don't want to bump a minor (^) on @typescript-eslint/parser but only a patch (~)

So instead I changed that to

> npm-check-updates --filterVersion "/^[\^]/" -t minor && npm-check-updates --filterVersion "/^[~]/" -t patch

Checking C:\mypath\package.json
[====================] 127/127 100%

 @types/node          ^17.0.9  →  ^17.0.10
 @types/react-router  ^5.1.17  →   ^5.1.18
 npm-check-updates    ^12.1.0  →   ^12.2.0
 typescript            ^4.5.4  →    ^4.5.5

Run ncu -u to upgrade package.json

Checking C:\mypath\package.json
[====================] 3/3 100%

 @typescript-eslint/eslint-plugin  ~5.9.0  →  ~5.9.1
 @typescript-eslint/parser         ~5.9.0  →  ~5.9.1

Run ncu -u to upgrade package.json

Which gives me the "correct" minor and patches for the ones I want and skips pinned versions.

Though I wonder if we could introduce a new target version like safe (naming is hard, maybe you have a better one) that does that automatically:

  1. Skips all pinned versions
  2. Checks for minor on caret
  3. Checks for patch on tilde
  4. maybe even skips versions that start with 0

What do you think?

EDIT: Apparently I missed #581 / #950 - however if you like I can try to take a stab at it 👍🏻

@raineorshine
Copy link
Owner

raineorshine commented Jan 21, 2022

@Primajin Please open a new issue and I will respond there. This issue is specifically about whether --target minor should exclude 0.x.x releases, and what colors should represent 0.x upgrades.

I recognize that I myself floated --target semver in this issue, but that was only to suggest that it might be a better solution than --target minor for some people with this issue. For new features, or to bring in other requirements, please open a new issue.

@Primajin
Copy link
Contributor

Sure happy to - just thought two birds one stone 😅

@raineorshine
Copy link
Owner

raineorshine commented Feb 28, 2022

As of v12.5.0, you can specify a custom target function in your .ncurc.js file, or when importing npm-check-updates as a module:

/** Custom target.
  @param dependencyName The name of the dependency.
  @param parsedVersion A parsed Semver object from semver-utils.
    (See https://git.coolaj86.com/coolaj86/semver-utils.js#semverutils-parse-semverstring)
  @returns One of the valid target values (specified in the table above).
*/
target: (dependencyName, [{ semver, version, operator, major, minor, patch, release, build }]) => {
  if (major === '0') return 'minor'
  return 'latest'
}

(edited to reflect the string type pointed out by @betaorbust)

You can hack together your own semver-like target logic, but the intention is to eventually add a --semver option that handles this for you.

@betaorbust
Copy link

As a note: on my machine the major tag came through as a string, instead of a number.

-if (major === 0) return 'minor'
+if (major === '0') return 'minor'

@raineorshine Thanks for your time and effort making such a great tool!

@Primajin
Copy link
Contributor

@betaorbust You can also just use two equals == then it doesn't matter if it's a string or a number 😉

@betaorbust
Copy link

betaorbust commented Apr 19, 2022

Chaotic Alignment has entered the chat

😆

@raineorshine
Copy link
Owner

Closing, as described in #958 (comment).

For the proposed --semver option, see #1054.

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

No branches or pull requests

8 participants
@ljharb @raineorshine @betaorbust @Primajin @kokushkin @lunany188 @ashidaharo and others