Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added
- **`socket manifest bazel [beta]`** — Generate Bazel JVM SBOM manifests by running `bazel query` against discovered Maven repos in a Bazel workspace. Closes the inline-Maven-declaration gap that lockfile-only parsing misses for repos like envoy, ray, tensorflow, tink-java, and or-tools. Auto-detects Bzlmod and legacy `WORKSPACE`.
- **`socket scan create --auto-manifest`** now covers Bazel workspaces in addition to Gradle/Scala/Kotlin/Conda. Repos with `MODULE.bazel`, `WORKSPACE`, or `WORKSPACE.bazel` are detected automatically and their Maven dependencies extracted as part of the standard scan-create flow.

## [1.1.93](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.93) - 2026-05-08

### Changed
Expand Down
4 changes: 4 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,11 @@ module.exports = [
'src/*/*.test.mts',
// Allow paths like src/commands/optimize/*.test.mts.
'src/*/*/*.test.mts',
// Allow paths like src/commands/manifest/bazel/*.test.mts.
'src/*/*/*/*.test.mts',
'test/*.mts',
// Allow loose one-off scripts.
'scripts/*.mts',
],
defaultProject: 'tsconfig.json',
tsconfigRootDir: rootPath,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/fix/coana-fix.mts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
GQL_PR_STATE_OPEN,
} from '../../constants.mts'
import { handleApiCall } from '../../utils/api.mts'
import { findSocketYmlSync } from '../../utils/config.mts'
import { spawnCoanaDlx } from '../../utils/dlx.mts'
import { getErrorCause } from '../../utils/errors.mts'
import {
Expand All @@ -44,7 +45,6 @@ import {
fetchGhsaDetails,
setGitRemoteGithubRepoUrl,
} from '../../utils/github.mts'
import { findSocketYmlSync } from '../../utils/config.mts'
import { getPackageFilesForScan } from '../../utils/path-resolve.mts'
import { setupSdk } from '../../utils/sdk.mts'
import { fetchSupportedScanFileNames } from '../scan/fetch-supported-scan-file-names.mts'
Expand Down
100 changes: 96 additions & 4 deletions src/commands/manifest/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,98 @@
# Manifest

(At the time of writing...)
`socket manifest <subcommand>` generates declarative dependency manifests
(`pom.xml`, `requirements.txt`, etc.) for ecosystems whose canonical build
system does not ship one out of the box. The resulting files are consumed by
`socket scan create`'s server-side per-ecosystem parsers.

## Subcommands

Sections are sorted alphabetically by subcommand name.

## socket manifest auto

Auto-detect the build system in the target directory and run the matching
manifest generator. Useful when you do not want to spell out the language.

## socket manifest bazel [beta]

Generates Bazel JVM SBOM manifests (`maven_install.json`-shaped) by running
`bazel query` against discovered Maven repos in a Bazel workspace. Output is
consumed by `socket scan create` and closes the
inline-Maven-declaration gap that lockfile-only parsing misses.

> **Note**: This command generates Maven dependency manifests for Bazel JVM
> workspaces. It does not run reachability analysis.

### Usage

```bash
socket manifest bazel [options] [DIR=.]
```

### Options

- `--bazel <path>` — path to bazel/bazelisk binary; default `$(which bazelisk) || $(which bazel)`.
- `--bazel-rc <path>` — path to additional `.bazelrc` fragments forwarded to bazel.
- `--bazel-flags <str>` — flags forwarded to every bazel invocation (single quoted string).
- `--bazel-output-base <dir>` — Bazel `--output_base` for read-only-cache CI environments.
- `--out <dir>` — output directory; default `./.socket/bazel-manifests/`.
- `--dry-run`, `--verbose` — standard diagnostic flags.

> **Upload**: This subcommand only generates manifests. To generate and
> upload in one step, use `socket scan create --auto-manifest .` — it
> detects the workspace, runs the same extraction this subcommand performs,
> and uploads the result.

### Examples

```bash
# Generate maven manifests from the current Bazel workspace.
socket manifest bazel .

# Use bazelisk explicitly.
socket manifest bazel --bazel=/usr/local/bin/bazelisk .
```

### Requirements

- `bazel` or `bazelisk` on `PATH` (or pass `--bazel <path>`).
- Network access on cold cache. Bazel and `rules_jvm_external` own their own
retry policy for transient Maven resolution failures — `socket manifest bazel`
does not retry on top of them.
- Writable Bazel output base; pass `--bazel-output-base` for read-only-cache CI.

This is the user-visible entry point for Bazel JVM SBOM support; the [beta] label and "Bazel JVM SBOM support" wording must stay consistent across release notes and docs.

## socket manifest cdxgen

Wraps the upstream `cdxgen` CycloneDX BOM generator for repos that already
have a working cdxgen configuration.

## socket manifest conda [beta]

Converts a Conda `environment.yml` file to a Python `requirements.txt` so the
Socket scan pipeline can consume the resulting manifest.

## socket manifest gradle [beta]

Uses Gradle (via the project's `gradlew`) to emit a `pom.xml` per subproject,
then feeds those files into the Socket scan pipeline. Mirrors the kotlin and
scala flows.

## socket manifest kotlin [beta]

Uses Gradle to generate a manifest file (`pom.xml`) for a Kotlin project; the
underlying flow is identical to the gradle subcommand.

## socket manifest scala [beta]

Generates a manifest file (`pom.xml`) from Scala's `build.sbt` file.

## socket manifest setup

Starts an interactive configurator that writes default flag values for
`socket manifest` into a `socket.json` in the current directory.

## Dev

Expand All @@ -16,8 +108,8 @@ npm run bs manifest yolo -- --cwd ~/socket/repos/kotlin/kotlinx.coroutines
And upload with this:

```
npm exec socket scan create -- --repo=depscantmp --branch=mastertmp --tmp --cwd ~/socket/repos/scala/akka socketdev .
npm exec socket scan create -- --repo=depscantmp --branch=mastertmp --tmp --cwd ~/socket/repos/kotlin/kotlinx.coroutines .
npm exec socket scan create -- --repo=example-repo --branch=example-branch --tmp --cwd ~/repos/scala/akka example-org .
npm exec socket scan create -- --repo=example-repo --branch=example-branch --tmp --cwd ~/repos/kotlin/kotlinx.coroutines .
```

(The `cwd` option for `create` is necessary because we can't go to the dir and run `npm exec`).
Expand All @@ -31,5 +123,5 @@ socket manifest scala .
socket manifest kotlin .
socket manifest yolo

socket scan create --repo=depscantmp --branch=mastertmp --tmp socketdev .
socket scan create --repo=example-repo --branch=example-branch --tmp example-org .
```
41 changes: 41 additions & 0 deletions src/commands/manifest/bazel/bazel-bin-detect.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { existsSync } from 'node:fs'

import { whichBin } from '@socketsecurity/registry/lib/bin'

import { InputError } from '../../../utils/errors.mts'

/**
* Resolve the bazel binary to invoke for `socket manifest bazel`.
*
* Resolution order:
* 1. If `explicit` is provided, return it iff it exists on disk; else throw.
* 2. Look up `bazelisk` on PATH (preferred — respects `.bazelversion`).
* 3. Fall back to `bazel` on PATH.
* 4. If neither is found, throw InputError with install instructions.
*/
export async function resolveBazelBinary(
explicit: string | undefined,
): Promise<string> {
if (explicit) {
if (!existsSync(explicit)) {
throw new InputError(
`--bazel path does not exist: ${explicit}. Install bazelisk or bazel, or pass an existing path via --bazel.`,
)
}
return explicit
}
// Prefer bazelisk: respects .bazelversion in the workspace.
const bazelisk = await whichBin('bazelisk', { nothrow: true })
if (bazelisk) {
return bazelisk
}
const bazel = await whichBin('bazel', { nothrow: true })
if (bazel) {
return bazel
}
throw new InputError(
'Could not find bazelisk or bazel on PATH. ' +
'Install bazelisk (recommended; https://github.com/bazelbuild/bazelisk) ' +
'or bazel, or pass --bazel <path>.',
)
}
53 changes: 53 additions & 0 deletions src/commands/manifest/bazel/bazel-bin-detect.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

// Mock whichBin so tests run with no bazel on PATH.
vi.mock('@socketsecurity/registry/lib/bin', () => ({
whichBin: vi.fn(),
}))

import { whichBin } from '@socketsecurity/registry/lib/bin'

import { resolveBazelBinary } from './bazel-bin-detect.mts'

describe('resolveBazelBinary', () => {
const mocked = vi.mocked(whichBin)

beforeEach(() => {
mocked.mockReset()
})

it('returns explicit path when it exists', async () => {
// Use a path that definitely exists on every dev machine.
const existing = process.execPath
await expect(resolveBazelBinary(existing)).resolves.toBe(existing)
})

it('throws InputError when explicit path does not exist', async () => {
await expect(
resolveBazelBinary('/no/such/bazel/binary/xyz'),
).rejects.toThrow(/--bazel path does not exist/)
})

it('returns bazelisk when on PATH', async () => {
mocked.mockResolvedValueOnce('/usr/local/bin/bazelisk')
await expect(resolveBazelBinary(undefined)).resolves.toBe(
'/usr/local/bin/bazelisk',
)
})

it('falls back to bazel when bazelisk is missing', async () => {
mocked
.mockResolvedValueOnce(null)
.mockResolvedValueOnce('/usr/local/bin/bazel')
await expect(resolveBazelBinary(undefined)).resolves.toBe(
'/usr/local/bin/bazel',
)
})

it('throws InputError when neither is on PATH', async () => {
mocked.mockResolvedValue(null)
await expect(resolveBazelBinary(undefined)).rejects.toThrow(
/Could not find bazelisk or bazel/,
)
})
})
Loading