Skip to content

feat: add --exclude-paths flag for path-based file exclusion#6741

Open
danielroymoore wants to merge 8 commits intomainfrom
feat/exclude-relative-paths
Open

feat: add --exclude-paths flag for path-based file exclusion#6741
danielroymoore wants to merge 8 commits intomainfrom
feat/exclude-relative-paths

Conversation

@danielroymoore
Copy link
Copy Markdown
Contributor

@danielroymoore danielroymoore commented Apr 23, 2026

Pull Request Submission Checklist

  • Follows CONTRIBUTING guidelines
  • Commit messages are release-note ready, emphasizing what was changed, not how.
  • Includes detailed description of changes
  • Contains risk assessment (Low | Medium | High)
  • Links to automated tests covering new functionality
  • Includes manual testing instructions (if necessary)

What does this PR do?

Adds a new --exclude-paths CLI flag that accepts comma-separated file or directory paths for excluding specific files and directories during --all-projects and --yarn-workspaces scans. This complements the existing --exclude flag, which only matches basenames.

The problem

The existing --exclude flag matches by basename only (e.g. --exclude=package.json excludes all package.json files). In workspace-style monorepos, this makes it impossible to exclude a specific package without excluding every package that shares the same manifest filename:

my-app/
  package.json         ← root
  packages/
    api/package.json   ← want to exclude this one
    web/package.json   ← but not this one

--exclude=package.json excludes all three. There was no way to target just packages/api/package.json.

What changed

New flag: --exclude-paths

  • Accepts comma-separated paths (e.g. --exclude-paths=packages/api/package.json,packages/web/package.json)
  • Accepts both relative and absolute paths
  • Requires --all-projects or --yarn-workspaces (same constraint as --exclude)
  • Whitespace around commas is trimmed

File discovery (find-files.ts)

  • Added excludePaths to FindFilesConfig — an array of resolved absolute paths
  • New isExcludedPath() helper performs exact path matching, with case-insensitive comparison on Windows
  • Paths are resolved once and reused for both exclusion filtering and recursive traversal (avoids duplicate pathLib.resolve calls)

Plugin integration (get-deps-from-plugin.ts)

  • Parses options.excludePaths into resolved absolute paths and passes them as excludePaths to find()
  • After the workspace plugin returns scanned projects, filters out any that match excluded paths — this catches projects discovered by workspace parsers (e.g. pnpm) that bypass the filesystem walk
  • Includes excludePaths in analytics data

Validation & errors

  • ExcludePathsFlagInvalidInputError with clear messaging about allowed input format
  • MissingOptionError when used without --all-projects or --yarn-workspaces
  • args.ts registers 'exclude-paths' for camelCase transformation
  • types.ts adds the option to Options and SupportedUserReachableFacingCliArgs

Help documentation

  • help/cli-commands/test.md and help/cli-commands/monitor.md updated with --exclude-paths documentation

Tests

  • Unit tests (test/tap/find-files.test.ts): Verifies excludePaths excludes specific files by absolute path, doesn't affect same-basename files at other paths, and can exclude entire directories
  • Acceptance tests (test/jest/acceptance/cli-args.spec.ts): Validates error messages for missing --all-projects
  • Acceptance tests (test/jest/acceptance/snyk-test/all-projects.spec.ts): End-to-end tests with pnpm workspace fixture confirming single path exclusion, multiple path exclusion, absolute path support, and that non-targeted package.json files are unaffected

Where should the reviewer start?

  1. src/cli/main.ts — validation logic for the new flag
  2. src/lib/find-files.ts — core exclusion in the file discovery engine
  3. src/lib/plugins/get-deps-from-plugin.ts — integration with the plugin system and post-scan filtering

How should this be manually tested?

In a workspace monorepo with multiple package.json files:

# Without --exclude-paths (all packages scanned)
snyk test --all-projects

# Exclude a specific workspace package
snyk test --all-projects --exclude-paths=packages/api/package.json

# Exclude multiple paths
snyk test --all-projects --exclude-paths=packages/api/package.json,packages/web/package.json

# Verify --exclude still works as before (basename-only)
snyk test --all-projects --exclude=package.json

# Validation: should error
snyk test --exclude-paths=packages/api/package.json  # missing --all-projects

Risk assessment: Low

  • Purely additive — no changes to existing --exclude behaviour or any other flags
  • The new flag goes through the same validation and discovery pipeline as --exclude
  • All changes are gated behind the presence of --exclude-paths; when absent, zero code paths are affected

Companion PR

This is consumed by snyk/cli-extension-dep-graph#152, which replaces the path.Split workaround in the orchestrator with --exclude-paths for forwarding processed file exclusions to the legacy CLI.

Trade-offs & callouts

  1. --exclude is unchanged. Existing behaviour is fully preserved. Both flags can be used together — --exclude for basename patterns, --exclude-paths for specific paths.
  2. Post-scan filtering in get-deps-from-plugin.ts. Workspace parsers (e.g. pnpm) discover projects by reading workspace config files rather than walking the filesystem, so they bypass the find() exclusion. An additional filter on inspectRes.scannedProjects catches these. This is intentional and mirrors how --exclude already needs special handling for workspace-discovered projects.
  3. No glob support. The flag accepts literal paths only, not globs. This keeps the implementation simple and predictable. Glob support could be added later if needed.
  4. Windows path handling. isExcludedPath() uses case-insensitive comparison on win32 to match Windows filesystem semantics. On Unix, comparison is exact.

The existing --exclude flag only accepts basenames, which causes
collateral exclusion in workspace-style projects where multiple files
share the same name (e.g. package.json). This adds --exclude-relative
to allow comma-separated relative paths for precise per-file exclusion
in --all-projects and --yarn-workspaces scans.

Made-with: Cursor
@danielroymoore danielroymoore requested review from a team as code owners April 23, 2026 08:00
@snyk-io
Copy link
Copy Markdown

snyk-io Bot commented Apr 23, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@snyk-pr-review-bot

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

Warnings
⚠️

Please make changes to snyk help text in Gitbook. Changes will be automatically synchronised to Snyk CLI as a scheduled PR.
For more information, see: help/README.md.

⚠️

Since the CLI is unifying on a standard and improved tooling, we're starting to migrate old-style imports and exports to ES6 ones.
A file you've modified is using either module.exports or require(). If you can, please update them to ES6 import syntax and export syntax.
Files found:

  • src/cli/args.ts
  • src/cli/main.ts
⚠️ There are multiple commits on your branch, please squash them locally before merging!
⚠️

"refactor: rename --exclude-relative to --exclude-paths and accept absolute paths" is too long. Keep the first line of your commit message under 72 characters.

Generated by 🚫 dangerJS against 04be038

@snyk-pr-review-bot

This comment has been minimized.

- Resolve path once in findInDirectory instead of twice
- Add case-insensitive path comparison for Windows
- Trim whitespace from comma-separated exclude-relative paths
- Fix Prettier formatting
@snyk-pr-review-bot

This comment has been minimized.

Workspace processors (e.g. processPnpmWorkspaces) read
pnpm-workspace.yaml directly and discover all workspace members,
bypassing the --exclude-relative file-level exclusion applied during
find(). Apply excludePaths as a post-filter on scannedProjects so
projects matched by --exclude-relative are consistently removed.

Made-with: Cursor
@snyk-pr-review-bot

This comment has been minimized.

@snyk-pr-review-bot

This comment has been minimized.

JamesPatrickGill and others added 2 commits April 27, 2026 10:42
…olute paths

Renames the flag to better reflect that it accepts both relative and
absolute paths. Drops input validation that rejected absolute paths and
parent traversals — neither added safety, since the flag is a filter on
discovered files and out-of-scope paths simply match nothing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@snyk-pr-review-bot

This comment has been minimized.

JamesPatrickGill added a commit to snyk/cli-extension-dep-graph that referenced this pull request Apr 27, 2026
Tracks the upstream rename in snyk/cli#6741, where the flag was renamed
to reflect that it now accepts both relative and absolute paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@danielroymoore danielroymoore changed the title feat: add --exclude-relative flag for path-based file exclusion feat: add --exclude-paths flag for path-based file exclusion Apr 27, 2026
@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Potential Plugin Exclusion Bypass 🟡 [minor]

In getDepsFromPlugin, the code filters inspectRes.scannedProjects using pathLib.resolve(root, targetFile). However, if a plugin returns a targetFile that is already an absolute path, pathLib.resolve(root, targetFile) will simply return the absolute path. If the user provided a relative path to --exclude-paths, it was resolved against the current working directory in getDepsFromPlugin. If root (the scan target) differs from the process CWD, the excludePaths array and the resolved project path will not match, causing the exclusion to fail silently.

inspectRes.scannedProjects = inspectRes.scannedProjects.filter(
  (project) => {
    const targetFile = project.meta?.targetFile || project.targetFile;
    if (!targetFile) return true;
    const resolved = pathLib.resolve(root, targetFile);
    return !excludePaths.includes(resolved);
  },
📚 Repository Context Analyzed

This review considered 19 relevant code sections from 9 files (average relevance: 0.78)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants