Repository: pre-commit/pre-commit

Issue:
improve `healthy()` check for node
See pre-commit/actions#45

for `language_version: system` hooks this looks like:

```
eslint...................................................................Failed
- hook id: eslint
- exit code: 127

/home/runner/.cache/pre-commit/repoibq27hfw/node_env-system/bin/node: line 5: /opt/hostedtoolcache/node/14.8.0/x64/bin/node: No such file or directory
/home/runner/.cache/pre-commit/repoibq27hfw/node_env-system/bin/node: line 5: /opt/hostedtoolcache/node/14.8.0/x64/bin/node: No such file or directory

##[error]The process '/opt/hostedtoolcache/Python/3.8.5/x64/bin/pre-commit' failed with exit code 1
```




Discussion:

Resolving patch:
diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py
--- a/pre_commit/languages/node.py
+++ b/pre_commit/languages/node.py
@@ -21,7 +21,6 @@
 from pre_commit.util import cmd_output_b
 
 ENVIRONMENT_DIR = 'node_env'
-healthy = helpers.basic_healthy
 
 
 @functools.lru_cache(maxsize=1)
@@ -73,6 +72,12 @@ def in_env(
         yield
 
 
+def healthy(prefix: Prefix, language_version: str) -> bool:
+    with in_env(prefix, language_version):
+        retcode, _, _ = cmd_output_b('node', '--version', retcode=None)
+        return retcode == 0
+
+
 def install_environment(
         prefix: Prefix, version: str, additional_dependencies: Sequence[str],
 ) -> None:


Test patch:
diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py
--- a/tests/languages/node_test.py
+++ b/tests/languages/node_test.py
@@ -1,14 +1,19 @@
+import os
+import shutil
 import sys
 from unittest import mock
 
 import pytest
 
 import pre_commit.constants as C
+from pre_commit import envcontext
 from pre_commit import parse_shebang
-from pre_commit.languages.node import get_default_version
+from pre_commit.languages import node
+from pre_commit.prefix import Prefix
+from testing.util import xfailif_windows
 
 
-ACTUAL_GET_DEFAULT_VERSION = get_default_version.__wrapped__
+ACTUAL_GET_DEFAULT_VERSION = node.get_default_version.__wrapped__
 
 
 @pytest.fixture
@@ -45,3 +50,31 @@ def test_uses_default_when_node_and_npm_are_not_available(find_exe_mck):
 def test_sets_default_on_windows(find_exe_mck):
     find_exe_mck.return_value = '/path/to/exe'
     assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT
+
+
+@xfailif_windows  # pragma: win32 no cover
+def test_healthy_system_node(tmpdir):
+    tmpdir.join('package.json').write('{"name": "t", "version": "1.0.0"}')
+
+    prefix = Prefix(str(tmpdir))
+    node.install_environment(prefix, 'system', ())
+    assert node.healthy(prefix, 'system')
+
+
+@xfailif_windows  # pragma: win32 no cover
+def test_unhealthy_if_system_node_goes_missing(tmpdir):
+    bin_dir = tmpdir.join('bin').ensure_dir()
+    node_bin = bin_dir.join('node')
+    node_bin.mksymlinkto(shutil.which('node'))
+
+    prefix_dir = tmpdir.join('prefix').ensure_dir()
+    prefix_dir.join('package.json').write('{"name": "t", "version": "1.0.0"}')
+
+    path = ('PATH', (str(bin_dir), os.pathsep, envcontext.Var('PATH')))
+    with envcontext.envcontext((path,)):
+        prefix = Prefix(str(prefix_dir))
+        node.install_environment(prefix, 'system', ())
+        assert node.healthy(prefix, 'system')
+
+        node_bin.remove()
+        assert not node.healthy(prefix, 'system')