Skip to content

Conversation

ahmedelgabri
Copy link
Contributor

The current checks assume only that the node package is installed globally using npm only. This PR will make it work if it's installed globally using npm or yarn.

@marvim marvim added the RFC label Jun 11, 2018
" `yarn global dir` returns the root directory of the global modules
let yarn_global_modules = get(split(system('yarn global dir'), "\n"), 0, '') . '/node_modules'
let is_node_modules_dir = isdirectory(npm_global_modules) || isdirectory(yarn_global_modules)
if v:shell_error || !is_node_modules_dir
return ''
Copy link
Member

Choose a reason for hiding this comment

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

v:shell_error must also be checked immediately after the npm command.

Also should skip yarn if it was found in npm, to save time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

v:shell_error must also be checked immediately after the npm command.

Then the yarn logic needs to move inside the if statement so we don't return an empty string, right?

Copy link
Member

Choose a reason for hiding this comment

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

sure

Copy link
Member

Choose a reason for hiding this comment

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

is there not a more generic way in nodejs-land to check if a freakin' module is available? There are numerous package managers ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

is there not a more generic way in nodejs-land to check if a freakin' module is available? There are numerous package managers ...

I don't think so or I'm not aware of any universal solution for this. But also the main package managers (covers 99%) are npm & yarn

What if I do it like this instead? any drawbacks or issues?

diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim
index 7d4d5698a..8b8b0f901 100644
--- a/runtime/autoload/provider/node.vim
+++ b/runtime/autoload/provider/node.vim
@@ -22,6 +22,14 @@ function! s:is_minimum_version(version, min_major, min_minor) abort
     \         && str2nr(v_list[1]) >= str2nr(a:min_minor)))
 endfunction
 
+function! s:get_node_client_entry(global_modules_path, cli_bin) abort
+  let entry_path = glob(a:global_modules_path . a:cli_bin)
+  if v:shell_error || !isdirectory(a:global_modules_path) || empty(entry_path)
+    return ''
+  endif
+  return entry_path
+endfunction
+
 " Support for --inspect-brk requires node 6.12+ or 7.6+ or 8+
 " Return 1 if it is supported
 " Return 0 otherwise
@@ -41,20 +49,13 @@ function! provider#node#Detect() abort
   if exists('g:node_host_prog')
     return g:node_host_prog
   endif
-  let npm_global_modules = get(split(system('npm root -g'), "\n"), 0, '')
-  " `yarn global dir` returns the root directory of the global modules
-  let yarn_global_modules = get(split(system('yarn global dir'), "\n"), 0, '') . '/node_modules'
-  let is_node_modules_dir = isdirectory(npm_global_modules) || isdirectory(yarn_global_modules)
-  if v:shell_error || !is_node_modules_dir
-    return ''
-  endif
   if !s:is_minimum_version(v:null, 6, 0)
     return ''
   endif
   let cli_bin = '/neovim/bin/cli.js'
-  let entry_point = empty(glob(npm_global_modules . cli_bin))
-        \ ? glob(yarn_global_modules . cli_bin)
-        \ : glob(npm_global_modules . cli_bin)
+  let entry_point = s:get_node_client_entry(get(split(system('npm root -g'), "\n"), 0, ''), cli_bin)
+        " `yarn global dir` returns the root directory of the global modules
+        \ || s:get_node_client_entry(get(split(system('yarn global dir'), "\n"), 0, '') . '/node_modules', cli_bin)
   if !filereadable(entry_point)
     return ''
   endif

Copy link
Contributor Author

@ahmedelgabri ahmedelgabri Jun 12, 2018

Choose a reason for hiding this comment

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

Also the check for empty(glob(npm_global_modules . cli_bin)) is now missing, which changes the logic. Was that intentional?

That's already happening inside the s:get_node_client_entry function here

let entry_path = glob(a:global_modules_path . a:cli_bin)
if v:shell_error || !isdirectory(a:global_modules_path) || empty(entry_path) " <--
  return ''
endif
return entry_path

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks good, but pass the 'npm root -g' command to s:get_node_client_entry then call system from there, so that the v:shell_error check is coupled with it more

The only reason I didn't do this was that for yarn I need to concat with /node_modules

s:get_node_client_entry(get(split(system('npm root -g'), "\n"), 0, ''), cli_bin)
" vs
s:get_node_client_entry(get(split(system('yarn global dir'), "\n"), 0, '') . '/node_modules', cli_bin)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@justinmk what do you think about this?

function! s:get_node_client_entry(package_manager_cmd) abort
  let cli_bin = '/neovim/bin/cli.js'
  " `yarn global dir` returns the root directory of the global modules
  let yarn_extra_path = a:package_manager_cmd ==# 'yarn global dir' ? '/node_modules' : ''
  let entry_path = glob(get(split(system(a:package_manager_cmd), "\n"), 0, '') . yarn_extra_path . cli_bin)
  if v:shell_error || empty(entry_path) || !isdirectory(entry_path)
    return ''
  endif
  return entry_path
endfunction

function! provider#node#Detect() abort
  if exists('g:node_host_prog')
    return g:node_host_prog
  endif
  if !s:is_minimum_version(v:null, 6, 0)
    return ''
  endif
  let entry_point = s:get_node_client_entry('npm root -g')
  let entry_point = !empty(entry_point) ? entry_point : s:get_node_client_entry('yarn global dir')
  if !filereadable(entry_point)
    return ''
  endif
  return entry_point
endfunction

Not a fan of this

let yarn_extra_path = a:package_manager_cmd ==# 'yarn global dir' ? '/node_modules' : ''

but not sure how to solve this.

Copy link
Member

Choose a reason for hiding this comment

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

That's fine.

Small nit: for such small scopes package_manager_cmd name is too verbose, cmd is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do the changes, rebase & push then 👍

@@ -502,10 +502,10 @@ function! s:check_node() abort
return
endif

if !executable('node') || !executable('npm')
if !executable('node') || !executable('npm') || !executable('yarn')
Copy link
Member

Choose a reason for hiding this comment

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

with this condition, checkhealth will warn if yarn isn't installed, even if npm is. how about:

if !executable('node') || (!executable('npm') && !executable('yarn'))

@@ -522,8 +522,8 @@ function! s:check_node() abort
let host = provider#node#Detect()
if empty(host)
call health#report_warn('Missing "neovim" npm package.',
\ ['Run in shell: npm install -g neovim',
\ 'Is the npm bin directory in $PATH?'])
\ ['Run in shell: npm install -g neovim or yarn global add neovim',
Copy link
Member

Choose a reason for hiding this comment

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

use a separate advice line:

\ ['Run in shell (if you use yarn): yarn global add neovim',

@ahmedelgabri ahmedelgabri force-pushed the fix-node-host-checkhealth branch 2 times, most recently from a8d3ce9 to 096a99f Compare June 12, 2018 10:05
@ahmedelgabri
Copy link
Contributor Author

@justinmk made all the changes needed, let me know if there is anything more needs to be done

@justinmk justinmk added this to the 0.3.1 milestone Jun 12, 2018
@@ -523,7 +523,8 @@ function! s:check_node() abort
if empty(host)
call health#report_warn('Missing "neovim" npm package.',
\ ['Run in shell: npm install -g neovim',
\ 'Is the npm bin directory in $PATH?'])
\ 'Run in shell (if you use yarn): yarn global add neovim',
\ 'Is the global node_modules/bin directory in $PATH?'])
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this mean? Either node_modules/ or bin/ is in PATH?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe that was not clear from my end (& also wrong). node_modules stores the bin executables inside node_modules/.bin folder that's how you can run these binaries either globally or not.

Copy link
Contributor

@janlazo janlazo Jun 12, 2018

Choose a reason for hiding this comment

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

The point of npm root -g when detecting the node.js host is that it doesn't use node_modules/.bin because those are shims. Shims don't work on Windows for node.js host because you can't pass the debug flags to node binary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I think maybe this message can be removed? 'Is the global node_modules/bin directory in $PATH?'

Because:

  1. npm will symlink the executables on mac or Linux in /usr/local/bin (don't know about windows)
  2. yarn you will need to add $(yarn global dir)/node_modules in your $PATH

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd remove it. In Windows, npm will copy (ie. npm link neovim copies the node_modules/neovim/ and its dependencies).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@justinmk are you also ok with removing this line?

'Is the global node_modules/bin directory in $PATH?' or before my change 'Is the npm bin directory in $PATH?'

Copy link
Member

Choose a reason for hiding this comment

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

sure

return ''
endif
let entry_point = s:get_node_client_entry('npm root -g')
let entry_point = !empty(entry_point) ? entry_point : s:get_node_client_entry('yarn global dir')
Copy link
Contributor

@janlazo janlazo Jun 12, 2018

Choose a reason for hiding this comment

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

Pass the name of the package manager instead to determine the command for the directory for global node modules. You can then check if the package manager is in PATH via executable().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do a check for executables here already https://github.com/neovim/neovim/pull/8528/files#diff-2d7f214e0c52c9cb5f1601bdfe0e46b6R505

but also if I pass only the name then I'll need to reconstruct the command inside the function too depends on that

let package_manager = executable(a:package_manager) ? a:package_manager : ''
let cmd =  package_manager ==# 'npm' ? package_manager . ' root -g' : package_manager . ' global dir'

Copy link
Contributor

Choose a reason for hiding this comment

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

That's the point. It's internal to the function so that it's easier to use and modify.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's the point. It's internal to the function so that it's easier to use and modify.

I understand, just thought passing the full command will make it more flexible. So if a new package manager X is released you pass X find-root command instead of adding more logic. But I'm fine doing this too.

Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't hurt to have "redundant" executable() checks for autoloaded functions because the user can test each function if they work without running :checkhealth.

Copy link
Contributor Author

@ahmedelgabri ahmedelgabri Jun 13, 2018

Choose a reason for hiding this comment

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

You need to check if yarn global dir returns a valid path on top of its node_modules/ subfolder otherwise it might use /node_modules/, where / is root, as its entry point if v:shell_error returns 0.

The check is already here, no? if v:shell_error || !isdirectory(global_modules_dir)
https://github.com/neovim/neovim/pull/8528/files/096a99f3dee634c11843baeb0fb3cc724d81fda4#diff-f9dd34ad99ab738d7adc7261f3fbb879R31

Fixed

Can you make the code more explicit? Only npm and yarn are supported.

let cmd = a:package_manager ==# 'yarn' ? 'yarn global dir' : 'npm root -g'

Ok will do that.

Copy link
Contributor Author

@ahmedelgabri ahmedelgabri Jun 13, 2018

Choose a reason for hiding this comment

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

@janlazo good now?

function! s:get_node_client_entry(package_manager) abort
  if !executable(a:package_manager)
    return ''
  endif
  let cli_bin = '/neovim/bin/cli.js'
  let cmd =  a:package_manager ==# 'yarn' ? 'yarn global dir' : 'npm root -g'
  let cmd_output = system(cmd)
  if v:shell_error || !isdirectory(cmd_output)
    return ''
  endif
  " `yarn global dir` returns the root directory of the global modules
  " so we need to append the `/node_modules` part
  let yarn_extra_path = a:package_manager ==# 'yarn' ? '/node_modules' : ''
  let global_modules_dir = get(split(cmd_output, "\n"), 0, '') . yarn_extra_path
  if !isdirectory(global_modules_dir)
    return ''
  endif
  let entry_path = glob(global_modules_dir . cli_bin)
  if !filereadable(entry_path)
    return ''
  endif
  return entry_path
endfunction



function! provider#node#Detect() abort
  if exists('g:node_host_prog')
    return g:node_host_prog
  endif
  if !s:is_minimum_version(v:null, 6, 0)
    return ''
  endif
  let entry_point = s:get_node_client_entry('npm')
  let entry_point = !empty(entry_point) ? entry_point : s:get_node_client_entry('yarn')
  return entry_point
endfunction

Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you break up get(split(system(cmd), "\n"), 0, '')? You need that to remove traililng <CR> before passing the output to isdirectory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was a mistake, this is better now?

function! s:get_node_client_entry(package_manager) abort
  if !executable(a:package_manager)
    return ''
  endif
  let cli_bin = '/neovim/bin/cli.js'
  let cmd =  a:package_manager ==# 'yarn' ? 'yarn global dir' : 'npm root -g'
  let modules_dir = get(split(system(cmd), "\n"), 0, '')
  if v:shell_error || !isdirectory(modules_dir)
    return ''
  endif
  " `yarn global dir` returns the root directory of the global modules
  " so we need to append the `/node_modules` part
  let global_modules_dir = a:package_manager ==# 'yarn' ? modules_dir . '/node_modules' : modules_dir
  if !isdirectory(global_modules_dir)
    return ''
  endif
  let entry_path = glob(global_modules_dir . cli_bin)
  if !filereadable(entry_path)
    return ''
  endif
  return entry_path
endfunction



function! provider#node#Detect() abort
  if exists('g:node_host_prog')
    return g:node_host_prog
  endif
  if !s:is_minimum_version(v:null, 6, 0)
    return ''
  endif
  let entry_point = s:get_node_client_entry('npm')
  let entry_point = !empty(entry_point) ? entry_point : s:get_node_client_entry('yarn')
  return entry_point
endfunction

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@janlazo I pushed the latest changes have a look & let me know what you think.

@ahmedelgabri ahmedelgabri force-pushed the fix-node-host-checkhealth branch 2 times, most recently from d60a058 to 88e96ea Compare June 13, 2018 11:55
if !isdirectory(global_modules_dir)
return ''
endif
let entry_path = glob(global_modules_dir . cli_bin)
Copy link
Contributor

Choose a reason for hiding this comment

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

cli_bin is used once so why not glob(global_modules_dir . '/neovim/bin/cli.js')?

endif
" `yarn global dir` returns the root directory of the global modules
" so we need to append the `/node_modules` part
let global_modules_dir = is_yarn ? modules_dir . '/node_modules' : modules_dir
Copy link
Contributor

Choose a reason for hiding this comment

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

Misleading variable name because modules_dir is used already and npm's global modules directory is also its root directory. I'd reuse modules_dir variable here by appending /node_modules for yarn or rename both variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@janlazo check again, updated the PR.

@janlazo
Copy link
Contributor

janlazo commented Jun 14, 2018

It looks fine now. Good job.

@janlazo
Copy link
Contributor

janlazo commented Jun 14, 2018

@ahmedelgabri Can you skip the glob check and go straight to filereadable? See #8552 (comment) and :h glob.

 let entry_path = global_modules_dir . '/neovim/bin/cli.js'

The current checks assumes only that the node package is installed
globally using `npm` only. This will make it work if it's installed
globally using `npm` or `yarn`
@ahmedelgabri ahmedelgabri force-pushed the fix-node-host-checkhealth branch from e2e8a8d to d1fb25a Compare June 14, 2018 18:34
@ahmedelgabri
Copy link
Contributor Author

@janlazo done & thanks for that didn't know that wildignore affects glob()

justinmk pushed a commit that referenced this pull request Jun 17, 2018
- "neovim" package may be installed with yarn. Check yarn if npm fails.
- Use filereadable() instead of glob(). closes #8552
@justinmk
Copy link
Member

Merged. Thanks @ahmedelgabri and @janlazo !

@justinmk justinmk closed this Jun 17, 2018
@justinmk justinmk removed the RFC label Jun 17, 2018
@ahmedelgabri ahmedelgabri deleted the fix-node-host-checkhealth branch June 17, 2018 11:48
justinmk added a commit that referenced this pull request Jul 18, 2018
FEATURES:
07499a8 #8709 man.vim: C highlighting for EXAMPLES section
07f82ad #8699 TUI: urxvt: also send xterm focus-reporting seqs
40911e4 #8616 API: emit nvim_buf_lines_event from :terminal
c46997a #8546 fillchars: Add "eob" flag

FIXES:
74d19f6 #8576 startup: avoid blank stdin buffer if other files were opened
4874214 #8737 Only waitpid() for processes that we care about
cd6e7e8 #8743 Check all child processes for exit in SIGCHLD handler
c230ef2 #8746 channel.c: Prevent channel_destroy_early() from freeing uninitialized rpc stuff
0ed8b12 #8681 transstr_buf: fix length comparison
d241f27 #8708 TUI: Fix standout mode
9afed40 #8698 man.vim: fix for mandoc
e889640 #8682 provider/node: npm --loglevel silent
1cbc830 #8613 API: nvim_win_set_cursor: set curswant
bf6048e #8628 checkhealth: Python: fix VIRTUAL_ENV check
3cc3506 #8528 checkhealth: node.js: also search yarn

CHANGES:
b751449 #8619 defaults: shortmess+=F
1248178 #8578 highlight: high-priority CursorLine if fg is set.
01570f1 #8726 terminal: handle &confirm and :confirm on unloading
56065bb #8721 screen: truncate showmode messages
bf2460e #7551 buffer: fix copying :setlocal options
c1c14fa #8520 Ex mode: always "improved" (gQ)
050f397 #7992 options: remove 'maxcombine` option (always 6)

INTERNAL:
463da84 #7992 screen: use UTF-8 representation
coditva pushed a commit to coditva/neovim that referenced this pull request Jul 28, 2018
FEATURES:
07499a8 neovim#8709 man.vim: C highlighting for EXAMPLES section
07f82ad neovim#8699 TUI: urxvt: also send xterm focus-reporting seqs
40911e4 neovim#8616 API: emit nvim_buf_lines_event from :terminal
c46997a neovim#8546 fillchars: Add "eob" flag

FIXES:
74d19f6 neovim#8576 startup: avoid blank stdin buffer if other files were opened
4874214 neovim#8737 Only waitpid() for processes that we care about
cd6e7e8 neovim#8743 Check all child processes for exit in SIGCHLD handler
c230ef2 neovim#8746 channel.c: Prevent channel_destroy_early() from freeing uninitialized rpc stuff
0ed8b12 neovim#8681 transstr_buf: fix length comparison
d241f27 neovim#8708 TUI: Fix standout mode
9afed40 neovim#8698 man.vim: fix for mandoc
e889640 neovim#8682 provider/node: npm --loglevel silent
1cbc830 neovim#8613 API: nvim_win_set_cursor: set curswant
bf6048e neovim#8628 checkhealth: Python: fix VIRTUAL_ENV check
3cc3506 neovim#8528 checkhealth: node.js: also search yarn

CHANGES:
b751449 neovim#8619 defaults: shortmess+=F
1248178 neovim#8578 highlight: high-priority CursorLine if fg is set.
01570f1 neovim#8726 terminal: handle &confirm and :confirm on unloading
56065bb neovim#8721 screen: truncate showmode messages
bf2460e neovim#7551 buffer: fix copying :setlocal options
c1c14fa neovim#8520 Ex mode: always "improved" (gQ)
050f397 neovim#7992 options: remove 'maxcombine` option (always 6)

INTERNAL:
463da84 neovim#7992 screen: use UTF-8 representation
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.

4 participants