nodejs / version-management

Discussion Group for Version Management
MIT License
42 stars 14 forks source link

Agree on a standard format for a `.node-version` file? #13

Open ljharb opened 7 years ago

ljharb commented 7 years ago

It seems like, at a minimum, this would need to support v4.5.6 and 6.7.8 - ie, static versions, both with and without the v.

It would certainly not support aliases, since a) that varies widely by version management tool, and b) the point of an "x-version" file is to target one specific version.

This raises the question, however, about io.js versions, which it should also support. Should iojs-v1.2.3 be allowed? If not, v1.2.3 would still unambiguously point to an io.js version. What about a future where there's more than the version number to target - like, "engine" or "architecture"?

marcelklehr commented 7 years ago

I agree with the minimum of static versions and would cast a vote for treating iojs versions and node versions as one range.

Something I'd like to throw into the ring:

the point of an "x-version" file is to target one specific version.

I'd like to question this. npm's package.json dependencies field has demonstrated the power of version patterns and semantic versioning. Usually, what people want is not to run 6.1.3 forever, but e.g. run the latest version in the 6.x series. So, if .node-version only holds static versions, that would mean having to update the file in every project every time a new version is released. This is not a user experience I'd advocate, but, I look forward to hearing your opinions on this.

koenpunt commented 7 years ago

that would mean having to update the file in every project every time a new version is released.

That's how rbenv works, and from what I read on the documentation this accounts the same for rvm.

koenpunt commented 7 years ago

Although the reasoning behind it isn't really applicable to node, here is @mislav's take on why .ruby-version has to be precise: https://gist.github.com/fnichol/1912050#gistcomment-682506

marcelklehr commented 7 years ago

The examples from https://github.com/nodejs/version-management/issues/14#issuecomment-269030366 would also mean fuzzy constraints in .node-version files are out of the question.

In classic node fashion, I would propose a JSON-structure for .node-version files:

{
  "version": "1.2.3"
}

Extensible as follows:

{
  "version": "1.2.3",
  "engine": "..."
}

On the plus side, it's extensible, it's simple and still readable. On the downside, it's a departure from how .node-version files are used currently.

ljharb commented 7 years ago

Another downside, parsing JSON without node available (since version managers generally need to operate without node available, like nvm) is very difficult.

koenpunt commented 7 years ago

@marcelklehr package.json already provides that. The purpose of a .node-version file would be simplicity.

marcelklehr commented 7 years ago

So, you are talking about ease of implementation. I think not having node (or any json parsing platform) available is a superficial constraint, since the user needs to install something, why can't it be a version manager that runs on node? It can be. nvs, for example, shows that.

I'm all for re-using package.json for this, but someone else said, the purpose of .node-version files is that not all environments use npm and thus have a package.json (as per https://github.com/nodejs/version-management/issues/12#issuecomment-269023427). If the purpose is to be able to easily read a file within a shell script then that's certainly the way to go. I find that purpose not convincing, though, since a version manager should work across all platforms, so choosing a programming environment less archaic than shell scripts is in order anyway.

ljharb commented 7 years ago

@marcelklehr yes, and a version manager implemented in node might be a great solution. but choosing a configuration format that imposes constraints like that on which implementations work is untenable - anything we choose absolutely must work for existing solutions as well as future ones, and nvm relies on posix, where adding a JSON parser is a heavyweight requirement.

marcelklehr commented 7 years ago

So, you're arguing, in fact a posthumous standard is necessary that works already, instead of a new standard. Fair point. I was assuming a new standard would be a good choice since a) we're trying to integrate more than just the version and b) .node-version contents are already incompatible. I don't necessarily agree that anything we choose here must work for existing solutions, but then I would rather see a new 'official' version manager than a fully backwards-compatible standard that all version managers slowly gravitate to.

ljharb commented 7 years ago

How are they already incompatible?

marcelklehr commented 7 years ago

Some version managers allow aliases, some don't, some treat io.js as a separate version range with its own tag, some don't, some provide fuzzy matching, some don't.

ljharb commented 7 years ago

That doesn't mean they're incompatible, as long as there is a union of consistent behavior. In other words, if all accept static versions with and/or without a v prefix, then that's compatibility.

marcelklehr commented 7 years ago

I think there's a mere intersection of consistent behavior. How would you integrate architecture and engine, then, without losing this compatibility?

ljharb commented 7 years ago

Sorry, s/union/intersection, is what I meant :-)

I don't know how, that's an important question. Perhaps on a second line, since it's likely that tools currently only grab the first line of the file?

It really depends what the parsing rules are for all the current engines. They may ignore everything after the third version number grouping; they may ignore everything after the first line - or they may hard-break if any unrecognized input exists. We'll have to research it.

philsturgeon commented 6 years ago

Bump. Where is this at? https://github.com/creationix/nvm/pull/1625 is a-waiting. :)

felixfbecker commented 6 years ago

Just wanted to throw this in here, ps-nvm supports both .nvmrc and reads package.json's engines.node field, with full npm semver range support. Because parsing JSON in PowerShell is trivial ;)

ngryman commented 6 years ago

I think that all node version managers should agree on this without having any feedback from the Node core team. Nothing against the core team but I don't think they'll make this a priority and we might be waiting a long time before things move on. Plus a bunch of node version managers already support it: avn, nodenv, fnm (I probably forget some).

So, I would propose that .node-version become a defacto standard. It should be a plain text file containing a semver or an alias (to be defined precisely). The reason it should be plain text is to allow easy processing for limited tools (aka no easy JSON support).

What do you think? Should we put an RFC somewhere and gather all Node manager authors around it? @ljharb Are you up for this?

Thanks!

ljharb commented 6 years ago

@ngryman the "core team" is those in the Version Management group, which just happens to be many of the maintainers of "all node version managers". It is us that have been unable to make a decision; we're not waiting on "node core" for anything whatsoever.

This very issue is where such an RFC should be posted, if someone comes up with one. The above comments pose problems that nobody has yet suggested a solution for; any RFC should address them before being posted.

marcelklehr commented 6 years ago

It looks like I have been the only one opposed to "prematurely" fixating on a de facto standard :) (Sorry, for my absence lately, too.)

I can see that not using a well-defined markup format for this standard makes sense for maintaining compatibility to the status quo. Thus, in the interest of extensibility I would definitely welcome some loophole that allows extending the data in the file. E.g. stressing that this standard only concerns itself with the first line of .node-version files seems very handy.

ljharb commented 6 years ago

The concern about limiting ourselves to one line is that then we can't do other things like specify npm version, or node-gyp version, etc.

marcelklehr commented 6 years ago

The idea is to limit the standard to one line, for now, as this seems to be the common denominator. If some version manager wants to specify the npm version, they might do so on a second line, as you pointed out yourself, while others would ignore that line if they don't recognize its meaning. So, we'd have partial compatibility :'D

On the other hand, it's probably smarter to standardize as much as possible, with the option to ignore it. So, we could have the full node version in the first line as mandatory, and afterwards a key=value pair per line, that can optionally be respected by the version manager, if it recognizes the key. If it doesn't recognize a key, it might throw a warning but would still work. If it ignores everything after the first line, it still works, partially, as intended. We should of course standardize a few common keys and appropriate values for those.

ljharb commented 6 years ago

I like that general approach - something like "first line is nothing but the node version, further lines must either be blank/only whitespace (ignored), begin with "#" (a comment, also ignored), or be in the format key=value (leading and trailing whitespaces are trimmed)"

ngryman commented 6 years ago

@ljharb Thanks for the precisions and sorry for my misunderstanding here. My goal was more to give a new impulsion to that topic more than blaming anyone, which I wouldn't dare to! And it seems that you guys are now discussing it, so yay!

Please allow me to give my point of view on this one. I think that .node-version should be as simple as it can be: a one-liner node version, that's it. To me, the justification behind this is that:

For the other "20% checks" (which are basically 80% of the time npm, Pareto inception), I think we should pass the torch to the engines field of package.json since a project checking against an npm version should always have a package.json.

So to sum up:

ljharb commented 6 years ago

That's also a reasonable approach.

Even in the "one-line" part, we'd have to figure out a union of semantics:

etc.

koenpunt commented 6 years ago

Might be good to follow the .ruby-version convention, which is also is a single static value.

marcelklehr commented 6 years ago

I agree that simplicity as a default is good. My reason for suggesting additional optional key-value lines was that node potentially has more properties than just the version, i.e. "engine" or "architecture" as mentioned

marcelklehr commented 6 years ago

Regarding fuzzy matching, I think installed native modules and modules that install other stuff based on the node version could break, if a fuzzy match caused the used node version to be updated without reinstalling the node modules.

ljharb commented 6 years ago

I think that part of any specification on a .node-version file would include mandating that global dependencies not be shared across node versions (without a full reinstallation), as that is massively brittle and bug-prone.

npm versions, as well as architectures, suggest that a simple version number won't be sufficient.

ngryman commented 6 years ago

I would suggest using semver here because it's widely used and understood by the community and aligns on what engines.node and more generally package.json uses.

It's also a good way to leverage version ranges and let the user be the most up-to-date in term of security and performance. It would also avoid fragmentation caused by users that use a fixed version and never bump it.

That said, I reckon that using semver goes a little against a "simple implementation", so perhaps only a "subset" that only requires lexical ordering would work:

# Pin major version, >=3.0.0
3
3.*
3.*.*

# Pin minor version, >=3.2.0
3.2
3.2.*

# Fixed version
3.2.1

Regarding fuzzy matching, I think installed native modules and modules that install other stuff based on the node version could break [...] I think that part of any specification on a .node-version file would include mandating that global dependencies not be shared across node versions [...]

I agree with @ljharb that each installed version of node should have its sandboxed global dependencies. It's way less error-prone that way.

marcelklehr commented 6 years ago

To clarify: I wasn't talking about global dependencies. It's the same with local dependencies. If you set a fuzzy node version in the .node-version file, then npm install something locally, which includes native modules or other version dependent stuff, and later some version manager decides to use a different node version that still matches the fuzzy version in .node-version, it might not be compatible with the modules you installed locally and things break. At least that's how I understand it.

ngryman commented 6 years ago

@marcelklehr Thanks for clarifying! You are totally right, but shouldn't this responsibility left to the user when he decides to use a fuzzy version?

ljharb commented 6 years ago

semver is not practical for POSIX-only tools like nvm.

ljharb commented 6 years ago

@ngryman fwiw “just one line” won’t really work, especially with different cpu architectures (32 bit, 64 bit, or future things) and JS engines (v8, chakra, spidermonkey). Whatever we decide on must be generically extensible in a backward and forward compatible way, full stop.

MatthewHerbst commented 6 years ago

semver is not practical for POSIX-only tools like nvm.

@ljharb would you mind expanding on that please? Curious to learn more, thanks!

ljharb commented 6 years ago

@MatthewHerbst parsing semver ranges in pure posix is prohibitively complex.

MatthewHerbst commented 6 years ago

@ljharb from a runtime performance perspective or the amount of code needed? There are already open-source POSIX parsers for semver (I have not verified how compliant any of them are), and they are trivial relative to say, the amount of code in nvm/nodenv.

ljharb commented 6 years ago

Both, as well as cognitive overhead of needing to continually maintain the code. Someone has been making a proof of concept of doing it in nvm, and it is prohibitively large and complex.

MatthewHerbst commented 6 years ago

Since it seems to have been agreed that some type of complex structure is needed, what complex structures are there that aren't "as prohibitively large and complex" to parse in POSIX-only environments?

I guess what I'm trying to say is, at some point, a decision simply needs to be made. If it takes a few hundred lines of code (in a standalone library or directly in tools), that seems acceptable rather than having this issue sit another two years with no resolution. Otherwise, why not close this issue and just accept that there will be no standard for the file?

ljharb commented 6 years ago

I think that we can, and should, define a small set of rules such that it's trivially parseable and also that it points to a single version. nvm already has something similar.

MatthewHerbst commented 6 years ago

Why is pointing to a single version important? For example, we build docker images tagged as node-8.11-latest right now for our apps. That way, we can upgrade the underlying image from say, 8.11.1 to 8.11.3 and all of our company's apps will get that upgrade without any changes needed in their repos. However, to support that, their .node-version file essentially needs semver, or something close to it.

ljharb commented 6 years ago

You could use 8.11.x or 8.11 and point to a range, without needing the full complexity of a semver range.

MatthewHerbst commented 6 years ago

Yeah, for sure - I don't think full semver support is needed. Above you argue for something more flexible though:

fwiw “just one line” won’t really work, especially with different cpu architectures (32 bit, 64 bit, or future things) and JS engines (v8, chakra, spidermonkey). Whatever we decide on must be generically extensible in a backward and forward compatible way, full stop.

Maybe it would help by listing out all the things we might have to consider?

For starters, from the above we have:

Maybe a yaml formatted file would make sense?

ljharb commented 6 years ago

ugh, no, no thanks on yaml.

I like your approach for the bullet list tho. Another item is "the LTS line, if any, you're running on" as an alternative to "version" - and another is "which fork" (meaning node, io.js, ayo, or anything in the future) - and another is "standard, release candidate, nightly".

MatthewHerbst commented 6 years ago

ugh, no, no thanks on yaml.

Heh :) Was just throwing out a random thought. What formats are you partial to? So, list wise then we are at:

Not sure I follow on when you would specify LTS over version, since there can be multiple LTS versions out at the same time I believe?

ljharb commented 6 years ago

You might want to specify "latest LTS", or "LTS argon" or "LTS boron" or "LTS carbon" etc. An LTS line isn't necessarily constrained to a single major version.

MatthewHerbst commented 6 years ago

An LTS line isn't necessarily constrained to a single major version

Good point!

Minimum requirements (so far):

avarun42 commented 5 years ago

It seems, from what I understand, that a consensus has been reached regarding the requirements of a .node-version file. Is there a specific format that's been agreed upon then? I'm personally partial to @marcelklehr's suggestion from earlier, where the first line is either a version or an LTS, and the only required line, then the following lines are optional key=value pairs of other things that can be specified from @MatthewHerbst's list.

shadowspawn commented 4 years ago

I have read through this thread a few times over the years (!), and have a simple pragmatic proposal in mind. I'll read through thread(s) again with a proposal in mind to check it against discussion and build up a case for it. Hopefully post within next week.

shadowspawn commented 4 years ago

This discussion has been wide ranging and interesting, covering ideas for a future official version manager, for current version managers to use in the future, and for an intersection of current version manager conventions.

I found existing support for .node-version in nvs, avn, nodenv, direnv, and asdf-nodejs.

I suggest we agree on the simplest format for .node-version, so new implementers can easily move forward, and users get the benefits.

(A simple format/intersection has been suggested a few times already, I'm having a go at explicitly pushing it! Discussions on how and what to support for the future will continue...)

I propose the portable format for .node-version is a single line with x.y.z version number.

Such as

12.13.1

In particular, these variations are not included in the portable format:

Implementations are not restricted to only accept this format. (In practice I expect most implementations pass either the whole file or the first line to their normal version parsing!)

The portable version numbers are those available from https://nodejs.org/dist with binary files included. iojs and source builds are not universally supported and not required by implementations.

Implementations with their own local configuration file should use that in preference to .node-version if both are available.

edit: removed fish-nvm, support for automatic switching has been dropped update: optional leading v is supported by existing implementations, see https://github.com/nodejs/version-management/issues/13#issuecomment-560353839


There are multiple comments on explicit support for future needs, or at least leaving the door open for additions. I have deliberately not included anything explicitly, but have deliberately only specified the backwards compatible format and not prevented additions.

As for that future need, I saw the approach rvm uses with supported files: a simple inflexible shared format file (.ruby-version), and a more flexible format file (.versions.conf). Rather than adding key=value lines into .node-version, I wonder whether a new file with all key=value would be a nice separation of historical and simple specification from a more flexible format, and the flexible format can be more uniform (all key=value).

elektronik2k5 commented 4 years ago

@shadowspawn I agree that we should support the simplest version of semver and any deviations from it. You can add nvs to your list of version managers which support .node-version.

I also think that there's a valid use case for supporting aliases which resolve to a valid semver, such as "lts", "latest" or "dubnium".

BTW, while I'm not affiliated with nvs in any way (beyond using it in dev and CI), I'd like to suggest endorsing it. It is: