From b1cc920ebb5ac01a86a32149dfa0644e496b44ec Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Wed, 15 Jul 2026 16:28:08 -0400 Subject: [PATCH] install via git for `language: node` this avoids trying to play catch-up with whatever preparation commands npm does when installing from `npm i git+...` (which differs from `npm i a/dir`) the latter does not run any preparation commands leading to broken installs the previous command we were using was suggested by the npm devs, however some of the options that it was using are no longer part of npm itself and npm 12.x errors on unknown options --- pre_commit/languages/node.py | 33 ++++++++----------- .../resources/empty_template_.npmignore | 1 - .../resources/empty_template_package.json | 4 --- pre_commit/store.py | 4 +-- tests/languages/node_test.py | 12 +++++++ 5 files changed, 27 insertions(+), 27 deletions(-) delete mode 100644 pre_commit/resources/empty_template_.npmignore delete mode 100644 pre_commit/resources/empty_template_package.json diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index af7dc6f87..304fdcdd8 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -17,7 +17,6 @@ from pre_commit.prefix import Prefix from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b -from pre_commit.util import rmtree ENVIRONMENT_DIR = 'node_env' run_hook = lang_base.basic_run_hook @@ -77,7 +76,18 @@ def health_check(prefix: Prefix, version: str) -> str | None: def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: - assert prefix.exists('package.json') + if prefix.exists('.git') and prefix.exists('package.json'): + # this requires a new-enough npm (2019ish?) + pkgs = (f'git+file://{prefix.prefix_dir}', *additional_dependencies) + else: + pkgs = (*additional_dependencies,) + + if not pkgs: + raise AssertionError( + '`language: node` must have package.json or ' + 'additional_dependencies', + ) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath @@ -89,22 +99,5 @@ def install_environment( cmd_output_b(*cmd) with in_env(prefix, version): - # https://npm.community/t/npm-install-g-git-vs-git-clone-cd-npm-install-g/5449 - # install as if we installed from git - - local_install_cmd = ( - 'npm', 'install', '--include=dev', '--include=prod', - '--ignore-prepublish', '--no-progress', '--no-save', - ) - lang_base.setup_cmd(prefix, local_install_cmd) - - _, pkg, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir) - pkg = prefix.path(pkg.strip()) - - install = ('npm', 'install', '-g', pkg, *additional_dependencies) + install = ('npm', 'install', '--allow-git=root', '-g', *pkgs) lang_base.setup_cmd(prefix, install) - - # clean these up after installation - if prefix.exists('node_modules'): # pragma: win32 no cover - rmtree(prefix.path('node_modules')) - os.remove(pkg) diff --git a/pre_commit/resources/empty_template_.npmignore b/pre_commit/resources/empty_template_.npmignore deleted file mode 100644 index 72e8ffc0d..000000000 --- a/pre_commit/resources/empty_template_.npmignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/pre_commit/resources/empty_template_package.json b/pre_commit/resources/empty_template_package.json deleted file mode 100644 index 042e9583c..000000000 --- a/pre_commit/resources/empty_template_package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "pre_commit_placeholder_package", - "version": "0.0.0" -} diff --git a/pre_commit/store.py b/pre_commit/store.py index dc90c0519..8bd1e1955 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -37,8 +37,8 @@ def _get_default_directory() -> str: _LOCAL_RESOURCES = ( - 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore', - 'package.json', 'pre-commit-package-dev-1.rockspec', + 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', + 'pre-commit-package-dev-1.rockspec', 'pre_commit_placeholder_package.gemspec', 'setup.py', 'environment.yml', 'Makefile.PL', 'pubspec.yaml', 'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv', diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py index 055cb1e92..0fb84a916 100644 --- a/tests/languages/node_test.py +++ b/tests/languages/node_test.py @@ -15,7 +15,9 @@ from pre_commit.prefix import Prefix from pre_commit.store import _make_local_repo from pre_commit.util import cmd_output +from pre_commit.util import cmd_output_b from testing.language_helpers import run_language +from testing.util import git_commit from testing.util import xfailif_windows @@ -40,6 +42,12 @@ def find_exe_mck(): yield mck +def _make_repo(r): + cmd_output_b('git', 'init', r) + cmd_output_b('git', 'add', '.', cwd=r) + git_commit(cwd=r) + + @pytest.mark.usefixtures('is_linux') def test_sets_system_when_node_and_npm_are_available(find_exe_mck): find_exe_mck.return_value = '/path/to/exe' @@ -61,6 +69,7 @@ def test_sets_default_on_windows(find_exe_mck): @xfailif_windows # pragma: win32 no cover def test_healthy_system_node(tmpdir): tmpdir.join('package.json').write('{"name": "t", "version": "1.0.0"}') + _make_repo(str(tmpdir)) prefix = Prefix(str(tmpdir)) node.install_environment(prefix, 'system', ()) @@ -75,6 +84,7 @@ def test_unhealthy_if_system_node_goes_missing(tmpdir): prefix_dir = tmpdir.join('prefix').ensure_dir() prefix_dir.join('package.json').write('{"name": "t", "version": "1.0.0"}') + _make_repo(str(prefix_dir)) path = ('PATH', (str(bin_dir), os.pathsep, envcontext.Var('PATH'))) with envcontext.envcontext((path,)): @@ -101,6 +111,7 @@ def test_installs_without_links_outside_env(tmpdir): 'dependencies': {'lodash': '*'}, }), ) + _make_repo(str(tmpdir)) prefix = Prefix(str(tmpdir)) node.install_environment(prefix, 'system', ()) @@ -124,6 +135,7 @@ def _make_hello_world(tmp_path): '#!/usr/bin/env node\n' 'console.log("Hello World");\n', ) + _make_repo(str(tmp_path)) def test_node_hook_system(tmp_path):