Skip to content

Commit d1c99be

Browse files
authored
feat(manifest): --facts mode emits Socket facts JSON for Gradle projects (REA-442) (#1318)
* feat(manifest): add --facts mode to socket manifest gradle (REA-442) Generates a per-(sub)project .socket.facts.json describing the resolved compile/runtime dependency graph, matching the canonical SocketFacts schema consumed by depscan's SBOM_Resolve pipeline. The new init script (socket-facts.init.gradle) registers a socketFacts task on every project, walks resolvable compile/runtime classpaths via LenientConfiguration so unresolved deps degrade gracefully instead of failing the build, dedupes Gradle's variant artifacts (java-classes-directory vs jar) into one component per logical Maven coordinate, and classifies prod/dev correctly (prod-seen-anywhere wins; production deps are no longer flagged dev just because test classpaths inherit them). Output filename matches Coana's .socket.facts.json so the depscan **/*.socket.facts.json glob picks both pre- and post-reachability versions out of a scan tarball. The TS wrapper (convert-gradle-to-facts.mts) routes --facts through the project's ./gradlew by default; the init script is bundled into dist/ alongside the existing pom-generating init.gradle. Verified end-to-end across JDK 8/Gradle 5.6.4, JDK 11/Gradle 6.9.4, JDK 17/Gradle 7.6.4, JDK 21/Gradle 8.10.2, JDK 21/Gradle 9.2.1 — outputs byte-identical after JSON normalization across the matrix on the test fixtures. Test fixtures cover single-module Java, multi-module with project deps, and an unresolvable-dep scenario. The integration test (socket-facts-init-gradle.e2e.test.mts) is e2e-only and auto-skips when no gradle binary is on PATH; CI doesn't yet install JDK/Gradle. Open follow-ups (tracked in REA-442): - AGP-aware variant config discovery and an Android fixture - Kotlin Multiplatform fixture to exercise kotlin.targets.compilations - Mirror --facts onto cmd-manifest-kotlin and the auto-detect path - Promote the sdkman matrix sweep to a CI job * test(manifest): drop gradle facts integration suite, match existing surface tests The previous commit added an e2e test that runs the socket-facts init script against real Gradle fixtures. That's broader coverage than the sibling `socket manifest gradle` / `kotlin` / `auto` commands currently have in CI — those are tested only via `--help` snapshot and a `--dry-run` short-circuit, and never actually invoke gradle. Bring the new `--facts` mode in line: keep the help-text snapshot (already covers the new flag) and add a `--facts --dry-run` case that mirrors the existing dry-run test pattern. Removes the e2e test and the gradle-facts fixtures; drops the matching .gitignore entries that no longer have anywhere to apply. The matrix sweep and integration coverage stay as an open follow-up in REA-442 — to be picked up alongside `setup-java`/`setup-gradle` in CI if/when we want any of the gradle commands actually exercised end-to-end. * fix(manifest): don't emit the scan-target project as a facts component The socketFacts init script was adding the project being scanned as a component in its own .socket.facts.json. With no parent edges this shows up downstream as `orphaned component not reachable from any direct dependency`. The project is the SBOM target, not one of its own dependencies — drop the root node entirely and let `directIds` carry the first-level edges. afterEvaluate is no longer needed since project coordinates were only used to populate that root entry. * feat(manifest): mirror --facts onto kotlin and auto-detect paths cmd-manifest-kotlin.mts: add the --facts flag (also overridable via defaults.manifest.gradle.facts in socket.json) and route through convertGradleToFacts when set. Test pair (--help snapshot + --facts --dry-run) mirrors what we did for cmd-manifest-gradle. generate_auto_manifest.mts: when defaults.manifest.gradle.facts is true, the auto-detect path now generates Socket facts instead of pom files, matching what the explicit subcommands do. Brings the new mode to feature parity with the existing pom path, which is exposed through gradle, kotlin and auto. * feat(manifest): surface --facts in socket manifest setup wizard `socket manifest setup` now asks whether gradle should emit Socket facts instead of pom.xml files, writing the answer to defaults.manifest.gradle.facts in socket.json. The prompt sits next to the existing --verbose toggle and follows the same yes/no/leave-default ternary shape. * test(manifest): restore gradle facts integration test, install JDK+Gradle in e2e CI Brings back the e2e test and gradle-facts fixtures we dropped earlier in this branch and wires `setup-java@v4` + `gradle/actions/setup-gradle@v4` into .github/workflows/e2e-tests.yml so the test actually exercises the init script on every PR (it was previously auto-skipping for lack of a gradle binary). Fixtures keep the guava 31.1-jre / slf4j 1.7.36 pins so resolution stays clean across Gradle 5.6.4 through 9.2.1 in the local sdkman matrix. The e2e CI uses Gradle 9.2.1 / JDK 21 as a single baseline; wider Gradle version coverage in CI is still tracked as a follow-up. Also restores the .gitignore entries for the .gradle/, build/, .socket.facts.json, and pom.xml outputs that integration runs produce. * feat(manifest): cover Android Gradle Plugin variants in --facts Adds an android-library fixture (AGP 8.7.3, compileSdk 34) and the minimum machinery the init script needs to scrape AGP-flavored classpaths without blowing up. Two changes to socket-facts.init.gradle: - Skip configurations matching *AndroidTest* (instrumented tests). Their resolution needs device-vs-host target attributes the init script doesn't set, and they fail before producing useful data. - Wrap per-configuration resolution in try/catch. AGP unit-test classpaths (releaseUnitTestCompileClasspath etc.) pull in the project's own debugApiElements, which exposes multiple variants (android-classes-jar, r-class-jar, android-lint, ...); without consumer-side build-type attributes we hit "variant ambiguity" errors. We log "[socket-facts] skipping <cfg>: ..." and continue so other classpaths still produce output. Production (release + debug compile/runtime) variants resolve fine. The e2e test skips the Android case when neither ANDROID_HOME nor ANDROID_SDK_ROOT is set — same auto-skip posture as the rest of the gradle suite. Asserts that androidx.annotation:annotation is captured as a direct dep, confirming AGP variant configs are being walked. Still pending: principled discovery via androidComponents.onVariants (AGP 7+) or android.libraryVariants — current name-pattern matching catches Android variant configs by suffix and gets the job done, but isn't AGP-aware in the strict sense. * test(manifest): add Kotlin Multiplatform fixture for --facts A minimal KMP project (jvm + js targets) exercises per-target compile and runtime classpaths (jvmMainCompileClasspath, jsTestRuntimeClasspath, ...) that aren't surfaced through Java's SourceSetContainer. Our name-pattern selection picks them up by suffix. The fixture pulls kotlinx-serialization-core (commonMain) so it shows up in both jvm and js target variants of the artifact, and slf4j-api (jvmMain-only) to confirm target-specific classpaths flow through. The test asserts both deps are present in the resulting components array. * feat(manifest): emit `tooling` flag on facts components Widens what `socket manifest gradle --facts` emits: instead of whitelisting only `*CompileClasspath` / `*RuntimeClasspath` configurations and silently dropping the rest, we now walk every resolvable configuration and tag each artifact with two independent boolean flags: - `dev: true` ← artifact only ever appeared in test-named configs - `tooling: true` ← artifact only ever appeared outside compile/runtime classpaths (annotation processors, linters, code-gen plugins, gradle plugin internals) "Only ever" means the inverse semantic: if a dep also appears in a non-test classpath, `dev` is cleared; if it also appears in a compile/runtime classpath, `tooling` is cleared. So a dep that shows up as both an `api` and an `annotationProcessor` ends up flagged as neither dev nor tooling — the production usage wins. Motivation: downstream reachability scanners (depscan) want to suppress reachability analysis for tooling artifacts, while still including them in the SBOM for non-reachability alerts (malware, license, supply chain). This was previously impossible because the script dropped tooling deps entirely. Schema: relies on a new `tooling: z.boolean().optional()` on SF_ArtifactSchema in depscan, separate work. The cli side emits the field regardless; older consumers that ignore it stay unaffected. Fixture/test: single-module-java now declares `annotationProcessor 'org.projectlombok:lombok:1.18.30'`, exercising the tooling path. A new test case asserts lombok is emitted with tooling=true while guava (api) and junit (testImplementation) are not. Effect on existing fixtures: - single-module-java: 11 components total (10 non-tooling + lombok) - kotlin-multiplatform: 29 components (11 non-tooling + 18 Kotlin compiler plugin classpath deps as tooling) - android-library: 83 components (5 non-tooling, 78 AGP internals as tooling) — previously these AGP internals were dropped - multi-module-java, unresolved-deps: unchanged shape * fix(manifest): make --verbose actually stream gradle output live Today both the pom path and the facts path print "(It will show no output, you can use --verbose to see its output)" but --verbose only dumped a captured stdout dump *after* gradle finished. For large multi-project builds (elasticsearch-scale), that means the user stared at a spinner for many minutes with no signal that anything was happening. When --verbose is set, spawn gradle with `stdio: 'inherit'` so the build's stdout/stderr stream live to the user's terminal. The spinner is skipped (would conflict with inherited tty output) and the post-run "Reported exports:" / "POM file copied to:" summary is skipped too — those lines were already visible inline during the streamed run. Non-verbose runs are unchanged: spinner + captured stdout + summary. Also corrects the misleading "(It will show no output, you can use --verbose to see its output)" message to "(No live output. Pass --verbose to stream gradle output instead.)". * perf(manifest): stop downloading artifact files in facts init script `dep.moduleArtifacts` access combined with `.file.isDirectory()` filtering was forcing Gradle to *download* every resolved artifact file. On large multi-project builds (e.g. elasticsearch) this pulled hundreds of MB of distribution archives — .deb / .tar.gz / .zip packaging outputs that some configurations expose as dependencies of the build target itself, not as library deps. User observed: `> :qa:packaging:socketFacts > elasticsearch-7.17.22-amd64.deb > 88.3 MiB/310.4 MiB downloaded` mid-task. Fix: read `artifact.type` / `artifact.extension` / `artifact.classifier` from already-fetched POM/GMM metadata. Never touch `artifact.file` — that's what triggers the actual file download. Replace the `!file.isDirectory()` filter (which forced fetch) with a name-based filter (`INTERNAL_ARTIFACT_TYPES`: java-classes-directory, java-resources-directory, android-classes-directory, android-resources-directory) that drops Gradle-internal variants we don't want to surface as `qualifiers.ext`. Verified locally: - commons-io:commons-io:2.15.1 resolves cleanly under cleared cache, emits qualifiers.ext='jar', no jar in ~/.gradle/caches/modules-2/files-2.1/commons-io after the run - 11/11 e2e fixture tests still green, qualifiers preserved across single-module / multi-module / Android / KMP fixtures * feat(manifest): emit single facts file per build, drop intra-project deps Previously each subproject's `socketFacts` task emitted its own `.socket.facts.json`, and intra-project deps (`project(':lib')`) flowed into every consuming subproject's file with no signal that they're project-local. Downstream consumers (coana `mvn dependency:get`) would then try to resolve them against Maven Central and fail because those coordinates don't publish to a repository. Restructured to mirror the pattern at program-analysis/plugins/coana-gradle-script/coana-workspaces.init.gradle: - shared accumulators (`nodes`, `directIds`, `reportedUnresolved`, `projectKeys`) live on `gradle.ext.socketFactsState` as synchronized collections, so --parallel-enabled builds don't race - per-subproject `socketFactsCollect` tasks resolve that subproject's configurations and contribute to the shared state - a root `socketFacts` task depends on every collector (via `gradle.projectsEvaluated`) and serializes the aggregated graph to a single `.socket.facts.json` at the build root Intra-project deps are dropped at visit time: when a ResolvedDependency matches a known project's `group:name`, we return an empty producedIds set, don't emit a node, and don't recurse. The externals those project deps expose are picked up via the intra-project's own collector instead (Gradle's classpath inheritance gives the consumer subproject those externals directly, and the producer subproject's collector emits them with `direct: true` from its own classpath position). Verified across the fixture matrix: - single-module-java: unchanged shape (no intra-project deps) - multi-module-java: emits ONE file at build root, no `com.example.socket:lib` or `com.example.socket:app` entries, guava (lib api) + slf4j (app impl) both present - unresolved-deps, kotlin-multiplatform, android-library: unchanged Side benefits: no more empty `.socket.facts.json` files on aggregator subprojects, and the unresolved-dep warning dedupes across the build. * fix(ci): correct gradle/actions v4 commit SHA pin Zizmor's `impostor-commit` audit failed because the SHA I used (0b6dd653...) was the v4 *tag object* SHA, not the commit it dereferences to. gradle/actions uses nested annotated tags — v4 points to another tag (48b5f213...) which points to commit ed408507eac0... That last hop is what should be pinned. setup-java@v4 was already correct (resolves directly to a commit). * fix(scan): stop stripping .socket.facts.json from --reach upload; clean up after success Two paired changes so the new `socket manifest gradle --facts` producer flow works end-to-end with `socket scan create . --reach`: perform-reachability-analysis.mts: previously stripped any `.socket.facts.json` from the manifest upload to compute-artifacts. That filter was added when the only source of such a file in the scan dir was a leftover post-reachability output from coana. With the new producer flow that file is *legitimate input* to compute-artifacts, so the strip would have made the backend return 0 artifacts and coana would have nothing to analyze. Removed. handle-create-new-scan.mts: on a successful scan, unlink the `.socket.facts.json` coana wrote at the path we instructed it to write to (`reachResult.data.reachabilityReport`). Failed scans leave the file in place for debugging. We only delete the path we explicitly control — producer-written `.socket.facts.json` is user-owned input and not touched here, though in the --reach path coana has already overwritten that file with its enriched output so it ends up being the same file path that gets removed anyway. Verified end-to-end against the single-module-java fixture using `socket manifest setup` (writing `defaults.manifest.gradle.facts: true` to socket.json) and `socket scan create . --reach --auto-manifest`: each run regenerates the facts file via the gradle generator, uploads to compute-artifacts, coana enriches, scan succeeds, and the file is cleaned up — making the command safely repeatable. Behavior note for users: because coana writes to `.socket.facts.json` and we delete that file after a successful scan, any standalone producer output also at that path gets removed. Users running `socket manifest gradle --facts` followed by `socket scan create . --reach` will need to re-run the producer before each subsequent scan, OR (recommended) drive the producer via `--auto-manifest` which regenerates on every run. * chore(gitignore): scope gradle-facts ignores to a nested .gitignore Move the gradle-facts-related transient patterns from the root .gitignore into a nested .gitignore at test/fixtures/commands/manifest/gradle-facts/. Patterns become unanchored (`.gradle/`, `build/`, `.socket.facts.json`) and tighten to the three things `socket manifest gradle --facts` runs actually produce — the previously listed `pom.xml` and `local.properties` patterns weren't generated by the --facts flow and have been dropped. Keeps the root .gitignore tidy and makes the transience signal visible to anyone working in the fixtures directory. * review(#1318): address PR feedback (findings 1, 2, 3, 4, 6, 8) Finding 1 — Kotlin Multiplatform fixture: bump `org.jetbrains.kotlin.multiplatform` from 1.9.25 (officially supports Gradle 6.8.3-8.6) to 2.1.0 (supports Gradle 9.x). CI installs Gradle 9.2.1 so the old plugin version would fail. Smoke-tested locally. Finding 2 — `handle-create-new-scan.mts`: rename `filterToCdxSpdxAndFactsFiles` → `filterToCdxSpdxOnly`. The "AndFactsFiles" branch in the function was dead at the only call site because the caller had already stripped `.socket.facts.json` from the input. Dropped the basename check from the function body; semantics unchanged. Finding 3 — Aggregator's `node.children` read now happens under `synchronized(nodes)` to mirror the writers in each `socketFactsCollect` doLast. Task dependencies via `aggregator.dependsOn(collector)` already establish a happens-before edge, but explicit synchronization makes the contract local and removes any implicit reliance on Gradle's task-graph memory visibility semantics. The aggregator snapshots into plain List/Map values first, then builds the JSON outside the critical section. Finding 4 — `convert-gradle-to-facts.mts`: replace `output.stdout.replace(regex, callback)`-for-side-effects pattern with `Array.from(stdout.matchAll(regex), m => m[1])`. Reads more directly. Finding 6 — Cross-reference comments at both `ext.SOCKET_FACTS_FILENAME` (Groovy) and `DOT_SOCKET_DOT_FACTS_JSON` (TS) noting that the two values are intentionally duplicated (Groovy can't import a TS constant) and must be kept in sync. Finding 8 — When the Gradle script's `components.isEmpty()` branch fires (no resolvable dependencies in the build), the TS wrapper no longer prints a bare "Reported exports:" header followed by nothing. It now suppresses the header when `matchAll` returns zero exports, and surfaces the `[socket-facts] no resolvable dependencies` skip message from Gradle stdout if present so the user understands why the file wasn't written. Not changed (replies posted separately): - Finding 5: `.toLowerCase()` IS load-bearing on case-insensitive filesystems (macOS HFS+, Windows). The constant is lowercase; the input might not be. Keeping the normalization. - Finding 7: function ordering left to match the existing `convert_gradle_to_maven.mts` pattern — consistency with precedent wins over the literal CLAUDE.md rule here. * release: v1.1.98 - Bump Coana CLI to 15.3.8 (package.json + pnpm-lock.yaml). - CHANGELOG entry for the new `socket manifest gradle --facts` / `socket manifest kotlin --facts` mode shipping alongside the previously-unreleased Bazel work, plus the Coana bump. - Drop now-redundant `.toLowerCase()` from the .socket.facts.json basename check in handle-create-new-scan.mts (per PR review Finding 5 — we control every producer of this filename). * ci: probe — comment out gradle/actions/setup-gradle to isolate startup_failure * ci: probe 2 — also remove setup-java to verify nothing in my changes is the cause * ci: revert e2e-tests.yml + drop gradle integration suite actions/setup-java@v4 isn't on the repo's GitHub Actions allowlist (confirmed by isolating it via probe — workflow_dispatch and pull_request both failed with startup_failure at 0s while it was included; removing it made the workflow start). The PR can't ship with the e2e workflow file failing to start. Reverting .github/workflows/e2e-tests.yml to match v1.x exactly, and deleting src/commands/manifest/socket-facts-init-gradle.e2e.test.mts rather than leaving it in place auto-skipping (which would look like real test coverage that's actually silent). Test fixtures under test/fixtures/commands/manifest/gradle-facts/ remain — they're still useful for manual local `gradle --init-script ... socketFacts` runs while iterating on the script. Follow-up: wire JDK + Gradle into the e2e job (either by getting actions/setup-java added to the org allowlist, or inline-installing both via curl + SHA verify per the existing pnpm/sfw-free pattern) and restore the e2e suite.
1 parent ac00b7c commit d1c99be

32 files changed

Lines changed: 930 additions & 72 deletions

.config/rollup.dist.config.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ async function copyInitGradle() {
7979
await fs.copyFile(filepath, destPath)
8080
}
8181

82+
async function copySocketFactsInitGradle() {
83+
const filepath = path.join(
84+
constants.srcPath,
85+
'commands/manifest/socket-facts.init.gradle',
86+
)
87+
const destPath = path.join(constants.distPath, 'socket-facts.init.gradle')
88+
await fs.copyFile(filepath, destPath)
89+
}
90+
8291
async function copyBashCompletion() {
8392
const filepath = path.join(
8493
constants.srcPath,
@@ -458,6 +467,7 @@ export default async () => {
458467
async writeBundle() {
459468
await Promise.all([
460469
copyInitGradle(),
470+
copySocketFactsInitGradle(),
461471
copyBashCompletion(),
462472
updatePackageJson(),
463473
// Remove dist/vendor.js.map file.

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
8-
9-
### Added
108
- **`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`.
119
- **`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.
1210

11+
## [1.1.98](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.98) - 2026-05-22
12+
13+
### Added
14+
- **`socket manifest gradle --facts [beta]`** (and its `socket manifest kotlin --facts` alias) — Emit a `.socket.facts.json` dependency graph from a Gradle build, consumable by `socket scan create --reach` as pregenerated SBOM input for Tier 1 reachability. Toggle also exposed via the `socket manifest setup` wizard for use with `--auto-manifest`.
15+
16+
### Changed
17+
- Updated the Coana CLI to v `15.3.8`.
18+
1319
## [1.1.101](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.101) - 2026-05-22
1420

1521
### Changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "socket",
3-
"version": "1.1.101",
3+
"version": "1.1.102",
44
"description": "CLI for Socket.dev",
55
"homepage": "https://github.com/SocketDev/socket-cli",
66
"license": "MIT AND OFL-1.1",

src/commands/manifest/cmd-manifest-gradle.mts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import path from 'node:path'
33
import { debugFn } from '@socketsecurity/registry/lib/debug'
44
import { logger } from '@socketsecurity/registry/lib/logger'
55

6+
import { convertGradleToFacts } from './convert-gradle-to-facts.mts'
67
import { convertGradleToMaven } from './convert_gradle_to_maven.mts'
78
import constants, { REQUIREMENTS_TXT, SOCKET_JSON } from '../../constants.mts'
89
import { commonFlags } from '../../flags.mts'
@@ -28,6 +29,11 @@ const config: CliCommandConfig = {
2829
type: 'string',
2930
description: 'Location of gradlew binary to use, default: CWD/gradlew',
3031
},
32+
facts: {
33+
type: 'boolean',
34+
description:
35+
'Emit a Socket facts JSON file (`.socket.facts.json`) describing the resolved dependency graph instead of generating `pom.xml` files',
36+
},
3137
gradleOpts: {
3238
type: 'string',
3339
description:
@@ -110,7 +116,7 @@ async function run(
110116
sockJson?.defaults?.manifest?.gradle,
111117
)
112118

113-
let { bin, gradleOpts, verbose } = cli.flags
119+
let { bin, facts, gradleOpts, verbose } = cli.flags
114120

115121
// Set defaults for any flag/arg that is not given. Check socket.json first.
116122
if (!bin) {
@@ -140,6 +146,14 @@ async function run(
140146
verbose = false
141147
}
142148
}
149+
if (facts === undefined) {
150+
if (sockJson.defaults?.manifest?.gradle?.facts !== undefined) {
151+
facts = sockJson.defaults?.manifest?.gradle?.facts
152+
logger.info(`Using default --facts from ${SOCKET_JSON}:`, facts)
153+
} else {
154+
facts = false
155+
}
156+
}
143157

144158
if (verbose) {
145159
logger.group('- ', parentName, config.commandName, ':')
@@ -175,13 +189,25 @@ async function run(
175189
return
176190
}
177191

192+
const parsedGradleOpts = String(gradleOpts || '')
193+
.split(' ')
194+
.map(s => s.trim())
195+
.filter(Boolean)
196+
197+
if (facts) {
198+
await convertGradleToFacts({
199+
bin: String(bin),
200+
cwd,
201+
gradleOpts: parsedGradleOpts,
202+
verbose: Boolean(verbose),
203+
})
204+
return
205+
}
206+
178207
await convertGradleToMaven({
179208
bin: String(bin),
180209
cwd,
181-
gradleOpts: String(gradleOpts || '')
182-
.split(' ')
183-
.map(s => s.trim())
184-
.filter(Boolean),
210+
gradleOpts: parsedGradleOpts,
185211
verbose: Boolean(verbose),
186212
})
187213
}

src/commands/manifest/cmd-manifest-gradle.test.mts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ describe('socket manifest gradle', async () => {
2424
2525
Options
2626
--bin Location of gradlew binary to use, default: CWD/gradlew
27+
--facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph instead of generating \`pom.xml\` files
2728
--gradle-opts Additional options to pass on to ./gradlew, see \`./gradlew --help\`
2829
--verbose Print debug messages
2930
@@ -85,4 +86,22 @@ describe('socket manifest gradle', async () => {
8586
expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
8687
},
8788
)
89+
90+
cmdit(
91+
['manifest', 'gradle', '--facts', FLAG_DRY_RUN, FLAG_CONFIG, '{}'],
92+
'should accept --facts with dry-run',
93+
async cmd => {
94+
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
95+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
96+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
97+
"
98+
_____ _ _ /---------------
99+
| __|___ ___| |_ ___| |_ | CLI: <redacted>
100+
|__ | * | _| '_| -_| _| | token: <redacted>, org: <redacted>
101+
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest gradle\`, cwd: <redacted>"
102+
`)
103+
104+
expect(code, '--facts --dry-run should exit with code 0').toBe(0)
105+
},
106+
)
88107
})

src/commands/manifest/cmd-manifest-kotlin.mts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import path from 'node:path'
33
import { debugFn } from '@socketsecurity/registry/lib/debug'
44
import { logger } from '@socketsecurity/registry/lib/logger'
55

6+
import { convertGradleToFacts } from './convert-gradle-to-facts.mts'
67
import { convertGradleToMaven } from './convert_gradle_to_maven.mts'
78
import constants, { REQUIREMENTS_TXT, SOCKET_JSON } from '../../constants.mts'
89
import { commonFlags } from '../../flags.mts'
@@ -33,6 +34,11 @@ const config: CliCommandConfig = {
3334
type: 'string',
3435
description: 'Location of gradlew binary to use, default: CWD/gradlew',
3536
},
37+
facts: {
38+
type: 'boolean',
39+
description:
40+
'Emit a Socket facts JSON file (`.socket.facts.json`) describing the resolved dependency graph instead of generating `pom.xml` files',
41+
},
3642
gradleOpts: {
3743
type: 'string',
3844
description:
@@ -115,7 +121,7 @@ async function run(
115121
sockJson?.defaults?.manifest?.gradle,
116122
)
117123

118-
let { bin, gradleOpts, verbose } = cli.flags
124+
let { bin, facts, gradleOpts, verbose } = cli.flags
119125

120126
// Set defaults for any flag/arg that is not given. Check socket.json first.
121127
if (!bin) {
@@ -145,6 +151,14 @@ async function run(
145151
verbose = false
146152
}
147153
}
154+
if (facts === undefined) {
155+
if (sockJson.defaults?.manifest?.gradle?.facts !== undefined) {
156+
facts = sockJson.defaults?.manifest?.gradle?.facts
157+
logger.info(`Using default --facts from ${SOCKET_JSON}:`, facts)
158+
} else {
159+
facts = false
160+
}
161+
}
148162

149163
if (verbose) {
150164
logger.group('- ', parentName, config.commandName, ':')
@@ -180,13 +194,25 @@ async function run(
180194
return
181195
}
182196

197+
const parsedGradleOpts = String(gradleOpts || '')
198+
.split(' ')
199+
.map(s => s.trim())
200+
.filter(Boolean)
201+
202+
if (facts) {
203+
await convertGradleToFacts({
204+
bin: String(bin),
205+
cwd,
206+
gradleOpts: parsedGradleOpts,
207+
verbose: Boolean(verbose),
208+
})
209+
return
210+
}
211+
183212
await convertGradleToMaven({
184213
bin: String(bin),
185214
cwd,
186-
gradleOpts: String(gradleOpts || '')
187-
.split(' ')
188-
.map(s => s.trim())
189-
.filter(Boolean),
215+
gradleOpts: parsedGradleOpts,
190216
verbose: Boolean(verbose),
191217
})
192218
}

src/commands/manifest/cmd-manifest-kotlin.test.mts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ describe('socket manifest kotlin', async () => {
2424
2525
Options
2626
--bin Location of gradlew binary to use, default: CWD/gradlew
27+
--facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph instead of generating \`pom.xml\` files
2728
--gradle-opts Additional options to pass on to ./gradlew, see \`./gradlew --help\`
2829
--verbose Print debug messages
2930
@@ -85,4 +86,22 @@ describe('socket manifest kotlin', async () => {
8586
expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
8687
},
8788
)
89+
90+
cmdit(
91+
['manifest', 'kotlin', '--facts', FLAG_DRY_RUN, FLAG_CONFIG, '{}'],
92+
'should accept --facts with dry-run',
93+
async cmd => {
94+
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
95+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
96+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
97+
"
98+
_____ _ _ /---------------
99+
| __|___ ___| |_ ___| |_ | CLI: <redacted>
100+
|__ | * | _| '_| -_| _| | token: <redacted>, org: <redacted>
101+
|_____|___|___|_,_|___|_|.dev | Command: \`socket manifest kotlin\`, cwd: <redacted>"
102+
`)
103+
104+
expect(code, '--facts --dry-run should exit with code 0').toBe(0)
105+
},
106+
)
88107
})

0 commit comments

Comments
 (0)