Open ljharb opened 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.
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.
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
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.
Another downside, parsing JSON without node available (since version managers generally need to operate without node available, like nvm
) is very difficult.
@marcelklehr package.json
already provides that. The purpose of a .node-version
file would be simplicity.
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.
@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.
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.
How are they already incompatible?
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.
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.
I think there's a mere intersection of consistent behavior. How would you integrate architecture and engine, then, without losing this compatibility?
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.
Bump. Where is this at? https://github.com/creationix/nvm/pull/1625 is a-waiting. :)
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 ;)
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!
@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.
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.
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.
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.
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)"
@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:
.nvmrc
so it's already something used.node
version 80% of the time (M. Pareto told me, but worth validating this point).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:
.node-version
package.json
That's also a reasonable approach.
Even in the "one-line" part, we'd have to figure out a union of semantics:
3.2.x
, or just 3.2
, or both, or neither?etc.
Might be good to follow the .ruby-version
convention, which is also is a single static value.
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
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.
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.
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.
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.
@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?
semver is not practical for POSIX-only tools like nvm.
@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.
semver is not practical for POSIX-only tools like nvm.
@ljharb would you mind expanding on that please? Curious to learn more, thanks!
@MatthewHerbst parsing semver ranges in pure posix is prohibitively complex.
@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.
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.
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?
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.
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.
You could use 8.11.x
or 8.11
and point to a range, without needing the full complexity of a semver range.
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?
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".
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?
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.
An LTS line isn't necessarily constrained to a single major version
Good point!
Minimum requirements (so far):
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.
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.
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).
@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:
npm
.node-version
and environment variables
It seems like, at a minimum, this would need to support
v4.5.6
and6.7.8
- ie, static versions, both with and without thev
.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"?