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 semver expression #1535

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

edwmurph
Copy link

@edwmurph edwmurph commented May 30, 2017

Update the nvm use algorithm to dynamically interpret the semver expression from the local package.json's engines.node value.

Key:
✅ : completed
☑️ : existing nvm functionality
🔜 : in progress
❌ : not started

Update to nvm use algorithm:

  1. ☑️ try to resolve to node version in local .nvmrc
  2. ✅ try to resolve to node version in package.json "engines.node" value (tested ✅)
  1. ✅ Retrieve semver from local package.json's engines.node (tested ✅)
  2. ✅ Resolve retrieved semver to the newest compatible node version (tested ✅)
  1. ☑️ try to resolve to node version in inherited .nvmrc
  2. ☑️ try to resolve to use default
  3. ☑️ if error and show help text

@joshdick
Copy link

I'd love to see this become part of nvm! 👍

@edwmurph I wonder if you could limit the network requests even further (once per day or even per configurable time interval) by saving the curl'd data to a file, possibly a hidden file in $NVM_DIR or $HOME or , and checking that file's modification time to determine whether a new copy should be downloaded?

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a massive amount of code to support a very edge use case imo.

Additionally, adding an additional file (besides nvm.sh and nvm-exec) that need to be available is actually a huge breaking change, because a lot of scripts hardcode references to these files.

Additionally, overwriting shell builtins (like cd)is a huge no, that I will never permit to be innvm` - that's hugely disruptive and not something a tool should be doing. The readme contains instructions for those who want to do this, but I won't ship code that does it for the user.

I appreciate the effort!

nvm_helper.sh Outdated
@@ -0,0 +1,419 @@
#!/bin/bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A shebang should only be present in an executable file, not in a sourced file…

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was manually executing it while I was developing it. This would be fixed in the final version. Again I was just looking for high level feedback on how this logic could be leveraged by nvm.

set -e

testing="true"
source ../../nvm_helper.sh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

… but here, this file is sourced

@edwmurph
Copy link
Author

"overwriting shell builtins is a huge no"

I completely agree however I think there are other ways nvm could leverage the ability to dynamically interpret semver expressions. For instance what do you think about instead hooking this logic into calls to "nvm use" when there are no other arguments and there isn't a version specified in a local .nvmrc?

@ljharb
Copy link
Member

ljharb commented May 30, 2017

@edwmurph i totally agree that the current algorithm when no version is supplied:

  1. check for .nvmrc
  2. use default, if present
  3. error and show help text

could be enhanced to, for example:

  1. check for local .nvmrc
  2. check for package.json "engines"
  3. check for inherited .nvmrc
  4. use default, if present
  5. error and show help text

Assuming that reading the semver range and interpreting it is something we can ship.

@edwmurph
Copy link
Author

That sounds great! There's still a little work left to do but I don't see why we couldn't get this ready to ship. Most of the remaining work is just ironing out some implementation decisions that I could use your input on.

  1. When interpreting ranges, how do you think a version should be chosen? E.g. Should the newest version be chosen or the oldest? The current implementation has a flag that lets you choose but is this configuration point something you think we should keep?
  2. The algorithm for interpreting semver expressions relies on having a list of up-to-date node versions. Right now I'm retrieving them from http://nodejs.org/dist/. Do you feel comfortable with this dependency? Also per @joshdick's suggestion, do you think this list should be cached in a file to further limit the number of network requests?
  3. What do you think about the name nvm_helper?

@ljharb
Copy link
Member

ljharb commented May 31, 2017

I don't want to get your hopes up, though - this (and any implementation of semver parsing) is almost certainly too large to be included in nvm with its current architecture.

It's wonderful to be exploring what it would look like - but please don't be under any illusions.

  1. I don't think that's a necessary configuration point; when choosing two semver ranges (the one in package.json, and the one representing all installed versions) always choose the latest matching version, full stop.
  2. Semver is a static format - i'm confused why we need a list of the remote versions at all for nvm use/nvm ls/nvm alias. Potentially for install, but we already grab that list as part of ls-remote during install, so that's not really an issue.
  3. As I said above, 100% of the code needs to live in nvm.sh or nvm-exec, so the name isn't really important right now.

@edwmurph
Copy link
Author

Good points I agree with all three of your responses thanks.

Also, could you clarify your concern about this logic being too large to be included? Are you saying you don't like that "visually" there are too many lines of code to add to nvm.sh for an edge use case?

@ljharb
Copy link
Member

ljharb commented May 31, 2017

It's not really about visual size, it's about how much code there is to reason about, how likely bugs will be, how hard it will be to fix those bugs, etc.

@edwmurph
Copy link
Author

Ok that makes sense. I have some ideas I'd like to explore around how to simplify the semver parser logic so I'm going to pursue this a little further. My goal is to update this PR by this weekend with a more polished proposal of the enhanced nvm use algorithm that accommodates your concerns as best as possible. Thanks for all your input.

@edwmurph
Copy link
Author

edwmurph commented Jun 1, 2017

In order to setup a dev environment, I'm assuming I need to run the makefile but I can't get it to complete because 3 tests are locally failing out of the box for me. Any chance you could try to help me debug? If so Is this thread the best space for that?

@ljharb
Copy link
Member

ljharb commented Jun 1, 2017

You don't need to run the Makefile to get set up.

@edwmurph let's continue that discussion (including which tests are failing) on #1339

@edwmurph
Copy link
Author

edwmurph commented Jun 1, 2017

Oh perfect thanks for pointing that out. I'm guessing my questions will be answered in that thread but I'll message you in there if I'm still stuck.

nvm.sh Outdated
return 1
fi
# TODO handle error case
# TODO implement without node?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'd definitely need to be implemented without node; nvm install on a system with no nodes installed needs to work for all forms of local version resolution.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started down the path of also adding this logic to nvm install but to your point, I'm thinking I should back that out and only add this logic to nvm use. If you agree with that, then do you still think this would need to be implemented without node?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I definitely think it should eventually be in install as well; but either way the semver parsing and range logic should be completely offline.

Yes, every part of nvm needs to be implemented without node, full stop.

Copy link
Author

@edwmurph edwmurph Jun 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR needs a reliable (works with package.json files that aren't perfectly spaced/formatted) and extensible (if npm changes the "engines" field again, this code would need to be adjusted) way to retrieve the semver expression from the package.json. After a little research, it looks like the only options are:

  1. implementing a full blown industrial json parser in bash just to use for this one edge case (reliable and extensible but too much code)

  2. hacking together a regular expression that interprets a nested value (engines.node) in the package.json file (not reliable and not easily extensible)

  3. imposing a dependency (https://stedolan.github.io/jq/)

  4. using node
    I agree that ideally, this should work without node but given the other options, it looks like using node might be the best choice with the only caveat that it wouldn't work for the 1% of use cases where systems have no nodes installed yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your analysis seems correct to me, and these are the exact reasons that this feature doesn't exist yet in nvm.

However, I think you underestimate; a significant percentage of the use case for nvm is "no node is installed yet"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a system with no node installed, nvm install currently reads from .nvmrc or the default alias, and installs it. This works the same if you have a node version installed.

It would be very inconsistent if nvm was capable of reading from package.json if you had a node installed, but that this ability magically appeared once you'd installed a node version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it's not obscure - it's the primary use case for a node version manager: installing node and then managing it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @ljharb I didn't realize you've already been in this exact debate multiple times before.
nodejs/version-management#13
nodejs/version-management#12

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since node is out of the question, is it worth exploring the idea of using regex to read the node version from package.json? The following command looks like it would work?

PKG_JSON_SEMVER_EXPRESSION=$(cat package.json \
    | tr '\n' ' ' \
    | grep -o '"engines":\s*{\(\s*"\(node\|npm\)":\s*\"[0-9.<>=*~^xXv\s]\+",\?\s*\)\+}' \
    | grep -o '"node":\s*\"[0-9.<>*~^xXv\s]\+"' \
    | awk -F: '{ print $2 }' \
    | sed 's/[",]//g')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All ideas are worth exploring :-)

Using regex seems like it'd be very brittle, since json can have all kinds of indentation, and multiple duplicate keys. However, with sufficient tests, it might be worth including.

@runk
Copy link

runk commented Jun 4, 2017

Cannot wait for this feature!


# TODO add more test cases
TEST_CASES=(
'{\n\t"engines": {\n\t\t"node":"1.1.1"\n\t}\n}"' '1.1.1'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for test cases - let's make a dir containing a whole bunch of .json files, that are all example package.json files. That'll be easier to understand from a human perspective.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, if we store each test case in its own example package.json file and make them human readable, it'd be difficult to see which ones are testing which type(s) of spacing. Also I can think of at least 25 test cases this should probably have, so I think it'd be easier to get the full picture of all the tests if they were listed as they currently are in a single file. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would assume that whatever spacing is present in a .json file, is verbatim the test case - human readable isn't the goal as much as human understandable :-)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Ok I'm convinced.

nvm.sh Outdated
if ! ((in_quotes)); then
if [[ "$i" == "{" ]]; then
open_brackets=$((open_brackets+1))
elif [[ "$i" == "}" ]]; then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we support posix, we can't use [[ ]] syntax - that's bash, not sh.

nvm.sh Outdated

nvm_get_node_from_pkg_json() {
local engines_object=''
local open_brackets=0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because ksh doesn't have local, the local foo and foo=0 pieces have to be on separate lines.

nvm.sh Outdated

printf "$1" \
| tr -d '[[:space:]]' \
| grep -o '"engines":{".*' \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please prefix commands with command so they override user aliases

"engines": {
"node": "${NODE_SEMVER_EXPRESSION}"
},
"descriptiion": "fake"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/descriptiion/description

nvm.sh Outdated
fi
if ! ((in_quotes)); then
if [ "$in_quotes" == "false" ]; then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

== also is a bashism, not a posixism

@edwmurph edwmurph force-pushed the master branch 8 times, most recently from d0216f2 to c2af1f4 Compare June 14, 2017 19:45
@edwmurph
Copy link
Author

@ljharb Is the continuous-integration check flaky by any chance? I'm specifically asking about my previous two commits. Are the errors reported in the failed build accurate?

My last commit only removed some comments and the build is indicating that that commit broke the installation_node test suite.

If I just keep retrying will they eventually pass?

@ljharb
Copy link
Member

ljharb commented Jun 15, 2017

Occasionally it can be, due to network conditions - particularly the io.js ones. I've reran the errored builds.

@edwmurph
Copy link
Author

@ljharb Is there a way I can manually trigger a rebuild? Also What do you think about the PR in it's current state? It basically extends the same functionality of .nvmrc to package.json files. If this idea were to ever be incorporated into nvm, this might be a good slice to stop at for now.

@ljharb
Copy link
Member

ljharb commented Jun 16, 2017

@edwmurph unfortunately no, travis-ci doesn't give you that ability. I can do it, or you can force push to trigger a whole new build.

I'll give the whole PR a review now if you're ready :-)

@edwmurph edwmurph force-pushed the master branch 2 times, most recently from 8758c51 to 68e830d Compare May 31, 2018 12:29
@edwmurph
Copy link
Author

edwmurph commented Jun 1, 2018

@ljharb Thank you for your previous review. At this point I have addressed all your comments. I also finished writing the rest of the tests and now believe the implementation has extensive test coverage. So when you have time, I would appreciate another round of review.

Currently, this feature requires the addition of 536 lines to nvm.sh which is ~14.6% (536/3651=.146..) increase.

Also I understand that this work is exploratory in that it's unclear if this feature is too large to be included in nvm with the current architecture. However, I would really like to understand the particular bottlenecks to see if they can be overcome.

@danawoodman
Copy link

Any chance this can be approved/merged soon?

@edwmurph
Copy link
Author

@danawoodman right now it looks like this PR is currently stalled on the problem of it being prohibitively complex. It does have extensive test coverage which provides some solid assurances. But admittedly debugging any issues that arise would not be easy for someone unfamiliar with the implementation.

So while this implementation does work for all the test cases I could think of, for this to be merged it would probably need to be further simplified to the point where it's easier to understand/debug. How much more simplification can take place is a question I'm not sure about but I would welcome anyone with more POSIX experience than me to open a PR against the fork I've been working out of to move this along.

@cyrilchapon
Copy link

cyrilchapon commented Jan 10, 2019

👍

Having both commited a .nvmrc and a engines in package.json is error prone.
Especially for us, loving-nvm-heroku-users.

For us, I could think of 2 strategies :

  • Spam heroku each day to ask for respect .nvmrc versions (which lack the feature of describing npm version also)
  • Upgrade nvm to respect package.json engines, which is a standard nowadays, even if "advisory"

I think the second one is more reasonable, isn't it ?

This is probably a lot of code, and semver appliance strategy review, but this is not at all "useless" for everyone. I'd be glad to elaborate on the use cases.

@zoobot
Copy link

zoobot commented Oct 14, 2022

@edwmurph Would you consider coming back to this for a refactor and simplification. Its been a while and I bet your simplifications skills have gotten better during covid. Would love to see this feature happen!!

@adrian-gierakowski

This comment was marked as off-topic.

@edwmurph
Copy link
Author

@zoobot i'd be happy to try another iteration on this

@ljharb could you provide some updated true north statements / requirements that i can bear in mind while preparing another iteration for review to avoid wasting time?

@distinctgrey
Copy link

@edwmurph wondering if you are still planning to rewrite the implementation?

Apparently you're the only one who took the effort to solve this (imho) shortcoming in nvm.
Would be a shame to let your earlier work go to waste!

@edwmurph
Copy link
Author

@distinctgrey i can definitely give this another shot but i need an nvm maintainer to provide some buy in and direction to avoid wasting time

@ljharb ljharb force-pushed the master branch 2 times, most recently from c6cfc3a to c20db2a Compare June 10, 2024 18:13
@danielbayley
Copy link

@edwmurph There is also now devEngines.runtime to consider…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.