Skip to content

Commit d792b29

Browse files
committed
wip
1 parent 099b095 commit d792b29

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

dep_checker/npm_audit.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ def has_package_lock(self, package_dir: Path) -> bool:
113113
def has_node_modules(self, package_dir: Path) -> bool:
114114
return (package_dir / "node_modules").is_dir()
115115

116+
def is_missing_lockfile_error(self, audit_data: Optional[Dict]) -> bool:
117+
if not isinstance(audit_data, dict):
118+
return False
119+
error = audit_data.get("error")
120+
return isinstance(error, dict) and error.get("code") == "ENOLOCK"
121+
116122

117123
def run_npm_install(self, package_dir: Path) -> bool:
118124
"""Install non-dev dependencies needed for an audit when no lockfile is present."""
@@ -389,6 +395,15 @@ def check_npm_vulnerabilities(self, vulnerability_class) -> List:
389395
continue
390396

391397
audit_data = self.run_npm_audit(package_dir, package_lock_only=package_lock_only)
398+
if package_lock_only and self.is_missing_lockfile_error(audit_data):
399+
logger.warning(
400+
f"npm audit reported ENOLOCK for {package_dir}; falling back to npm install + npm audit"
401+
)
402+
if not self.run_npm_install(package_dir):
403+
logger.warning(f"Skipping npm audit for {package_dir} due to install failure")
404+
self.failed_packages.append(f"{package_dir}: npm install failed")
405+
continue
406+
audit_data = self.run_npm_audit(package_dir, package_lock_only=False)
392407
if audit_data is None:
393408
logger.warning(f"Skipping vulnerability parsing for {package_dir} due to audit failure")
394409
self.failed_packages.append(f"{package_dir}: npm audit failed")

dep_checker/test_npm_audit.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def fake_run(cmd, **kwargs):
6767
finally:
6868
npm_audit.subprocess.run = original_run
6969

70-
assert ["npm", "install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund", "--silent"] in calls
70+
assert ["npm", "install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"] in calls
7171
assert ["npm", "audit", "--omit=dev", "--json"] in calls
7272
assert ["npm", "audit", "--omit=dev", "--package-lock-only", "--json"] in calls
7373

@@ -158,13 +158,60 @@ def fake_run(cmd, **kwargs):
158158
assert ["npm", "audit", "--omit=dev", "--package-lock-only", "--json"] not in calls
159159

160160

161+
def test_enolock_falls_back_to_install_and_normal_audit() -> None:
162+
calls = []
163+
164+
class Result:
165+
def __init__(self, returncode: int = 0, stdout: str = "", stderr: str = ""):
166+
self.returncode = returncode
167+
self.stdout = stdout
168+
self.stderr = stderr
169+
170+
def fake_run(cmd, **kwargs):
171+
calls.append(cmd)
172+
if cmd == ["npm", "--version"]:
173+
return Result(stdout="10.0.0\n")
174+
if cmd[:2] == ["npm", "install"]:
175+
return Result()
176+
if cmd == ["npm", "audit", "--omit=dev", "--package-lock-only", "--json"]:
177+
return Result(stdout='{"error":{"code":"ENOLOCK"}}')
178+
if cmd == ["npm", "audit", "--omit=dev", "--json"]:
179+
return Result(stdout='{"vulnerabilities": {}}')
180+
raise AssertionError(cmd)
181+
182+
original_run = npm_audit.subprocess.run
183+
npm_audit.subprocess.run = fake_run
184+
try:
185+
with tempfile.TemporaryDirectory() as temp_dir:
186+
package_dir = Path(temp_dir) / "deps" / "pkg"
187+
write_package_json(
188+
package_dir / "package.json",
189+
{"name": "pkg", "version": "1.0.0", "dependencies": {"lodash": "4.17.0"}},
190+
)
191+
write_package_json(
192+
package_dir / "package-lock.json",
193+
{"name": "pkg", "lockfileVersion": 3},
194+
)
195+
196+
checker = NPMAuditChecker(Path(temp_dir), timeout=60)
197+
vulnerabilities = checker.check_npm_vulnerabilities(Vulnerability)
198+
assert vulnerabilities == [], vulnerabilities
199+
finally:
200+
npm_audit.subprocess.run = original_run
201+
202+
assert ["npm", "audit", "--omit=dev", "--package-lock-only", "--json"] in calls
203+
assert ["npm", "install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"] in calls
204+
assert ["npm", "audit", "--omit=dev", "--json"] in calls
205+
206+
161207
def test_npm_audit_basic() -> None:
162208
"""Test basic npm audit functionality"""
163209
print("Testing npm audit functionality...")
164210
test_find_package_json_files_skips_nested_manifests()
165211
test_npm_commands_omit_dev()
166212
test_lockfile_skips_install()
167213
test_node_modules_skips_install_and_lockfile_only()
214+
test_enolock_falls_back_to_install_and_normal_audit()
168215

169216
with tempfile.TemporaryDirectory() as temp_dir:
170217
temp_path = Path(temp_dir)

0 commit comments

Comments
 (0)