Closed boneskull closed 5 years ago
I'm happy to send a PR to that effect if you wish.
Sure. PR welcome :)
Coming late to the party; better late than never :man_dancing: :wink:
Just for sake of clarification:
npm
lives in/usr/local/bin
, and thepackages
dir should be/usr/local/lib/node_modules
./usr/local/lib/node_modules/npm/npmrc
contains a prefix of/usr/local
.
packages
& binaries
are just computed fields. Source of problem is that global-dirs
fails to read prefix from /usr/local/lib/node_modules/npm/npmrc
simply because it does not expect npm config to exist there.
So lets deep dive into this issue...
node
installed by homebrew located?
node
, OTOH, lives in/usr/local/bin
but is symlinked into/usr/local/Cellar/node...
.
Actually it is other way around:
$ which node
/usr/local/bin/node
$ realpath $(which node)
/usr/local/Cellar/node/11.6.0/bin/node
/usr/local/node
is a symlink pointing to actual node located inside brew's cellar.
This means
global-dirs
can't reliably useprocess.execPath
to determine where npm lives.
process.execPath
is just a fallback option if other means of getting npm prefix don't return valid result. Before that following steps are done:
PREFIX
env variable
:bulb: While we are here we could fix #4 too.~/.npmrc
PREFIX
env variable
:bulb: While we are here we could fix #4 too.
b) parent dir of node
binary on windows
Example from code: c:\node\node.exe → prefix=c:\node\
c) fallback to grandparent of node
binary elsewhere :warning: ← source of error
Example from code: /usr/local/bin/node → prefix=/usr/local
d) ...prefix
from global npm configReason why this fails is because node
actually lives inside brew's cellar (as explained earlier) so global-dirs
sees /usr/local/Cellar/node/<node_version>/bin/node
as executable path (process.execPath
resolves /usr/local/bin/node
back to actual location), grandparent of which is /usr/local/Cellar/node/<node_version>
and as we known that's wrong.
_
environment variable (possible solution)However, the
/usr/local/bin/node
symlink will be present inprocess.env._
in some shells. Perhapsglobal-dirs
should preferprocess.env._
overprocess.execPath
when present?
Quote from man bash
:
When bash invokes an external command, the variable _ is set to the full file
name of the command and passed to that command in its environment.
This is shell specific (not defined by POSIX); yeah bash
& zsh
support this but:
$ tcsh
% node -e "console.log(process.env._)"
/bin/tcsh
If I remember correctly (too lazy to check) tcsh
is default (Free)BSD shell and I don't want BSD folks to get mad at us :wink: Also I'm not sure about other shells like: fish
, ksh
or dash
and I seriously doubt we want enter area of cross-shell compability (testing) :disappointed:
Manual says: set to the full file name of the command
. That means I can do following:
$ brew --prefix node
/usr/local/opt/node
$ # this is symlink brew creates pointing to node's location inside its cellar
$ $(brew --prefix node)/bin/node -e "console.log(process.env._)"
/usr/local/opt/node/bin/node
Surely people don't do this regularly but my point is that it is invocation dependent same as you later described in #6:
If, for example, your executable is
ava
, we can't useprocess.env._
because it points to/path/to/ava
. If we instead rannode node_modules/.bin/ava
, thenprocess.env._
would point tonode
in yourPATH
.
Changes you proposed in #6 (https://github.com/sindresorhus/global-dirs/pull/6/files#diff-168726dbe96b3ce427e7fedce31bb0bcR25) will fail in case I just described because:
// invoked with `/usr/local/opt/node/bin/node script.js`
// process.env._ → /usr/local/opt/node/bin/node
// path.basename(process.env._) → node
path.dirname(path.dirname(path.basename(process.env._) === 'node' ? process.env._ : process.execPath));
//=> process.dirname(process.dirname(process.env._)) → /usr/local/opt/node
and as we know that's wrong path/prefix.
Going back to my global-dirs
prefix getting steps list - special case before 3.c)
could be introduced. The one that would check for existence of /usr/local/lib/node_modules/npm/npmrc
and return that path instead.
This seems like magic path but it is actually calculated from: $(brew --prefix)/lib/node_modules/npm/npmrc
. File gets created by brew's postinstall script for node formula:
https://github.com/Homebrew/homebrew-core/blob/9495d4020857454285be2ebb0c070edf24d30168/Formula/node.rb#L60-L85
where most interesting lines are:
node_modules = HOMEBREW_PREFIX/"lib/node_modules"
(node_modules/"npm/npmrc").atomic_write("prefix = #{HOMEBREW_PREFIX}\n")
In order to reliably get brew's prefix brew --prefix
needs to get executed inside subprocess but I wonder how often people install brew
outside of default installation directory :thinking:
@boneskull also suggested following: https://github.com/sindresorhus/global-dirs/pull/6#issuecomment-444750246 so lets compare it to my approach:
solution by | result |
---|---|
boneskull | fails because global npmrc contains evaluated version of prefix = $(brew --prefix) :rotating_light: |
vladimyr | fails because /usr/local/lib/node_modules/npm/npmrc is harcoded i.e. brew prefix /usr/local is hardcoded/assumed :rotating_light: |
npmrc
file populated by node's formula postinstall step?solution by | result |
---|---|
boneskull | fails because npmrc contents/prefix is hardcoded/assumed :rotating_light: |
vladimyr | still works because path to global npmrc is hardcoded instead :white_check_mark: |
As @boneskull concluded only reliable way to determine prefix in given scenario would require extra I/O but I firmly believe it is safe to assume that brew prefix is always set to /usr/local
. I'm eager to hear any possible counterarguments or examples where that assumption is not true.
PS Just for the record I instantly get sick when someone mentions installing node with homebrew (use nvm or n please :pray:); yet I just did it just to do this little research :upside_down_face:
Quoting myself:
but I firmly believe it is safe to assume that brew prefix is always set to
/usr/local
. I'm eager to hear any possible counterarguments or examples where that assumption is not true.
Quoting from official homebrew installation docs From 2nd paragraph describing standard installation procedure:
The standard script installs Homebrew to
/usr/local
so that you don’t need sudo when youbrew install
. It is a careful script; it can be run even if you have stuff installed to/usr/local
already. It tells you exactly what it will do before it does it too. And you have to confirm everything it will do before it starts.
Explaning alternative installation methods (alternative prefix):
However do yourself a favour and install to
/usr/local
. Some things may not build when installed elsewhere. One of the reasons Homebrew just works relative to the competition is because we recommend installing to/usr/local
. Pick another prefix at your peril!
If homebrew authors are fine with here be dragons approach when using custom prefix I don't see why global-dirs
shouldn't follow. 😉
@vladimyr Thanks for the very thorough research. I agree we can just assume /usr/local
.
I guess PR #6 will fix this issue right? If not, is there anything I can help with?
I guess PR #6 will fix this issue right? If not, is there anything I can help with?
Probably but that's completely different pair of shoes from what I proposed here earlier...
Fixed by #6
I'm not sure when this changed, but when installing
node
with Homebrew, thenpm.packages
directory is nonexistent:In fact, the npm prefix seems to be incorrect:
npm
lives in/usr/local/bin
, and thepackages
dir should be/usr/local/lib/node_modules
./usr/local/lib/node_modules/npm/npmrc
contains a prefix of/usr/local
.node
, OTOH, lives in/usr/local/bin
but is symlinked into/usr/local/Cellar/node...
.This means
global-dirs
can't reliably useprocess.execPath
to determine wherenpm
lives. However, the/usr/local/bin/node
symlink will be present inprocess.env._
in some shells. Perhapsglobal-dirs
should preferprocess.env._
overprocess.execPath
when present?references: boneskull/create-yo#1