Skip to content

Commit edb4e20

Browse files
committed
CM-55551 - fix annotation mismatch
1 parent 7749b00 commit edb4e20

File tree

7 files changed

+149
-12
lines changed

7 files changed

+149
-12
lines changed

cycode/cli/files_collector/sca/go/restore_go_dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ def get_commands(self, manifest_file_path: str) -> list[list[str]]:
4444
def get_lock_file_name(self) -> str:
4545
return GO_RESTORE_FILE_NAME
4646

47-
def get_lock_file_names(self) -> str:
47+
def get_lock_file_names(self) -> list[str]:
4848
return [self.get_lock_file_name()]

cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def get_commands(self, manifest_file_path: str) -> list[list[str]]:
4141
def get_lock_file_name(self) -> str:
4242
return BUILD_GRADLE_DEP_TREE_FILE_NAME
4343

44-
def get_lock_file_names(self) -> str:
44+
def get_lock_file_names(self) -> list[str]:
4545
return [self.get_lock_file_name()]
4646

4747
def get_working_directory(self, document: Document) -> Optional[str]:

cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def get_commands(self, manifest_file_path: str) -> list[list[str]]:
3434
def get_lock_file_name(self) -> str:
3535
return join_paths('target', MAVEN_CYCLONE_DEP_TREE_FILE_NAME)
3636

37-
def get_lock_file_names(self) -> str:
37+
def get_lock_file_names(self) -> list[str]:
3838
return [self.get_lock_file_name()]
3939

4040
def try_restore_dependencies(self, document: Document) -> Optional[Document]:

cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py

Lines changed: 143 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,153 @@
11
import os
2+
from typing import Optional
23

34
import typer
45

5-
from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies
6+
from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies, build_dep_tree_path
67
from cycode.cli.models import Document
8+
from cycode.cli.utils.path_utils import get_file_content
9+
from cycode.logger import get_logger
10+
11+
logger = get_logger('NPM Restore Dependencies')
712

813
NPM_PROJECT_FILE_EXTENSIONS = ['.json']
914
NPM_LOCK_FILE_NAME = 'package-lock.json'
10-
NPM_LOCK_FILE_NAMES = [NPM_LOCK_FILE_NAME, 'yarn.lock', 'pnpm-lock.yaml', 'deno.lock']
15+
# Alternative lockfiles that should prevent npm install from running
16+
ALTERNATIVE_LOCK_FILES = ['yarn.lock', 'pnpm-lock.yaml', 'deno.lock']
17+
NPM_LOCK_FILE_NAMES = [NPM_LOCK_FILE_NAME] + ALTERNATIVE_LOCK_FILES
1118
NPM_MANIFEST_FILE_NAME = 'package.json'
12-
13-
1419
class RestoreNpmDependencies(BaseRestoreDependencies):
1520
def __init__(self, ctx: typer.Context, is_git_diff: bool, command_timeout: int) -> None:
1621
super().__init__(ctx, is_git_diff, command_timeout)
1722

1823
def is_project(self, document: Document) -> bool:
1924
return any(document.path.endswith(ext) for ext in NPM_PROJECT_FILE_EXTENSIONS)
2025

26+
def _resolve_manifest_directory(self, document: Document) -> Optional[str]:
27+
"""Resolve the directory containing the manifest file.
28+
29+
Uses the same path resolution logic as get_manifest_file_path() to ensure consistency.
30+
Falls back to absolute_path or document.path if needed.
31+
32+
Returns:
33+
Directory path if resolved, None otherwise.
34+
"""
35+
manifest_file_path = self.get_manifest_file_path(document)
36+
manifest_dir = os.path.dirname(manifest_file_path) if manifest_file_path else None
37+
38+
# Fallback: if manifest_dir is empty or root, try using absolute_path or document.path
39+
if not manifest_dir or manifest_dir == os.sep or manifest_dir == '.':
40+
base_path = document.absolute_path if document.absolute_path else document.path
41+
if base_path:
42+
manifest_dir = os.path.dirname(base_path)
43+
44+
return manifest_dir
45+
46+
def _find_existing_lockfile(self, manifest_dir: str) -> tuple[Optional[str], list[str]]:
47+
"""Find the first existing lockfile in the manifest directory.
48+
49+
Args:
50+
manifest_dir: Directory to search for lockfiles.
51+
52+
Returns:
53+
Tuple of (lockfile_path if found, list of checked lockfiles with status).
54+
"""
55+
all_lock_file_names = [NPM_LOCK_FILE_NAME] + ALTERNATIVE_LOCK_FILES
56+
lock_file_paths = [
57+
os.path.join(manifest_dir, lock_file_name)
58+
for lock_file_name in all_lock_file_names
59+
]
60+
61+
existing_lock_file = None
62+
checked_lockfiles = []
63+
for lock_file_path in lock_file_paths:
64+
lock_file_name = os.path.basename(lock_file_path)
65+
exists = os.path.isfile(lock_file_path)
66+
checked_lockfiles.append(f'{lock_file_name}: {"exists" if exists else "not found"}')
67+
if exists:
68+
existing_lock_file = lock_file_path
69+
break
70+
71+
return existing_lock_file, checked_lockfiles
72+
73+
def _create_document_from_lockfile(
74+
self, document: Document, lockfile_path: str
75+
) -> Optional[Document]:
76+
"""Create a Document from an existing lockfile.
77+
78+
Args:
79+
document: Original document (package.json).
80+
lockfile_path: Path to the existing lockfile.
81+
82+
Returns:
83+
Document with lockfile content if successful, None otherwise.
84+
"""
85+
lock_file_name = os.path.basename(lockfile_path)
86+
logger.info(
87+
'Skipping npm install: using existing lockfile, %s',
88+
{'path': document.path, 'lockfile': lock_file_name, 'lockfile_path': lockfile_path},
89+
)
90+
91+
relative_restore_file_path = build_dep_tree_path(document.path, lock_file_name)
92+
restore_file_content = get_file_content(lockfile_path)
93+
94+
if restore_file_content is not None:
95+
logger.debug(
96+
'Successfully loaded lockfile content, %s',
97+
{'path': document.path, 'lockfile': lock_file_name, 'content_size': len(restore_file_content)},
98+
)
99+
return Document(relative_restore_file_path, restore_file_content, self.is_git_diff)
100+
else:
101+
logger.warning(
102+
'Lockfile exists but could not read content, %s',
103+
{'path': document.path, 'lockfile': lock_file_name, 'lockfile_path': lockfile_path},
104+
)
105+
return None
106+
107+
def try_restore_dependencies(self, document: Document) -> Optional[Document]:
108+
"""Override to prevent npm install when any lockfile exists.
109+
110+
The base class uses document.absolute_path which might be None or incorrect.
111+
We need to use the same path resolution logic as get_manifest_file_path()
112+
to ensure we check for lockfiles in the correct location.
113+
114+
If any lockfile exists (package-lock.json, pnpm-lock.yaml, yarn.lock, deno.lock),
115+
we use it directly without running npm install to avoid generating invalid lockfiles.
116+
"""
117+
# Check if this is a project file first (same as base class caller does)
118+
if not self.is_project(document):
119+
logger.debug('Skipping restore: document is not recognized as npm project, %s', {'path': document.path})
120+
return None
121+
122+
# Resolve the manifest directory
123+
manifest_dir = self._resolve_manifest_directory(document)
124+
if not manifest_dir:
125+
logger.debug(
126+
'Cannot determine manifest directory, proceeding with base class restore flow, %s',
127+
{'path': document.path},
128+
)
129+
return super().try_restore_dependencies(document)
130+
131+
# Check for existing lockfiles
132+
logger.debug('Checking for existing lockfiles in directory, %s', {'directory': manifest_dir, 'path': document.path})
133+
existing_lock_file, checked_lockfiles = self._find_existing_lockfile(manifest_dir)
134+
135+
logger.debug(
136+
'Lockfile check results, %s',
137+
{'path': document.path, 'checked_lockfiles': ', '.join(checked_lockfiles)},
138+
)
139+
140+
# If any lockfile exists, use it directly without running npm install
141+
if existing_lock_file:
142+
return self._create_document_from_lockfile(document, existing_lock_file)
143+
144+
# No lockfile exists, proceed with the normal restore flow which will run npm install
145+
logger.info(
146+
'No existing lockfile found, proceeding with npm install to generate package-lock.json, %s',
147+
{'path': document.path, 'directory': manifest_dir, 'checked_lockfiles': ', '.join(checked_lockfiles)},
148+
)
149+
return super().try_restore_dependencies(document)
150+
21151
def get_commands(self, manifest_file_path: str) -> list[list[str]]:
22152
return [
23153
[
@@ -37,9 +167,16 @@ def get_restored_lock_file_name(self, restore_file_path: str) -> str:
37167
def get_lock_file_name(self) -> str:
38168
return NPM_LOCK_FILE_NAME
39169

40-
def get_lock_file_names(self) -> str:
170+
def get_lock_file_names(self) -> list[str]:
41171
return NPM_LOCK_FILE_NAMES
42172

43173
@staticmethod
44174
def prepare_manifest_file_path_for_command(manifest_file_path: str) -> str:
45-
return manifest_file_path.replace(os.sep + NPM_MANIFEST_FILE_NAME, '')
175+
# Remove package.json from the path
176+
if manifest_file_path.endswith(NPM_MANIFEST_FILE_NAME):
177+
# Handle both cases: with separator (e.g., '/path/to/package.json') and without (e.g., 'package.json')
178+
if os.sep in manifest_file_path:
179+
return manifest_file_path.replace(os.sep + NPM_MANIFEST_FILE_NAME, '')
180+
else:
181+
return ''
182+
return manifest_file_path

cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ def get_commands(self, manifest_file_path: str) -> list[list[str]]:
2020
def get_lock_file_name(self) -> str:
2121
return NUGET_LOCK_FILE_NAME
2222

23-
def get_lock_file_names(self) -> str:
23+
def get_lock_file_names(self) -> list[str]:
2424
return [self.get_lock_file_name()]

cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ def get_commands(self, manifest_file_path: str) -> list[list[str]]:
1515
def get_lock_file_name(self) -> str:
1616
return RUBY_LOCK_FILE_NAME
1717

18-
def get_lock_file_names(self) -> str:
18+
def get_lock_file_names(self) -> list[str]:
1919
return [self.get_lock_file_name()]

cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ def get_commands(self, manifest_file_path: str) -> list[list[str]]:
1515
def get_lock_file_name(self) -> str:
1616
return SBT_LOCK_FILE_NAME
1717

18-
def get_lock_file_names(self) -> str:
18+
def get_lock_file_names(self) -> list[str]:
1919
return [self.get_lock_file_name()]

0 commit comments

Comments
 (0)