jashkenas / underscore

JavaScript's utility _ belt

Home Page:https://underscorejs.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fix the versioning

danielchatfield opened this issue · comments

1.7.0 introduced loads of breaking changes.

The number of dependant modules which are now broken as a result is huge, personally I think that 1.7.0 should be killed (removed from npm) and 2.0 released - the longer the delay the harder it will be to do this.

underscore.js is solely consumed via package managers that mandate the use of semver, you may personally not like semver but that is what is used by the installers to determine compatibility. Last time this was brought up you stated that if you used semver then we would be on underscore version 47 now - well that is much better than having broken code everywhere and lodash has managed to keep the version number below 4.0.0 without breaking everyone's code.

I think we should leave 1.7.0 as 1.7.0. There are a lot of changes, some of which change behavior for edge cases, which justifies bumping the minor version up a notch. If you're using npm's ~ operator, you won't be automatically upgraded to 1.7.0.

But it's not really the kind of complete overhaul or massive change to the public API that justifies a 2.0. Perhaps once things are really polished and locked down.

But that said, let's leave this ticket open for discussion...

If you're using npm's ~ operator, you won't be automatically upgraded to 1.7.0.

By default npm uses ^

According to Semver.org bumping the minor release should:

MINOR version when you add functionality in a backwards-compatible manner, and

If the behaviors changed do not solely constitute urgent bug fixes which were causing the edge cases to change, this is not a minor release.

Coupled with the change to NPM moving to ^ which will accept any version within a major release AND the fact that underscore is the MOST depended upon package in NPM it is likely better to resolve this issue as @danielchatfield suggests.

From your changelog:

Underscore templates no longer accept an initial data object. _.template always returns a function now.

Changing a method signature and what it returns (without it previously being deprecated) in a minor version is a pretty big deal:

> 14:21:41 ~/src/test
↳ npm i underscore
npm http GET https://registry.npmjs.org/underscore
npm http 304 https://registry.npmjs.org/underscore
npm http GET https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz
npm http 200 https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz
underscore@1.7.0 ../../node_modules/underscore
14:21:47 ~/src/test
↳ node
> var und = require('underscore');
undefined
> und.template('hello: <%= name %>', {name: 'moe'});
{ [Function]
  source: 'function(obj){\nvar __t,__p=\'\',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,\'\');};\nwith(obj||{}){\n__p+=\'hello: \'+\n((__t=( name ))==null?\'\':__t)+\n\'\';\n}\nreturn __p;\n}' }
>

Versus:

14:20:43 ~/src/test
↳ npm i underscore@1.6.0
npm http GET https://registry.npmjs.org/underscore
npm http 200 https://registry.npmjs.org/underscore
npm http GET https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz
npm http 200 https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz
underscore@1.6.0 ../../node_modules/underscore
14:20:52 ~/src/test
↳ node
> var und = require('underscore');
undefined
> und.template("hello: <%= name %>", {name: 'moe'});
'hello: moe'

@jashkenas I understand and respect your stance regarding semver. However, when making breaking changes I believe there is a universal expectation (that predates semver) to bump a major version. At the very least it sets expectations properly, but it will also help those who do follow semver (which is the vast majority of developers these days).

Considering how many people who depend of underscore now have broken code because of this latest release, I think it would be a good decision to do what @megawac said:

releasing 1.7.1 or 1.8 as a revert of 1.6.0 and then working on 2.0.0 for some time next week

+1 on removing and replacing with 2.0.0. Rigid semver specifies that no breaking changes can appear except as a major version (unless you're below version 1.0). This has introduced mysterious breaks for me, deep in node module hierarchies. :0(

This definitely just caused major issues for me. That's what I get for not thoroughly reading the changelog, but I didn't imagine a minor version upgrade would be that dangerous.

Is the solution to add backwards compatibility back into 1.7?

If you're using npm's ~ operator, you won't be automatically upgraded to 1.7.0.

This really isn't the point. Semver is meant for humans just as much as machines. The entire point of semver is to give confidence to consumers of the library that they can safely upgrade, whether that happens automatically with a package manager instruction or by hand.

it's not really the kind of complete overhaul or massive change to the public API that justifies a 2.0

That's not what the major number is for though. It's for any breaking change.

I'm 👍 for revising how Underscore bumps. It seems like most releases in recent memory have had some kind of hiccup with the 1.7.0 bump being the worst (failed unit tests, incomplete features, missing documentation, missing breaking change notices, and versioning that caused many projects to break). The process as it stands today isn't working.

Underscore is among the most popular packages on npm. The whole npm ecosystem is based on semver. Underscore should respect semver.

Underscore templating changes in 1.7.0 broke our project/templates and we had to revert back to 1.6.0. Pretty big / major change to the templating system alone.

As much as I'd like to make everyone happy by using "semantic versioning" — I'm afraid I just can't swallow the stuff. This margin is too narrow to contain the argument against it, but suffice to say, Node doesn't do it, Rails doesn't do it, Python doesn't do it, jQuery doesn't (really) do it — and as a fun little open-source project, it's nice for us to provide real version numbers that are useful for humans, instead of mechanical version numbers that are of merely dubious use for computers.

But you can have your cake and eat it too. I've just published an "official" npm repository for underscore-semver, which will be released at the same time as Underscore is released, and will always contain a "semantic" derivation of the true Underscore version number. Feel free to depend on that, if you prefer: https://www.npmjs.org/package/underscore-semver

For the curious, or if you'd like to do something similar, here's the script I'll be using to keep things in sync: https://gist.github.com/jashkenas/c71021bba8ee580ded92

I hope that's an acceptable solution.

I'm sorry @jashkenas thats a ridiculous response. What is the human readability issue about v2.0 over v1.7. Underscore breaking releases don't have to be more than once a year. Moreover they don't all have to be breaking as the underscore-semver repo seems to indicate...

All people are asking is when you change the contract (not extending the contract) of the function, you let them know via a major bump.

Additionally the an issue here is nested deps -- things that rely on underscore that you in turn rely on. Now, each of those packages needs to be updated and published to point to a new repo as well. Things that may not be well maintained may be forever broken now.

Tl;DR goodbye underscore, hello lodash

The most useful thing you can do with a version number is not break people's code.

Regardless of opinions on semantics, it was obvious before this release that using 1.7.0 as the new version number would break a significant amount of code, likely in a non-obvious manner. I've yet to hear any reasoning that could justify that loss in user trust and productivity.

Having two identical repos with different versioning schemes is going to lead to even more headaches than having nonstandard versioning.

It's one thing to state, @jashkenas, the way in which you believe version numbers should be used. The underscore-semver package, though, is a straw man: none of us is suggesting that Underscore should be at v170.0.0 by now!

@jashkenas -- Wait, so when people came to your project with real concerns about the health of the node.js ecosystem and the expenditure of thousands of man-hours fixing things and hunting bugs, your response was to write a script to sync something over to a version called "v170.0.0"? (which also does nothing to address the issues they identified with an existing install base)

seriously? don't you think that's kind of going to alienate parts of the community? it's a whole lot easier to read that as spite than as an attempt to be helpful... you're in a position of some responsibility and authority here, and this sort of thing just lets everybody down.

Cross-posted from https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e:

Spurred by recent events (https://news.ycombinator.com/item?id=8244700), this is a quick set of jotted-down thoughts about the state of "Semantic" Versioning, and why we should be fighting the good fight against it.

For a long time in the history of software, version numbers indicated the relative progress and change in a given piece of software. A major release (1.x.x) was major, a minor release (x.1.x) was minor, and a patch release was just a small patch. You could evaluate a given piece of software by name + version, and get a feeling for how far away version 2.0.1 was from version 2.8.0.

But Semantic Versioning (henceforth, SemVer), as specified at http://semver.org/, changes this to prioritize a mechanistic understanding of a codebase over a human one. Any "breaking" change to the software must be accompanied with a new major version number. It's alright for robots, but bad for us.

SemVer tries to compress a huge amount of information — the nature of the change, the percentage of users that will be affected by the change, the severity of the change (Is it easy to fix my code? Or do I have to rewrite everything?) — into a single number. And unsurprisingly, it's impossible for that single number to contain enough meaningful information.

If your package has a minor change in behavior that will "break" for 1% of your users, is that a breaking change? Does that change if the number of affected users is 10%? or 20? How about if instead, it's only a small number of users that will have to change their code, but the change for them will be difficult? — a common event with deprecated unpopular features. Semantic versioning treats all of these scenarios in the same way, even though in a perfect world the consumers of your codebase should be reacting to them in quite different ways.

Ultimately, breaking changes are no fun, and we should strive to avoid them when possible. To the extent that SemVer encourages us to avoid changing our public API, it's all for the better. But to the extent that SemVer encourages us to pretend like minor changes in behavior aren't happening all the time; and that it's safe to blindly update packages — it needs to be re-evaluated.

Some pieces of software are like icebergs: a small surface area that's visible, and a mountain of private code hidden beneath. For those types of packages, something like SemVer can be helpful. But much of the code on the web, and in repositories like npm, isn't code like that at all — there's a lot of surface area, and minor changes happen frequently.

Ultimately, SemVer is a false promise that appeals to many developers — the promise of pain-free, don't-have-to-think-about-it, updates to dependencies. But it simply isn't true. Node doesn't follow SemVer, Rails doesn't do it, Python doesn't do it, Ruby doesn't do it, jQuery doesn't (really) do it, even npm doesn't follow SemVer. There's a distinction that can be drawn here between large packages and tiny ones — but that only goes to show how inappropriate it is for a single number to "define" the compatibility of any large body of code. If you've ever had trouble reconciling your npm dependencies, then you know that it's a false promise. If you've ever depended on a package that attempted to do SemVer, you've missed out on getting updates that probably would have been lovely to get, because of a minor change in behavior that almost certainly wouldn't have affected you.

If at this point you're hopping on one foot and saying — wait a minute, Node is 0.x.x — SemVer allows pre-1.0 packages to change anything at any time! You're right! And you're also missing the forest for the trees! Keeping a system that's in heavy production use at pre-1.0 levels for many years is effectively the same thing as not using SemVer in the first place.

The responsible way to upgrade isn't to blindly pull in dependencies and assume that all is well just because a version number says so — the responsible way is to set aside five or ten minutes, every once in a while, to go through and update your dependencies, and make any minor changes that need to be made at that time. If an important security fix happens in a version that also contains a breaking change for your app — you still need to adjust your app to get the fix, right?

SemVer is woefully inadequate as a scheme that determines compatibility between two pieces of code — even a textual changelog is better. Perhaps a better automated compatibility scheme is possible. One based on matching type signatures against a public API, or comparing the runs of a project's public test suite — imagine a package manager that ran the test suite of the version you're currently using against the code of the version you'd like to upgrade to, and told you exactly what wasn't going to work. But SemVer isn't that. SemVer is pretty close to the most reductive compatibility check you would be able to dream up if you tried.

If you pretend like SemVer is going to save you from ever having to deal with a breaking change — you're going to be disappointed. It's better to keep version numbers that reflect the real state and progress of a project, use descriptive changelogs to mark and annotate changes in behavior as they occur, avoid creating breaking changes in the first place whenever possible, and responsibly update your dependencies instead of blindly doing so.

Basically, Romantic Versioning, not Semantic Versioning.

All that said, okay, okay, fine — Underscore 1.7.0 can be Underscore 2.0.0. Uncle.

(typed in haste, excuse any grammar-os, will correct later)

Kudos Jeremy for making this happen. So will there be a 1.7.1 reverting the (breaking) changes? (Or reverting to pre-1.7.0?) That'd be double-awesome.

+1 @jashkenas, thanks for listening.

1.7.1 — then 2.0.0 when @megawac is ready to roll with it.

Node doesn't follow SemVer

Just to clarify, node is pre 1.0 which allows to break without bumping major version numbers. See point 4 of semver.

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

Though most people agree that node being pre 1.0 at this point with 1000s of companies using it in production is a bit of a joke. At least if you take SemVer seriously. If not, it's par for the course.

Just to clarify, node is pre 1.0 which allows to break without bumping major version numbers. See point 4 of semver.

I added a paragraph for you:

If at this point you're hopping on one foot and saying — wait a minute, Node is 0.x.x — SemVer allows pre-1.0 packages to change anything at any time! You're right! And you're also missing the forest for the trees! Keeping a system that's in heavy production use at pre-1.0 levels for many years is effectively the same thing as not using SemVer in the first place.

Cheers!

@jashkenas 👍

From HN:

I can accept everything you have written here - my feelings towards semver are very similar to your own. I feel in this instance what transpired was a deviation from what you have written - I went to start a new project and 2 of the dependencies I installed (out of 10) had problems, this release changed a lot of stuff that affected many of the dependent modules.

If the asshole responsible...

If anyone was the asshole it was me, however I truly believe that underscore is better off and I would like to thank you for all the work you have put into it. :)

I think all meaningful discussion has been had, you may want to lock this issue. Thanks again for all your hard work.

Thanks for underscore, @jashkenas. I think what you've done is reasonable, and I hope this issue will get more people to lock to major/minor rather than locking to the major version in their manifests.

@jashkenas you can always say that v2+ will no longer follow semver and to always depend on underscore with a ~

@jashkenas Your argument against SemVer is surprisingly solid. And I agree with basically all of your points.

I do however have one point/question, and I'd be curious to hear your response.

I might be completely alone with this opinion, but I tend to categorize software packages into three main categories: libraries, frameworks, and applications.

And I can easily understand how applications like MS Office, and Photoshop would just confuse the shit out of people if they used SemVer. Versioning becomes a marketing issue more so than a development one for them. And I tend to think the same applies to a lesser extent to frameworks like Rails for example.

However, with a low-level library by programmers for programmers, it makes less sense to me. Obviously any change to a library I use should be reviewed rather than blindly accepted. But at the same time I'd also like to have a heads up about how big of a change and potential time drain a package version bump might be.

Personally I've found that if a project is not following SemVer, it makes me end up treating any version bump as if it was a major version bump where I'd have to assume everything will break and I might need to spend days dealing with aftermath of the upgrade.

Potentially this is a good thing, but for the most part for me it means a patch version is released with a security fix for example, and because of time constraints I don't even bother looking at it until some point in the future when I have time to deal with any potentially massive and/or breaking changes. While in contrast if it's a library I know uses SemVer, I'd quickly spend those 20 seconds to 5 minutes to see what's changed, apply the update and get the security fix.

And as a final argument for the use of SemVer in library development. If I'm building a project or even another library, that depends on underscore and other libraries. I would prefer to at set the dependency version to 0.0.x so users of my library might get bug fixes from patch versions of my dependencies without me having to update my library too whenever any of my dependencies are updated.

In short, the version number representing the conceptual state of the software makes sense for applications, and to a lesser extent frameworks too. But much less so for a library. If I'm gonna take the example to the extreme:

A library could be rewritten to improve it's internal code structure, and in the process completely changing it's API, but without adding any new features. And so because there's no new features, it could arguably only merit a minor version bump, cause conceptually the library still only does the exact same thing and hasn't really changed in terms of it's conceptual state. This would be a headache, cause arguably this minor version bump should have been rename to a new project as it's API is completely different.

I don't think this is an argument against semver, but an argument for the semver used in practice rather than the semver exactly as described in the spec. If you lock to the major version alone, you're probably going to have problems, whether you're using npm, rubygems, or another package manager that uses semver. I hope the semver spec will be updated to something closer to what's used in practice, or its maintainers will point to a successful widespread application of it, for us to draw inspiration from.

SemVer allows pre-1.0 packages to change anything at any time!

Which is why if you are depending on a pre-1.0 package, you should have that shit locked down and be especially attentive of the changelog when upgrading.

After releasing 1.0, a package author is making a statement that they are confident in the decisions they've made and are willing to maintain backwards compatibility until 2.0.

You can also do this in future major releases with a 2.0.0-beta.x stage, where you can make breaking changes several times until you're happy with the results and craft the final major release afterwards.

The real point of SemVer (at least in my mind) is to be a structure for a package to build up a level of trust with its users. In other words: a package maintainer/contributor should be actively thinking about the changes they're making and how it will affect others.

We strictly follow SemVer in the Marionette, which is why we regularly see people upgrade from early releases of v1 all the way up to the last release of v1 without changing a single line of code. They get dozens of new features and bug fixes for free.

Then when we bumped to v2, we had a massive set of changes (we also made several 2.0.0-beta.x releases). However, we were careful that there was a clear upgrade path. With an even more detailed changelog, and even an upgrade tool. We even have a nice roadmap for transparency. As a direct result of this, our community puts a lot of trust in us.

I love Underscore and Backbone, that being said I have trouble trusting them.

@jimeh The trouble with that as I see it is, semver doesn't allow you to distinguish between

  1. Large breaking changes that affect everybody significantly
  2. Breaking changes that affect obscure users significantly
  3. Breaking changes that affect everybody trivially
  4. Breaking changes that affect obscure users trivially

I would argue, based purely on the meaning of "major", "minor", and "patch", that case 1 should bump major, cases 2 and 3 should bump minor, and we can argue about case 4.

Semver forces all cases to be considered "major". This is unnecessarily pessimistic about the actual work required to upgrade, and a user who locks to major will not get updates that could be free or near-free level of effort.

It's also unrealistic about the maintainer's responsibility. Probably any change could conceivably break some code, somewhere, if you think hard enough. Packages like underscore-semver are partly a joke, but they're partly an honest reflection from a software developer who works for free that he's not going to realistically invest the time to test every change against an unknown and variable downstream ecosystem.

Finally, no version scheme can really protect you against breaking changes. Even if you had the QA team to plausibly say you are confident that 1.0.1 didn't break anything (which pretty much no project that uses semver actually does), being confident and being right are two different things. You will still release a .patch release that breaks things now and again, and so downstream needs some plan other than "look at the version number" to ensure that upgrades don't break behavior.

I did find @jashkenas' argument pretty good.

But the fact remains that SemVer is useful and essential (at least for our team) to avoid dependency hell. Libraries are great, they save code and time, but I don't want to have read every single changelog of every single package of every single application we have whenever they update. That would end up taking hours each week! Knowing and for the most part feeling confident in the fact that any *.x.x will work, x.*.x will contain some fixes and improvements/features, and x.x.* will usually just contain fixes, is pretty vital to being able to maintain dependencies efficiently.

Honestly, not mentioning any names, I'd much rather use a package which tries to maintain this compatibility scheme - or at the very least fails verbosely and descriptively if a breaking change is absolutely necessary - rather than one which rejects the good (SemVer) for lack of the perfect.

SemVer tries to compress a huge amount of information — the nature of the change, the percentage of users that will be affected by the change, the severity of the change (Is it easy to fix my code? Or do I have to rewrite everything?) — into a single number. And unsurprisingly, it's impossible for that single number to contain enough meaningful information.

I would agree with @jashkenas if this were true, but it isn't. The percentage of users affected by the change isn't denoted anywhere, the severity isn't denoted anywhere, ease of fix isn't denoted anywhere, and rewrite requirement isn't denoted anywhere. That was complete and total garbage.

  • Major versions contain changes to previously existent functionality, and can break your code.
  • Minor versions contain additions to previously non-existent functionality, and can't break your code.
  • Patches contain changes to previously existent functionality that is broken, and can not break your code (unless you're relying on the bug – which means you aren't using the functionality as documented).

I understand the attachment to the romantic notions of v2.0 and v3.0 as full code rewrites, but that's not how the rest of the community works. Get it fixed, or get forked.

P.S. Why did you publish the package underscore-semver to NPM as v170.0.0? It wasn't funny, and NPM squatting isn't welcome – especially when you're taking up a useful name (since underscore apparently doesn't follow SemVer).

"I'd much rather use a package which tries to maintain this compatibility scheme - or at the very least fails verbosely and descriptively if a breaking change is absolutely necessary - rather than one which rejects the good (SemVer) for lack of the perfect."

Thank you @connor4312 for this.

It seems to me there is not one person who has an argument against SemVer and can offer a viable alternative. If SemVer is so bad, tell me what's better, I'll give it a go.

You guys don't want to wait for new features in 1.7 and are stuck on 1.6? Don't wait for the official release, use a release candidate. They have all the newest features for beta testing before they go public, that's what they're for. If you want to be a beta tester, want all the latest and greatest immediately, fantastic. We need people like you to use new code and report on any bugs so when it does go public, the rest of use have a smooth upgrade.

Unfortunately, I'm not one of those people. At least not all the time, I'm definitely an early adopter on side projects. I upgrade all my dependencies to their pre-alpha-release-candidate versions (like the ones that are pretty much telling you there are more bugs than features), just so I can tinker around with new behavior.

However, when you maintain a large codebase, written before your time, with many dependencies, you can't review the change log for every patch. That would take most of my time, and I have my plate full of bugs and new features I need to add to my own code. I schedule all my tasks. The scope of the work directly affects when and if I'm going to do it; everything is planned out. Today I'm doing items 1, 2, and 3. I can squeeze in the 3rd because item 2 (upgrade a library) is a minor version upgrade, should only take a minute. As it turns out, the change broke a bunch of my tests, I have to go identify the problem, and before you know it I'm either making widespread changes to my codebase, or I'm reverting the upgrade. Option 1 puts me over budget on time, I can't get everything done, so now I look bad. Option 2 forces me to not complete item 2, I can't get everything done, so now I look bad.

Simple solution: review the change log. It's not difficult, and the change log for 1.7 stated outright _.template was no longer backwards compatible. Had I read it carefully, no issue. I'm fine with that, from now on I know not to trust Underscore's versioning scheme, since I (as did everyone it seems) assumed it ran on SemVer. Or even better, don't adopt new versions immediately. I assumed a version, at the time it went public, would be thoroughly tested for obvious problems like this. Lesson learned, don't make assumptions like that, stay a version behind to play it safe. That being said, I don't really care what kind of versioning Underscore uses, I just want to be on the same page as the maintainers. But to be honest, if the version reflects the aesthetic progress of a library in a "human" format rather than an indication if my code will work, what do I care as a user if Underscore is upgrading from 1.7 to 2.0? I don't, at all. It's just another upgrade to me. 1.7 to 2.0 in SemVer means something.

I'm not saying anyone is in the wrong here, I think it's just simple miscommunication between how the maintainers and users treat versioning of the library. And I think its safe to say we're all on the same page now. The only concern I have is through this whole experience, I feel like I lost some trust in the library. And that's just disappointing.

Don't hate the tool, hate how it's used. SemVar is ultimately about the library owner being fully aware of how much trouble breaking changes can have. Hypothetically, the new _.template could have stayed in a stable 2.0.0-beta-x branch indefinitely, waiting for breaking changes to accumulate. When it got to 2.0.0-beta-19 and the community felt that it was time to commit to the new API rather than hide behind betas, that's when 2.0.0 can be released. The arduousness of the process is motivation to think very carefully about adding functionality in the first place.

And yet. Fracturing the codebase with pent-up changes just makes it hard to switch (see: Python 3). SemVar removes a lot of the freedom from the developer to make minor but breaking changes to improve an API by making it more flexible, more consistent, or the like, which just entombs the wrong way. In some sense it's best to rip off the band-aid quickly and be done with it. Because mistakes happen, and adding strict supersets of behavior isn't always plausible. Software development is hard, and we need some freedom to go and fix our inevitable mistakes.

Just to add my 2¢...
All of this is about a social contract. It has nothing to do with prioritizing a machinistic understanding over a human one. Instead it is about the realization that the person that is patching is not always a developer

Semver creates a social contract that says to an admin; if it is a minor or patch release apply it. Otherwise, wait for your dev team to evaluate it. This enables things like security fixes to be applied faster than the normal change cycle. That is an important piece that we shouldn't loose.

That said, Having a way to indicate the magnitude of changes would be a welcome extension. However, I don't think that need merits throwing the baby out with the bath water to get it. Instead, couldn't we solve both needs by adding a revision number, or commit count, to the end of Semver?

I guess what troubles me is that you're all complaining to Jeremy about his responsibilities to your projects, even though you didn't take the responsible step of avoiding NPM's default '--save' behavior, which is to use ^n.n.n. This default behavior of NPM is braindead and has always led to weird versioning problems with any number of other libraries. You also assume that underscore is exclusive to the node ecosystem, which it most certainly is not. I'm not sure if Jeremy needs to apologize for that or to act on it. Start taking control over your projects, use '~n.n.n'

@kode4food I can do that, but I can't make all the dependencies which in turn rely on underscore do that.

@danielchatfield pull request and then cross your fingers?

I think a new property in the package.json for npm would be nice, so that projects could provide a reasonable default for npm install --save. So projects which use semver could set this to ^x.x.x and others might set it to ~x.x.x or even x.x.x.

@niclashoyer I just read the entire thread and you're the first person with a meaningful solution to the problem. To all the SemVer haters I say of course there are problems, but

  1. no one has really given a better alternative other than @niclashoyer here.
  2. You say I should take the time to upgrade my dependencies manually. But guess what I do. When a major version changes, I go and make the upgrade manually and deal with the changes, but it's nice to know that sub-dependencies are something I don't have to worry about. Not following SemVer helps no one. Following SemVer hurts no one. Of course it's not perfect and it doesn't talk about the stage a project is in to humans. Guess what, I think You should just start using a separate number for humans and a separate number for machines!

Guys, read and learn from others.... they have already solve this problem.

@niclashoyer Absolutely +1, that would best for everyone.

@drewcrawford I have a few arguments for you.

TL;DR:

  • Let the version number indicate the version of the public API of your library. As a user of a library I don't care if the doCoolShit method was completely rewritten in a mind blowing way, as long as the public API was not (intentionally) changed.
  • SemVer allows library authors and users to quickly infer the intention of any specific update based on it's version number.
  • GutVer (bumping version numbers when it "feels right") will mean wildly different things to different people.
  • SemVer isn't perfect, nor are humans, nor are computers. Bugs will always happen. But SemVer at least makes developers instantly more informed about any specific library update they pull in compared to GutVer.
  • For things like singular software libraries that expose a single public API, like Underscore.js, I think SemVer makes perfect sense.
  • For frameworks like Rails, and programming languages like Ruby and Python it makes less sense, as they're typically a massive composition of hundreds of different public APIs. Also these projects pre-date SemVer and have their own established conventions about version numbers.
  • And finally for applications like MS Office, and Photoshop, SemVer makes little to no sense, as their version numbers is more of a marketing device than a real version number.

The long version:

The trouble with that as I see it is, semver doesn't allow you to distinguish between

  1. Large breaking changes that affect everybody significantly
  2. Breaking changes that affect obscure users significantly
  3. Breaking changes that affect everybody trivially
  4. Breaking changes that affect obscure users trivially

I would argue, based purely on the meaning of "major", "minor", and "patch", that case 1 should bump major, cases 2 and 3 should bump minor, and we can argue about case 4.

Please define what you mean with obscure users. The only thing I can think it might mean is people who rely on private methods from a library, at which point those obscure users are simply on their own. Which makes points 2 and 4 irrelevant.

And also, how do you define if something will effect users significantly or trivially? I would imagine that a lot of the time it would depend a lot more on the user's own code base using the library, than the library itself. Aside from maybe completely changing the way you interact with the library.

This leaves me with two points (1 and 3) to tackle:

  1. Large breaking changes that affect everybody significantly
  2. Breaking changes that affect everybody trivially

I would personally consider both cases require a major version bump. However, this is in the case of a library, in the case of Rails, or say MS Office like I previously stated, SemVer doesn't make as much sense.

When it comes to a library, to me SemVer is not about representing the internal conceptual state of the code-base. As a user of a software library, I couldn't care less if it's doCoolShit() method was completely rewritten in a much better and cleaner way.

To me the version number of a library represents the state of the public API to the library. This gives me a quick overview of the intention behind any update based on the version number:

  • Changes to the major version means the public API has changed and code using the old API will probably not work correctly anymore.
  • Changes to the minor version means the public API has been expanded in one or more of the following ways:
    • Additional methods added.
    • Existing methods were given extra arguments at the end of their argument list.
    • Callbacks might be given additional arguments.
    • Methods return values might contain more detailed information (if return value is an array/hash).
  • Changes to the patch version means the public API has not been changed at all, but the underlaying code has some sort of change, like a bug fix, performance improvement, internal code cleanup/restructuring.

And to tackle your last points:

It's also unrealistic about the maintainer's responsibility. Probably any change could conceivably break some code, somewhere, if you think hard enough. Packages like underscore-semver are partly a joke, but they're partly an honest reflection from a software developer who works for free that he's not going to realistically invest the time to test every change against an unknown and variable downstream ecosystem.

Finally, no version scheme can really protect you against breaking changes. Even if you had the QA team to plausibly say you are confident that 1.0.1 didn't break anything (which pretty much no project that uses semver actually does), being confident and being right are two different things. You will still release a .patch release that breaks things now and again, and so downstream needs some plan other than "look at the version number" to ensure that upgrades don't break behavior.

I think this is what your issue boils down to, you want a versioning system that solves all issues, and because it doesn't let's just go with version numbers that "feel" right. What feels right to one developer is often completely different than what feels right to another.

Bugs will always happen, as long as humans write software at least. But we can at least try to stick to some sort of convention. SemVer allows me to at a glance infer the intention of any specific update to a library based on which of the three numbers changed. Of course that doesn't mean bugs will not be introduced, but it gives me valuable information right up front without having to spend time digging through a changelog to see what the update is supposed to do.

At the end of the day SemVer isn't perfect, but at least it's understandable. While GutVer (as I'll call it when versions are bumped based on how the dev feels about the changes) means wildly different things for different people.

A somewhat decent example I guess is Adium, when it was rebooted for v2.0, it ended up being Adium v2.0 v0.1, so v0.1 of "Adium v2.0". Like how do you make sense of that? Admittedly as the old v0.x and v1.x fell into obscurity they removed the 2.0 from branding around v0.9 if I recall, but it still stores it's user data in a "Adium 2.0" folder for example.

“GutVer” is a great term, @jimeh. No author should be making a value judgement of obscurity or triviality, especially for a popular library. Get it documented, and communicate your intent.

@jimeh

Your argument, and other comments here, characterize me as holding SemVer to some standard of perfect, finding it wanting, and saying it's bad. That's not true.

My argument is that it offers zero or near-zero value. It is not a case of it not being perfect; it is a case of it not being even good.

The "value" that SemVer purports to provide is that a non-human package manager can make decisions about whether to upgrade the package. But if mistakes can be made--an author can accidentally twiddle the public API in a minor version--then no sysadmin should every rely on this to be right in production. To delegate production updates to a system that can fail is, quite frankly, irresponsible administration for production. The sysadmin must, in 100% of cases, verify the update manually, since the author can be wrong, and the mistake isn't going to cost the package author, it's going to cost you. And now what has SemVer done for you?

What instead GutVer does (or FerVer is, I think, a sensible alternative really) is it says well, breaks are a fact of life. Let's communicate something to a human about how severe we think the break is. Is it big, is it small? And now the human sysadmin tasked with updating the package makes a call. They may even plan a release schedule and allocate upgrade time based purely on the version number. I would allocate different time for updating Python 2->3 than Python 2.6->2.7 for example. That is something that cannot be done with SemVer at all, because all API changes are considered the same severity level.

Sure it's not objectively machine-readible but SemVer is only machine-readible if you are an irresponsible sysadmin who delegates updates to a faulty machine, which is not a case worth considering. In all other cases, SemVer provides zero or near-zero value, and GutVer/FerVer offers the clear value of being able to estimate the time required to make breaking updates.

Finally, your dismissal of all the projects that don't use SemVer for some reason (even more than I listed, you've added MS Office and Adium to the mix) I think just provide additional ammo that SemVer doesn't solve the problems of many real software projects. Apparently SemVer isn't appropriate for "frameworks" or "programming languages" or things where there is a "marketing issue" or Adium for some unspecified reason, which are vague enough holes that I can drive a truckload of my own programming projects through them and avoid using SemVer for them.

The thing is, is that it is easy to make claims that X is good in some theoretical way, as we are doing here. And it is easy to attract some early adopters that share your belief that X is good. But when major software projects, fully aware of X, decide not to adopt it, now we have empirical evidence that X has serious practical problems. From what I can see, the response is to declare them out of scope in some way ("silly framework, semver is for non-frameworks") rather than find out what their problems are and try to fix the standard so it will work for them.

On that basis, you might as well just declare me out of scope along with them.

@drewcrawford Updating production code wasn't the use case that started this issue - everyone acknowledges that you shouldn't update production code without testing. I don't know why people keep bringing this up, not a single person has said that this broke production code because they just blindly updated.

I started a new project and installed 10 dependencies, 2 of them were broken because of this. The only way to fix this is to manually create a lock file to pin the dependencie's dependencies down (after spending hours working out what was wrong).

I completely agree with the criticism of semver and I would even be ok with putting out really minor "breaking changes" on a minor release (where we are talking about breaking just a couple of dependent modules) but this release contained substantial breaking changes that broke hundreds of dependent modules and initially many of the changes were not even documented or included in the changelog. It cannot be the state of affairs that a minor release of underscore renders hundreds of packages uninstallable. And I don't accept that the solution is for all these dependent modules to pin the version down and therefore have to keep releasing updates containing just a version bump.

While it didn't break production code for me. It wasted a good hour of my time before I realized what was going on and had to go research the changes in underscore templates. I honestly thought I was going nuts until I saw the changes in 1.7.0.

@danielchatfield I actually agree with you. A release that breaks a lot of downstream modules should be considered "major".

Why are people not discussing http://sentimentalversioning.org/ here?

Jeremy calls his versioning system romantic (which is very sentimental of him) It looks like semver (major.minor.patch), but with a personal meaning.

@jashkenas thanks for rolling it back, but #senver FTW!

I think we can combine all the requirements discussed in this thread, and all of semantic and romantic versioning with a superset of both numbering schemes that uses 4 versions and reads like "A.B.C.D".

  • A: a major breaking change like a rewrite or complete overhaul of the API
  • B: a minor breaking change that doesn't justify a completely new A, but clearly signals the breakage it introduces
  • C: a non-breaking extension of the API
  • D: a bug fix that doesn't change the API at all

If following that paradigm, Underscore would have gone from version 1.6.0.0 to 1.7.0.0 now, and everybody would be happy. @jashkenas (who makes good points) could keep his meaningful human-readable version numbers for all of us to enjoy. And NPM (which has good points for needing SemVer to do its job) could handle the breaking change appropriately, for all of us to enjoy as well.

3 numbers will never be enough to express 4 different things, no matter how one looks at it.

@kevgo The only problem being, the number of different things are way more than 4.

And @jashkenas hinted at needing lesser manually specified parameters which have to be cognitively handled by a developer, not more

I would be happy to get behind a 4-component major.minor.nonbreak.bugfix standard. In fact I proposed such a change with semver in this thread.

Unfortunately for semver, calling "minor" a breaking change is too radical a thing for them, even though lots of software does it (Python, Rails, Django, just to name a few).

So I would sign on to such a standard, but we won't be calling it semver.

@drewcrawford I think it's a case of the tradeoff between human-readable and easily understood, vs more information for the machine to infer from. The current 3-component format seems a good balance in general, for you would be too worried deciding on the right new version number for your packages otherwise.

The right solution would still be something involving more automation, than manual entry.
It just needs to be built, and I think this discussion (and @jashkenas's well thought out reply) might just lead to one.

@asyncanup I disagree strongly that 4-component schemes introduce analysis paralysis. For authors that agree with semver that all API changes are major, they could simply interpolate a ".0." into their existing numbers and carry on as usual. There is nothing to agonize about.

I'm not opposed to solutions that rely on automation to solve this problem, but as usual the devil is in the details, and it's difficult to make any intelligent comment in the abstract.

I think I'm on board with semver now, with versions regularly going over 10. It seems fine to me that Chrome will turn 40 by the end of the year. It will take some getting used to it but it's worth it IMHO.

@drewcrawford What I propose would not replace or "change" existing SemVer, but extend it by adding a larger version in front (maybe call it the "generation"). The lower 3 versions (B, C, D) would map to the same semantics as defined by SemVer.

@jdalton You're my hero

just use lodash :)

when is the version 1.7.11 solving the problems of 1.7.0 expected to be released?

@harrysingh The bump is being discussed over on #1855.

This is the most useless thread I've read, ever.
Intentionally ignoring semver at this point in time is just a very bad thing.

(ノಠ益ಠ)ノ彡┻━┻

if i saw a 0.x.0 update i sure as hell would be reading changelogs...

https://www.npmjs.com/package/npm-check

Underscore and Backbone are so dead now...lol

Dead or alive they deserve respect.