Skip to content

CM-67195: Add Bun package manager support to SCA local scans#478

Open
arad-traud-cycode wants to merge 4 commits into
cycodehq:mainfrom
arad-traud-cycode:CM-67195-bun-cli-support
Open

CM-67195: Add Bun package manager support to SCA local scans#478
arad-traud-cycode wants to merge 4 commits into
cycodehq:mainfrom
arad-traud-cycode:CM-67195-bun-cli-support

Conversation

@arad-traud-cycode

Copy link
Copy Markdown

What & why

Enables cycode scan -t sca to support Bun projects during local SCA scans, so CLI users get the same coverage as platform scans. Closes the CLI portion of Bun Phase 1 (CM-67195).

Bun is now a first-class package manager alongside npm, Yarn, and Pnpm — the implementation mirrors the existing Pnpm/Yarn restore handlers.

Changes

  • New RestoreBunDependencies handler — detects a Bun project via bun.lock or a packageManager / engines bun signal in package.json. If bun.lock already exists it's read directly; otherwise bun install --ignore-scripts is run to generate it (then cleaned up).
  • Bun ≥ 1.2 enforcement — only Bun 1.2+ emits the text-based bun.lock we parse (older Bun writes binary bun.lockb). Before generating a lockfile the handler verifies bun --version; on older/missing Bun it logs a warning and skips the restore rather than producing an unparseable lockfile. An already-present bun.lock skips the check (its existence implies Bun ≥ 1.2).
  • npm fallback guardbun.lock added to npm's alternative-lockfile guard so npm doesn't claim Bun projects, and the Bun handler is registered ahead of the npm fallback.
  • consts.pybun.lock added to the SCA supported files and the npm ecosystem map.
  • Tests — unit coverage for detection, version gating (supported / old / missing Bun), direct-read of existing lockfile, and generated-lockfile cleanup; plus an "npm skips Bun project" test.

Acceptance criteria

  • ✅ Bun project detected via bun.lockbun install restores deps before scanning
  • ✅ Bun recognized alongside npm / Yarn / Pnpm
  • ✅ CLI does not fall back to npm restore for Bun projects (when a lockfile is present)
  • ✅ Unit tests cover the Bun restore flow

Testing

  • Full SCA test suite passes (173 tests, incl. 21 new for Bun); ruff check and ruff format clean.
  • Verified end-to-end against a live environment: existing-bun.lock project reads the lockfile directly (npm skipped); no-lockfile project runs the version gate + bun install --ignore-scripts and generates/parses bun.lock.

Note

There is a documented NOTE in restore_npm_dependencies.py::is_project: the npm guard is lockfile-on-disk based, so a project declaring packageManager: "bun@..." with no lockfile is claimed by both handlers (pre-existing behavior shared by pnpm/yarn). Left as-is for consistency; tracked on CM-67195.

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

arad-traud-cycode and others added 2 commits June 28, 2026 16:54
Add a dedicated Bun restore handler so `cycode scan -t sca` correctly
restores Bun project dependencies before scanning, mirroring the existing
pnpm/yarn pattern.

- New RestoreBunDependencies: detects Bun via bun.lock or a
  packageManager/engines bun signal; reads an existing bun.lock directly,
  otherwise runs `bun install --ignore-scripts` to generate it.
- Enforce Bun >=1.2 (text bun.lock) before generating a lockfile; skip
  with a warning on older or missing Bun.
- Register the handler ahead of the npm fallback and add bun.lock to npm's
  alternative-lockfile guard so npm does not claim Bun projects.
- Add bun.lock to the SCA supported files and npm ecosystem map.
- Unit tests for detection, version gating, restore, and cleanup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dencies

Move the Bun-specific version check into the base class as a reusable,
opt-in capability so the logic lives in one place instead of a single
handler.

- BaseRestoreDependencies gains parse_tool_version() and
  is_supported_tool_version(), enforced automatically right before the
  install command runs.
- Handlers opt in by overriding get_version_command() and
  get_minimum_supported_version() (both default to None = no check), so
  every existing handler is unchanged.
- RestoreBunDependencies now just declares ['bun', '--version'] and
  (1, 2); its bespoke _parse_bun_version/_is_supported_bun_version are
  removed.
- Version-parsing and gate tests moved to the base-class test module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@arad-traud-cycode arad-traud-cycode force-pushed the CM-67195-bun-cli-support branch from ee443f1 to e41b00e Compare June 28, 2026 13:54
Some tools print versions with a leading 'v' (e.g. `node --version` ->
v20.1.0). Since parse_tool_version is now a shared base-class helper,
make it strip an optional leading 'v' so a future opt-in handler isn't
misparsed as "could not determine version".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines +190 to +222
def is_supported_tool_version(self) -> bool:
"""Verify the installed package manager meets get_minimum_supported_version().

Returns True when no minimum is declared (the default for all handlers), so existing
handlers are unaffected. Only runs the version command for handlers that opt in.
"""
minimum_version = self.get_minimum_supported_version()
version_command = self.get_version_command()
if minimum_version is None or version_command is None:
return True

tool_name = version_command[0]
minimum_str = '.'.join(str(part) for part in minimum_version)

raw_version = shell(command=version_command, timeout=self.command_timeout, silent_exc_info=True)
version = parse_tool_version(raw_version)
if version is None:
logger.warning(
'Could not determine %s version; %s+ is required to restore dependencies, %s',
tool_name,
minimum_str,
{'raw_version': raw_version},
)
return False
if version < minimum_version:
logger.warning(
'Unsupported %s version; %s+ is required to restore dependencies, %s',
tool_name,
minimum_str,
{'detected_version': '.'.join(str(part) for part in version)},
)
return False
return True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function relevant for anything other than Bun?

If not, I'd move to logic to _indicates_bun

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — it's only used by Bun. Moved the version check back into restore_bun_dependencies.py (_parse_bun_version + _is_supported_bun_version) and reverted BaseRestoreDependencies to its original form. (f4ad542)

Comment on lines +15 to +16
# The minimum-version gate lives in the base class; patch shell/execute_commands there.
_BASE_MODULE = 'cycode.cli.files_collector.sca.base_restore_dependencies'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we're moving the minimum-version gate to bun, is this still relevant?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — reverted. The gate now lives in the Bun module again, so the version-parse/gate tests moved back to test_restore_bun_dependencies.py and patch the Bun module's shell; the base test module is unchanged from main. (f4ad542)

Per review: the minimum-version check is only used by Bun, so keep it
co-located in the Bun handler rather than as generic machinery on the
base class.

- Revert BaseRestoreDependencies to its original form (no version hooks,
  no parse_tool_version).
- Restore _parse_bun_version + _is_supported_bun_version in
  restore_bun_dependencies.py; the generate path verifies Bun >=1.2
  before running `bun install`.
- Move the version-parse/gate tests back to the Bun test module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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