Skip to content

Commit 6a005ac

Browse files
authored
Add beta Bazel JVM manifest support (#1312)
## Summary Adds beta Bazel JVM SBOM support to Socket CLI. Bazel is multi-language, but this PR starts with Bazel + Maven because many Bazel JVM repos declare Maven dependencies through `rules_jvm_external` in `MODULE.bazel` or `WORKSPACE` instead of committing a manifest Socket can already scan. The extractor asks Bazel what Maven artifacts it resolved, converts that into a `maven_install.json`-shaped manifest, and sends it through the existing scan pipeline. ## What changed - Adds `socket manifest bazel [beta]`, a generation-only command for producing Bazel JVM SBOM manifests. - Extends `socket scan create --auto-manifest` so Bazel workspaces are detected automatically and scanned through the normal scan-create flow. - Detects Bazel workspaces via `MODULE.bazel`, `WORKSPACE`, or `WORKSPACE.bazel`. - Supports Bzlmod and legacy WORKSPACE invocation modes. - Discovers Maven repos from Bazel-visible repos / workspace metadata, including custom repo names beyond `@maven`. - Parses `jvm_import` and `aar_import` rules from `bazel query --output=build`. - Uses `unsorted_deps.json` when available as a faster structured source. - Writes auto-manifest output under `.socket-auto-manifest/maven_install.json` so we do not overwrite a repo's checked-in `maven_install.json`. - Adds docs, changelog entry, `socket.json` defaults, fixtures, and test coverage. ## User flow Generate only: ```bash socket manifest bazel . ```
1 parent e5a1fc9 commit 6a005ac

45 files changed

Lines changed: 3892 additions & 15 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44

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

7+
## [Unreleased]
8+
9+
### Added
10+
- **`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`.
11+
- **`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.
12+
713
## [1.1.96](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.96) - 2026-05-15
814

915
### Changed

eslint.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,11 @@ module.exports = [
220220
'src/*/*.test.mts',
221221
// Allow paths like src/commands/optimize/*.test.mts.
222222
'src/*/*/*.test.mts',
223+
// Allow paths like src/commands/manifest/bazel/*.test.mts.
224+
'src/*/*/*/*.test.mts',
223225
'test/*.mts',
226+
// Allow loose one-off scripts.
227+
'scripts/*.mts',
224228
],
225229
defaultProject: 'tsconfig.json',
226230
tsconfigRootDir: rootPath,

src/commands/fix/coana-fix.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
GQL_PR_STATE_OPEN,
2828
} from '../../constants.mts'
2929
import { handleApiCall } from '../../utils/api.mts'
30+
import { findSocketYmlSync } from '../../utils/config.mts'
3031
import { spawnCoanaDlx } from '../../utils/dlx.mts'
3132
import { getErrorCause } from '../../utils/errors.mts'
3233
import {
@@ -44,7 +45,6 @@ import {
4445
fetchGhsaDetails,
4546
setGitRemoteGithubRepoUrl,
4647
} from '../../utils/github.mts'
47-
import { findSocketYmlSync } from '../../utils/config.mts'
4848
import { getPackageFilesForScan } from '../../utils/path-resolve.mts'
4949
import { setupSdk } from '../../utils/sdk.mts'
5050
import { fetchSupportedScanFileNames } from '../scan/fetch-supported-scan-file-names.mts'

src/commands/manifest/README.md

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,98 @@
11
# Manifest
22

3-
(At the time of writing...)
3+
`socket manifest <subcommand>` generates declarative dependency manifests
4+
(`pom.xml`, `requirements.txt`, etc.) for ecosystems whose canonical build
5+
system does not ship one out of the box. The resulting files are consumed by
6+
`socket scan create`'s server-side per-ecosystem parsers.
7+
8+
## Subcommands
9+
10+
Sections are sorted alphabetically by subcommand name.
11+
12+
## socket manifest auto
13+
14+
Auto-detect the build system in the target directory and run the matching
15+
manifest generator. Useful when you do not want to spell out the language.
16+
17+
## socket manifest bazel [beta]
18+
19+
Generates Bazel JVM SBOM manifests (`maven_install.json`-shaped) by running
20+
`bazel query` against discovered Maven repos in a Bazel workspace. Output is
21+
consumed by `socket scan create` and closes the
22+
inline-Maven-declaration gap that lockfile-only parsing misses.
23+
24+
> **Note**: This command generates Maven dependency manifests for Bazel JVM
25+
> workspaces. It does not run reachability analysis.
26+
27+
### Usage
28+
29+
```bash
30+
socket manifest bazel [options] [DIR=.]
31+
```
32+
33+
### Options
34+
35+
- `--bazel <path>` — path to bazel/bazelisk binary; default `$(which bazelisk) || $(which bazel)`.
36+
- `--bazel-rc <path>` — path to additional `.bazelrc` fragments forwarded to bazel.
37+
- `--bazel-flags <str>` — flags forwarded to every bazel invocation (single quoted string).
38+
- `--bazel-output-base <dir>` — Bazel `--output_base` for read-only-cache CI environments.
39+
- `--out <dir>` — output directory; default `./.socket/bazel-manifests/`.
40+
- `--dry-run`, `--verbose` — standard diagnostic flags.
41+
42+
> **Upload**: This subcommand only generates manifests. To generate and
43+
> upload in one step, use `socket scan create --auto-manifest .` — it
44+
> detects the workspace, runs the same extraction this subcommand performs,
45+
> and uploads the result.
46+
47+
### Examples
48+
49+
```bash
50+
# Generate maven manifests from the current Bazel workspace.
51+
socket manifest bazel .
52+
53+
# Use bazelisk explicitly.
54+
socket manifest bazel --bazel=/usr/local/bin/bazelisk .
55+
```
56+
57+
### Requirements
58+
59+
- `bazel` or `bazelisk` on `PATH` (or pass `--bazel <path>`).
60+
- Network access on cold cache. Bazel and `rules_jvm_external` own their own
61+
retry policy for transient Maven resolution failures — `socket manifest bazel`
62+
does not retry on top of them.
63+
- Writable Bazel output base; pass `--bazel-output-base` for read-only-cache CI.
64+
65+
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.
66+
67+
## socket manifest cdxgen
68+
69+
Wraps the upstream `cdxgen` CycloneDX BOM generator for repos that already
70+
have a working cdxgen configuration.
71+
72+
## socket manifest conda [beta]
73+
74+
Converts a Conda `environment.yml` file to a Python `requirements.txt` so the
75+
Socket scan pipeline can consume the resulting manifest.
76+
77+
## socket manifest gradle [beta]
78+
79+
Uses Gradle (via the project's `gradlew`) to emit a `pom.xml` per subproject,
80+
then feeds those files into the Socket scan pipeline. Mirrors the kotlin and
81+
scala flows.
82+
83+
## socket manifest kotlin [beta]
84+
85+
Uses Gradle to generate a manifest file (`pom.xml`) for a Kotlin project; the
86+
underlying flow is identical to the gradle subcommand.
87+
88+
## socket manifest scala [beta]
89+
90+
Generates a manifest file (`pom.xml`) from Scala's `build.sbt` file.
91+
92+
## socket manifest setup
93+
94+
Starts an interactive configurator that writes default flag values for
95+
`socket manifest` into a `socket.json` in the current directory.
496

597
## Dev
698

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

18110
```
19-
npm exec socket scan create -- --repo=depscantmp --branch=mastertmp --tmp --cwd ~/socket/repos/scala/akka socketdev .
20-
npm exec socket scan create -- --repo=depscantmp --branch=mastertmp --tmp --cwd ~/socket/repos/kotlin/kotlinx.coroutines .
111+
npm exec socket scan create -- --repo=example-repo --branch=example-branch --tmp --cwd ~/repos/scala/akka example-org .
112+
npm exec socket scan create -- --repo=example-repo --branch=example-branch --tmp --cwd ~/repos/kotlin/kotlinx.coroutines .
21113
```
22114

23115
(The `cwd` option for `create` is necessary because we can't go to the dir and run `npm exec`).
@@ -31,5 +123,5 @@ socket manifest scala .
31123
socket manifest kotlin .
32124
socket manifest yolo
33125
34-
socket scan create --repo=depscantmp --branch=mastertmp --tmp socketdev .
126+
socket scan create --repo=example-repo --branch=example-branch --tmp example-org .
35127
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { existsSync } from 'node:fs'
2+
3+
import { whichBin } from '@socketsecurity/registry/lib/bin'
4+
5+
import { InputError } from '../../../utils/errors.mts'
6+
7+
/**
8+
* Resolve the bazel binary to invoke for `socket manifest bazel`.
9+
*
10+
* Resolution order:
11+
* 1. If `explicit` is provided, return it iff it exists on disk; else throw.
12+
* 2. Look up `bazelisk` on PATH (preferred — respects `.bazelversion`).
13+
* 3. Fall back to `bazel` on PATH.
14+
* 4. If neither is found, throw InputError with install instructions.
15+
*/
16+
export async function resolveBazelBinary(
17+
explicit: string | undefined,
18+
): Promise<string> {
19+
if (explicit) {
20+
if (!existsSync(explicit)) {
21+
throw new InputError(
22+
`--bazel path does not exist: ${explicit}. Install bazelisk or bazel, or pass an existing path via --bazel.`,
23+
)
24+
}
25+
return explicit
26+
}
27+
// Prefer bazelisk: respects .bazelversion in the workspace.
28+
const bazelisk = await whichBin('bazelisk', { nothrow: true })
29+
if (bazelisk) {
30+
return bazelisk
31+
}
32+
const bazel = await whichBin('bazel', { nothrow: true })
33+
if (bazel) {
34+
return bazel
35+
}
36+
throw new InputError(
37+
'Could not find bazelisk or bazel on PATH. ' +
38+
'Install bazelisk (recommended; https://github.com/bazelbuild/bazelisk) ' +
39+
'or bazel, or pass --bazel <path>.',
40+
)
41+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
3+
// Mock whichBin so tests run with no bazel on PATH.
4+
vi.mock('@socketsecurity/registry/lib/bin', () => ({
5+
whichBin: vi.fn(),
6+
}))
7+
8+
import { whichBin } from '@socketsecurity/registry/lib/bin'
9+
10+
import { resolveBazelBinary } from './bazel-bin-detect.mts'
11+
12+
describe('resolveBazelBinary', () => {
13+
const mocked = vi.mocked(whichBin)
14+
15+
beforeEach(() => {
16+
mocked.mockReset()
17+
})
18+
19+
it('returns explicit path when it exists', async () => {
20+
// Use a path that definitely exists on every dev machine.
21+
const existing = process.execPath
22+
await expect(resolveBazelBinary(existing)).resolves.toBe(existing)
23+
})
24+
25+
it('throws InputError when explicit path does not exist', async () => {
26+
await expect(
27+
resolveBazelBinary('/no/such/bazel/binary/xyz'),
28+
).rejects.toThrow(/--bazel path does not exist/)
29+
})
30+
31+
it('returns bazelisk when on PATH', async () => {
32+
mocked.mockResolvedValueOnce('/usr/local/bin/bazelisk')
33+
await expect(resolveBazelBinary(undefined)).resolves.toBe(
34+
'/usr/local/bin/bazelisk',
35+
)
36+
})
37+
38+
it('falls back to bazel when bazelisk is missing', async () => {
39+
mocked
40+
.mockResolvedValueOnce(null)
41+
.mockResolvedValueOnce('/usr/local/bin/bazel')
42+
await expect(resolveBazelBinary(undefined)).resolves.toBe(
43+
'/usr/local/bin/bazel',
44+
)
45+
})
46+
47+
it('throws InputError when neither is on PATH', async () => {
48+
mocked.mockResolvedValue(null)
49+
await expect(resolveBazelBinary(undefined)).rejects.toThrow(
50+
/Could not find bazelisk or bazel/,
51+
)
52+
})
53+
})

0 commit comments

Comments
 (0)