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

Use package.json engines version? #651

Open
kievechua opened this issue Feb 10, 2015 · 166 comments
Open

Use package.json engines version? #651

kievechua opened this issue Feb 10, 2015 · 166 comments
Labels
feature requests I want a new feature in nvm!

Comments

@kievechua
Copy link

Run nvm use will read local package.json's engines.node and change version accordingly.
Or anyway that auto switch the version.

@ljharb ljharb added the feature requests I want a new feature in nvm! label Feb 10, 2015
@ljharb
Copy link
Member

ljharb commented Feb 10, 2015

Hmm - engines.node is only meant to be advisory, and no part of the node ecosystem uses it for any other purpose. For example, if a package specifies "0.8" that doesn't mean it won't work on iojs or 0.12 - it might be problematic to default the user to 0.8 just because the author hadn't updated their package.json.

I could definitely add this as an alternative, when .npmrc wasn't available - I'm just not convinced it would be a good thing. Also, the engines field can specify semver ranges - so I'd need to use semver to resolve that between the available node versions - which is a dependency that requires node in software that doesn't have node as a dependency.

@assaf
Copy link

assaf commented Mar 24, 2015

NPM treats engines.node as advisory, but that's not the only way to use package.json. It's actually very convenient when everyone on the team, the CI server, and production servers all pick the same version, based on package.json. And a huge time saved when shuffling between multiple projects (consulting, microservices, migration, etc).

So it would be of great help if nvm could pick up on the current project's specific choice of iojs/Node.

@ljharb
Copy link
Member

ljharb commented Mar 24, 2015

@assaf it already can! Just put a .nvmrc file in your project repo, and do a bare nvm install or nvm use in the project directory.

@assaf
Copy link

assaf commented Mar 24, 2015

How do you tell .nvmrc to pick the version specified in package.json?

@ljharb
Copy link
Member

ljharb commented Mar 24, 2015

That's not what I meant - I meant you just manually put a version in that file and commit that to the repo.

@assaf
Copy link

assaf commented Mar 24, 2015

That's not what I was asking for. Having multiple files that specify which version of iojs/Node is in use, just means more things breaking because "works on my machine". Common sense is to keep the project DRY and have a single authoritative specification of the version in use.

@ljharb
Copy link
Member

ljharb commented Mar 24, 2015

Right, I agree with that. The problem is that node.engines in package.json specifies the minimum version a module works with - ie, most of mine say 0.4, but I wouldn't want nvm to use 0.4 by default. By contrast, .nvmrc specifies the version I want to use it with, which is the maximum version it works with - iojs or stable for most of mine.

Thus, since they're for two separate purposes, I wouldn't want to get more DRY than two separate configurations. "Too DRY" can be worse than "not DRY enough".

@assaf
Copy link

assaf commented Mar 24, 2015

If you don't like this feature don't use it. I'd like to have such a feature that I will then choose to use for my projects.

@ljharb
Copy link
Member

ljharb commented Mar 24, 2015

"if you don't like it then don't use it" is not really a good justification for adding any feature to any project. It's nice that you want it, and thanks for offering your thoughts, but:

  • I don't think the feature makes sense semantically - in other words, that the package.json "minimum supported version range" field makes sense as a "run it under this one single version of node/io.js"
  • Parsing JSON from the shell is prohibitively complex - we can't use any npm modules and would have to use a technique that worked in bash, dash, ksh, and zsh. Can you suggest one?
  • The semver range issue I mentioned above.

In other words, even if I thought this was a good feature on its face, there's just no way it's practical, so it simply can't happen.

I'm happy to review a PR in the future if someone can come up with a simple way to achieve it, but I remain opposed on the semantic appropriateness of this feature and would need to be convinced.

@ljharb ljharb closed this as completed Mar 24, 2015
@assaf
Copy link

assaf commented Mar 24, 2015

I'm most familiar with https://github.com/stedolan/jq which Heroku uses to parse package.json as part their deploy setup. Semver can also solved: https://github.com/heroku/heroku-buildpack-nodejs/blob/master/lib/build.sh#L133

PR would be pointless, we can't even agree that my use case for nvm is even worth keeping this issue open for.

@ljharb
Copy link
Member

ljharb commented Mar 24, 2015

Yes but that would be an additional dependency to install, which currently nvm requires installing none (simply curl or wget to install node/iojs versions).

If you wrote a small commandline script that did what you wanted and supplied the version to nvm use, that would at least convince me that it's possible in a practical sense.

@assaf
Copy link

assaf commented Mar 24, 2015

https://gist.github.com/assaf/ee377a186371e2e269a7

@ljharb
Copy link
Member

ljharb commented Mar 25, 2015

Thanks, that's great - I'm not stoked on the reliance on a heroku app for semver resolution, and the usual advice in #iojs on freenode is to put the iojs version in engines.node, so I'd expect that to be supported.

nvm_json_extract looks solid tho, even though it's a bunch of support functions.

Currently, we have nvm-exec - what would you think about adding a new executable file to the repo that, when run, prints out the contents of engines.node or engines.iojs (and perhaps picks intelligently by default, and can be overridden to only look at a provided field)? Then at least, you could do nvm use $(nvm package-json-version) or something, and that'd be a step in the direction you want, without running afoul of my semantic concerns.

@assaf
Copy link

assaf commented Mar 25, 2015

If you set engines.node to 1.6.2 it will call nvm use v1.6.2. If you set engines.iojs to 1.6.2 it will call nvm use iojs-v1.6.2. If you set engines.node to 0.12.0 and engines.iojs to 1.6.2, it will call nvm use iojs-v1.6.2. It follows how people use engines today to set target versions.

The semver version is used for resolving ranges. Heroku maintains it as part of their build infrastructure, a front-end to nodejs.org/iojs.org, so it's already used by many of the people who will find this feature useful, to develop and deploy against the same version.

However, if you need repeatable builds, you should set the specific version in package.json and only that version will be used. No substitutions. And no dependencies on 3rd parties.

This works. It allows you to use package.json to specify which version of io.js/Node you want your code to run against. And it's based on a common deployment pattern (Heroku but also other PaaS influenced by them).

It does not deprecate or change existing functionality. It should still be possible use .nvmrc, but this could co-exist for developer who prefer to set the engine version in package.json and have it picked up on local, CI, and production.

@ljharb
Copy link
Member

ljharb commented Mar 25, 2015

Awesome, that behavior makes sense to me, thanks for clarifying.

Currently, if I don't have a .nvmrc and run nvm use, I get an error and help output - adding an additional fallback would be a breaking change (which isn't a problem, just worth noting).

@ljharb ljharb reopened this Mar 25, 2015
@ljharb ljharb changed the title [Feature Request] Easier way to switch between version Use package.json engines version? Mar 25, 2015
@assaf
Copy link

assaf commented Mar 25, 2015

I don't think it's a good idea to break current behavior. Could be a new command, or use an alias, maybe nvm use package?

@ljharb
Copy link
Member

ljharb commented Mar 25, 2015

Interesting idea! As an alias, it'd have to be overridable, just like node/stable/unstable/iojs are, but maybe that's OK. That would certainly make it work with every nvm command seamlessly.

@assaf
Copy link

assaf commented Mar 25, 2015

Alternatively, it could be a path, so nvm use <version | file> first looks up an alias with that name, if that fails, it looks up a file with that name. That way, it could be a shell script somewhere inside the project, pointing to the package.json in a parent directory.

And if you're in a directory that contains a package.json, nvm ls could also list that version, so you would see:

package.json -> iojs-v1.6 (-> iojs->1.6.2)

@focusaurus
Copy link

+1 to @assaf's request here. I was in the middle of coding stuff into my shell files to do exactly this when I discovered this issue. I'd also like to note that from my perspective there are big, important differences between an application and a reusable module, and many things in the npm ecosystem fail to recognize this, causing me much frustration. This is an application-driven feature request where your code is the first thing the node binary loads, and I think engines.node and engines.iojs are good ways to express an application's chosen runtime version.

@jamsyoung
Copy link

I ran across this issue when I had the same thought that it would be convenient that nvm use would just pickup whatever I have in my package.json. After reading the comments here I agree with @ljharb and can see that this will likely cause more problems than it is worth since most people probably do not use the package.json engines data like I do with CI to indicate what version of node to install on the build agent.

I would like share that you can still use the info from package.json with nvm by utilizing jq as follows:

$ nvm use $(jq -r .engines.node package.json)

And if you don't have the version installed yet, you can use the same concept with nvm install

$ nvm install $(jq -r .engines.node package.json)

Yeah, thats a lot of typing, but you could alias it if you really wanted to.

@focusaurus
Copy link

A version of grabbing engines.node without the external dependency on jq is: node -p -e 'require("./package").engines.node'

@ljharb
Copy link
Member

ljharb commented Apr 7, 2015

Although that won't work unless you already have node installed, which isn't something to rely on when using a node version manager :-)

@bettiolo
Copy link

bettiolo commented Jun 1, 2015

Is anyone actively working on a command line nvm < use | install > package.json?

@hurrymaplelad
Copy link
Contributor

FYI, nodenv has a passable plugin api copied from rbenv. It was pretty straightforward to build a plugin to parse package.json engines after @assaf did all the hard work :)

We're looking at using sh-semver to evaluate semver ranges without a network call or a node dependency.

@patcon
Copy link

patcon commented Dec 10, 2015

Just wanted to chime in to say that I agree with @assaf's original sentiment that locking to specific node version should be encouraged -- lack of version constraint is a good thing imho, should folks choose to use engines.node

@acjay
Copy link

acjay commented Dec 22, 2015

👍 To the original suggestion. It's super helpful in having a smooth process for getting people up and running on a project locally, especially from zero. I'm imagining a workflow that would be as simple as:

  • clone project from Github
  • install NVM
  • nvm install package.json or something, which would install the correct version of Node and NPM for the project and npm install all the modules
  • npm start

Even better, if there were a way to nvm install-repo [email protected]:..., we could get rid of step 1.

bitdivine added a commit to dfinity/oisy-wallet that referenced this issue Sep 11, 2024
# Motivation
By default, `nvm` installs the latest `node`. But the latest nodejs does
not work with this repo; `npm ci` fails.

I note that `nvm` explicitly does NOT use the engines version in
`package.json`. This has been discussed to death in
nvm-sh/nvm#651

# Changes
* Add an `.nvmrc` configuration file that specifies node version 20.

# Tests
I have manually run `nvm install` locally and that has worked. I'm not
sure whether we want to use `nvm` in CI, so I have changed nothing
there.
spotlight301 pushed a commit to spotlight301/Dapp-Soro that referenced this issue Sep 30, 2024
I wish the nvmrc were not necessary, but it still is.
See nvm-sh/nvm#651

I think nvmrc is the most popular Node version manager, so it _probably_
makes sense to add the `.nvmrc`. If we want to skip this and go just
with the `package.json`, that's fine too.
joaovmp pushed a commit to joaovmp/Soroban_dapp that referenced this issue Oct 23, 2024
I wish the nvmrc were not necessary, but it still is.
See nvm-sh/nvm#651

I think nvmrc is the most popular Node version manager, so it _probably_
makes sense to add the `.nvmrc`. If we want to skip this and go just
with the `package.json`, that's fine too.
@hichemfantar
Copy link

hichemfantar commented Nov 20, 2024

Can we have the behavior so it uses the engines property and if that doesn't exist then it uses the version in .nvmrc
This way it's also backwards compatible

@ljharb
Copy link
Member

ljharb commented Nov 20, 2024

@hichemfantar see #651 (comment)

@theoephraim

This comment has been minimized.

@d-lach

This comment has been minimized.

@ljharb

This comment has been minimized.

@mindplay-dk
Copy link

what if this feature was explicitly opt-in?

for example, allow .nvmrc to contain something like $engine (or $auto or $resolve or something) and this would specifically ask nvm to resolve the version via engines.

that way, it's completely optional and not a breaking change.

of course, there is still the issue of actually resolving the node version - but this issue gets enough traffic to suggest that this is in fact a feature people are looking for, so if someone is willing and able to implement it...?

@ljharb
Copy link
Member

ljharb commented Feb 14, 2025

@mindplay-dk The primary issue is parsing the JSON, the secondary issue is that using engines for this is a category error. That a lot of people want to make a category error doesn't mean they should be encouraged or enabled to do so.

@mindplay-dk
Copy link

I guess, if I'm thinking about it, .nvmrc specifies "the version to install", and engines specifies "versions that are supported" - is that what you mean by "category error"? that these values are in fact specifying two different things?

on the one hand, I can see that argument.

on the other hand, what if I want "the version to install" to just be equal to "the latest version supported by the project"?

that's what I'm suggesting $engine (or whatever) instead of a version number would mean.

I think a lot of people want this because it makes more sense than specifying the version in two places, which creates the risk of specifying a "version to install" that isn't even compatible with "versions supported".

at the end of the day, I could see the argument that this is mostly a nit pick, but still, it's clearly going to rub a lot of programmers the wrong way. 😄

@alexkrolick
Copy link

alexkrolick commented Mar 1, 2025

Just a note that fnm use (Fast Node Manager) supports this behavior by default with the --resolve-engines flag defaulting to true: https://github.com/Schniz/fnm/blob/master/docs/commands.md#fnm-use

Perhaps nvm could add such a flag but default it to false?

@ljharb
Copy link
Member

ljharb commented Mar 1, 2025

@alexkrolick there's still the technical barrier to "parsing JSON in POSIX shell", but also, it's still a category error - iow, it's simply incorrect to conflate the two things, and "but other tools do it!" doesn't change that.

@mindplay-dk
Copy link

for other struggling to understand why this is a "category error", perplexity.io provided the following explanation:

The maintainer's claim that automatically using the engines.node version from package.json when running nvm use would be a "category error" is likely correct. This does indeed constitute a category error in programming. Here's why:

  1. Purpose mismatch: The engines.node field in package.json specifies the supported Node.js versions for the package, not necessarily the version to use for development or execution[4]. It's meant to indicate compatibility, not to dictate the exact version to be used.

  2. Semantic difference: An .nvmrc file explicitly defines the Node.js version to use for a project, while engines.node defines a range of compatible versions[4]. Using the latter for version selection conflates two distinct concepts.

  3. Potential conflicts: The engines.node field often specifies a range (e.g., ">=14.0.0") rather than a specific version. Automatically selecting a version from this range could lead to inconsistent behavior across different environments.

  4. Violation of principle: This change would blur the lines between package metadata (in package.json) and development environment configuration (in .nvmrc), which are separate concerns in software development.

In programming, a category error occurs when concepts or elements from one category are incorrectly applied to another, incompatible category[1]. In this case, using package compatibility information (engines.node) to determine the development environment setup (nvm use) would be mixing two distinct categories of information, thus constituting a category error.

The maintainer's stance aligns with good software design principles, keeping separate concerns (package metadata vs. development environment configuration) in their appropriate domains.

Citations:
[1] https://learn.microsoft.com/en-us/cpp/standard-library/error-category-class?view=msvc-170
[2] https://blog.falcony.io/en/8-ways-to-categorize-errors
[3] https://spin.atomicobject.com/categorize-software-errors/
[4] https://blog.ploeh.dk/2024/01/29/error-categories-and-category-errors/
[5] https://education.launchcode.org/intro-to-professional-web-dev/chapters/errors-and-debugging/categories-of-errors.html
[6] https://education.launchcode.org/intro-to-programming-csharp/chapters/errors-and-debugging/categories-of-errors.html
[7] https://teachen.info/cspp/unit3/u0301-errortypes.html
[8] https://www.oslash.com/blog/the-5-most-common-types-of-errors-in-programming-and-how-to-avoid-them


so okay, yes, it would be a category error.

well, here's a different idea then:

since node and nvm are two separate tools, there will be projects that specify engines.node but don't have an .nvmrc.

what if nvm use would prompt you and ask:

an .nvmrc file is not present in this project - would you like to initialize .nvmrc using engines.node? [y/n]

now the version number is in your .nvmrc where it's supposed to be.

this way, there's no category error - the "version to install" is sourced from .nvmrc where it's supposed to be.

all good? 🙂

@danielbayley
Copy link

Maybe I can buy the pushback against reading from engines.node, but I'm not certain you can make the same counter-argument against using devEngines.runtime.version.

@theoephraim
Copy link

theoephraim commented Mar 1, 2025

Is it really a "category error" when developing software to be deployed (not a library) and you have the version in engines.node pinned to a specific version? I'd suspect that case of having a single version and not a range is actually much more common than having a range.

Regardless of if you want to make arguments about it being too hard to infer the correct behaviour when there is a range (although I suspect we could pick something that would work well enough, and let users opt out by still using an nvmrc file) - I just can't get over the fact that it seems so obvious to have NVM infer the version to use at least in the specific version scenario.

@ljharb
Copy link
Member

ljharb commented Mar 1, 2025

@mindplay-dk no, no prompts are acceptable, because nvm is most often used headlessly.

@danielbayley you're entirely correct - using devEngines.runtime.version would be fine, if not for the technical barrier of parsing JSON.

@theoephraim there's no way to know if nvm is being used in a directory with an application or a library, so yes, it still is.

@theoephraim
Copy link

Of course not - but you could tell if the version was pinned, in which case there is no ambiguity. Was pointing out it is a very real use case, likely more common than using a range, since most people are not working on published libraries.

@mindplay-dk
Copy link

no prompts are acceptable, because nvm is most often used headlessly.

you could make this feature conditional on interactive mode, e.g.:

if [[ $- == *i* ]]
then
    do_interactive_stuff
fi

alternatively, how about an nvm init command?

I mean, that's pretty far from what people are asking for, so I'm not sure if it's even worth the effort.

with regards to parsing JSON, I discovered a JSON path implementation in pure bash - it works:

$ ./JSONPath.sh -b -f ./package.json '$.engines.node'
>= 22

now, of course, this gives you a node compatible version constraint, so I don't know if nvm can even use that for anything? 🤔

@ljharb
Copy link
Member

ljharb commented Mar 2, 2025

no, nvm needs posix shell, which is far more restrictive than bash, unfortunately.

@mindplay-dk
Copy link

alright, so that means it can't be done?

sounds like we're wasting our time even discussing it - this issue should probably just be closed? 😌

@ljharb
Copy link
Member

ljharb commented Mar 2, 2025

No, it just means that it's very hard to do.

If someone writes or finds a POSIX-compliant JSON parser, then we could read what's in package.json. Then, we'd either need a POSIX-compliant semver parser, or, we'd need to highly restrict the value of the field (like, just =1.2.3 or 1.2.3, or something).

@danielbayley
Copy link

it's very hard to do.
If someone writes or finds a POSIX-compliant JSON parser, then we could read what's in package.json.

@ljharb What about this one?

@ljharb
Copy link
Member

ljharb commented Mar 2, 2025

@danielbayley that's a viable candidate! it'd need some polish (we'd need to inline it, but with a comment linking back to the source), and a lot more tests than that repo has, but it's doable.

@danielbayley
Copy link

it'd need some polish

What doesn't…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature requests I want a new feature in nvm!
Projects
None yet
Development

No branches or pull requests