Skip to content

Commit 40fc69e

Browse files
authored
feat: add monorepo workspace support with --sub-path and --workspace-name (#120)
- Add --sub-path option to scan manifest files in a subdirectory while preserving git context from target-path - Add --workspace-name option to append suffix to repository name (repo-name-workspace_name) - Require both options to be used together with validation - Update scanning logic to use combined target_path + sub_path for manifest file detection - Modify repository naming to include workspace suffix when provided - Preserve git repository context (commits, branches, etc.) from main target-path - Enable Socket CLI to work with monorepo structures where manifests are in subdirectories This allows users to scan specific workspaces within a monorepo while maintaining proper git context and
1 parent 656a458 commit 40fc69e

File tree

4 files changed

+70
-9
lines changed

4 files changed

+70
-9
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.2.8"
9+
version = "2.2.9"
1010
requires-python = ">= 3.10"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.2.8'
2+
__version__ = '2.2.9'

socketsecurity/config.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class CliConfig:
6060
license_file_name: str = "license_output.json"
6161
save_submitted_files_list: Optional[str] = None
6262
save_manifest_tar: Optional[str] = None
63+
sub_path: Optional[str] = None
64+
workspace_name: Optional[str] = None
6365

6466
@classmethod
6567
def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
@@ -106,6 +108,8 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
106108
'license_file_name': args.license_file_name,
107109
'save_submitted_files_list': args.save_submitted_files_list,
108110
'save_manifest_tar': args.save_manifest_tar,
111+
'sub_path': args.sub_path,
112+
'workspace_name': args.workspace_name,
109113
'version': __version__
110114
}
111115
try:
@@ -129,6 +133,14 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
129133
if args.owner:
130134
config_args['integration_org_slug'] = args.owner
131135

136+
# Validate that sub_path and workspace_name are used together
137+
if args.sub_path and not args.workspace_name:
138+
logging.error("--sub-path requires --workspace-name to be specified")
139+
exit(1)
140+
if args.workspace_name and not args.sub_path:
141+
logging.error("--workspace-name requires --sub-path to be specified")
142+
exit(1)
143+
132144
return cls(**config_args)
133145

134146
def to_dict(self) -> dict:
@@ -285,6 +297,18 @@ def create_argument_parser() -> argparse.ArgumentParser:
285297
default="[]",
286298
help="Files to analyze (JSON array string)"
287299
)
300+
path_group.add_argument(
301+
"--sub-path",
302+
dest="sub_path",
303+
metavar="<path>",
304+
help="Sub-path within target-path for manifest file scanning (while preserving git context from target-path)"
305+
)
306+
path_group.add_argument(
307+
"--workspace-name",
308+
dest="workspace_name",
309+
metavar="<name>",
310+
help="Workspace name suffix to append to repository name (repo-name-workspace_name)"
311+
)
288312

289313
path_group.add_argument(
290314
"--excluded-ecosystems",

socketsecurity/socketcli.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,35 @@ def main_code():
136136
raise Exception(f"Unable to find path {config.target_path}")
137137

138138
if not config.repo:
139-
config.repo = "socket-default-repo"
139+
base_repo_name = "socket-default-repo"
140+
if config.workspace_name:
141+
config.repo = f"{base_repo_name}-{config.workspace_name}"
142+
else:
143+
config.repo = base_repo_name
140144
log.debug(f"Using default repository name: {config.repo}")
141145

142146
if not config.branch:
143147
config.branch = "socket-default-branch"
144148
log.debug(f"Using default branch name: {config.branch}")
145149

150+
# Calculate the scan path - combine target_path with sub_path if provided
151+
scan_path = config.target_path
152+
if config.sub_path:
153+
import os
154+
scan_path = os.path.join(config.target_path, config.sub_path)
155+
log.debug(f"Using sub-path for scanning: {scan_path}")
156+
# Verify the scan path exists
157+
if not os.path.exists(scan_path):
158+
raise Exception(f"Sub-path does not exist: {scan_path}")
159+
160+
# Modify repository name if workspace_name is provided
161+
if config.workspace_name and config.repo:
162+
config.repo = f"{config.repo}-{config.workspace_name}"
163+
log.debug(f"Modified repository name with workspace suffix: {config.repo}")
164+
elif config.workspace_name and not config.repo:
165+
# If no repo name was set but workspace_name is provided, we'll use it later
166+
log.debug(f"Workspace name provided: {config.workspace_name}")
167+
146168
scm = None
147169
if config.scm == "github":
148170
from socketsecurity.core.scm.github import Github, GithubConfig
@@ -179,6 +201,21 @@ def main_code():
179201
# Check if we have supported manifest files
180202
has_supported_files = files_to_check and core.has_manifest_files(files_to_check)
181203

204+
# If using sub_path, we need to check if manifest files exist in the scan path
205+
if config.sub_path and not files_explicitly_specified:
206+
# Override file checking to look in the scan path instead
207+
import os
208+
from pathlib import Path
209+
210+
# Get manifest files from the scan path
211+
try:
212+
scan_files = core.find_files(scan_path)
213+
has_supported_files = len(scan_files) > 0
214+
log.debug(f"Found {len(scan_files)} manifest files in scan path: {scan_path}")
215+
except Exception as e:
216+
log.debug(f"Error finding files in scan path {scan_path}: {e}")
217+
has_supported_files = False
218+
182219
# Case 3: If no supported files or files are empty, force API mode (no PR comments)
183220
if not has_supported_files:
184221
force_api_mode = True
@@ -264,7 +301,7 @@ def main_code():
264301
log.info("Push initiated flow")
265302
if scm.check_event_type() == "diff":
266303
log.info("Starting comment logic for PR/MR event")
267-
diff = core.create_new_diff(config.target_path, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar)
304+
diff = core.create_new_diff(scan_path, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar)
268305
comments = scm.get_comments_for_pr()
269306
log.debug("Removing comment alerts")
270307

@@ -317,14 +354,14 @@ def main_code():
317354
)
318355
else:
319356
log.info("Starting non-PR/MR flow")
320-
diff = core.create_new_diff(config.target_path, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar)
357+
diff = core.create_new_diff(scan_path, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar)
321358

322359
output_handler.handle_output(diff)
323360

324361
elif config.enable_diff and not force_api_mode:
325362
# New logic: --enable-diff forces diff mode even with --integration api (no SCM)
326363
log.info("Diff mode enabled without SCM integration")
327-
diff = core.create_new_diff(config.target_path, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar)
364+
diff = core.create_new_diff(scan_path, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar)
328365
output_handler.handle_output(diff)
329366

330367
elif config.enable_diff and force_api_mode:
@@ -337,7 +374,7 @@ def main_code():
337374
}
338375
log.debug(f"params={serializable_params}")
339376
diff = core.create_full_scan_with_report_url(
340-
config.target_path,
377+
scan_path,
341378
params,
342379
no_change=should_skip_scan,
343380
save_files_list_path=config.save_submitted_files_list,
@@ -356,7 +393,7 @@ def main_code():
356393
}
357394
log.debug(f"params={serializable_params}")
358395
diff = core.create_full_scan_with_report_url(
359-
config.target_path,
396+
scan_path,
360397
params,
361398
no_change=should_skip_scan,
362399
save_files_list_path=config.save_submitted_files_list,
@@ -367,7 +404,7 @@ def main_code():
367404
else:
368405
log.info("API Mode")
369406
diff = core.create_new_diff(
370-
config.target_path, params,
407+
scan_path, params,
371408
no_change=should_skip_scan,
372409
save_files_list_path=config.save_submitted_files_list,
373410
save_manifest_tar_path=config.save_manifest_tar

0 commit comments

Comments
 (0)