Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Updated the Coana CLI to v `15.10.55`.
- Generated Maven, Gradle and sbt `.socket.facts.json` files are now substantially smaller, making uploads for large JVM projects faster and more reliable.

### Fixed
- Running `socket manifest maven` or `socket manifest scala` on a directory without a build now fails with a clear message, instead of crashing (Maven) or silently producing a bogus Socket facts file (sbt).

## [1.1.178](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.178) - 2026-09-23

### Changed
Expand Down
9 changes: 9 additions & 0 deletions src/commands/manifest/run-manifest-facts.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
expandEnvVarRefs,
formatMissingEnvVarRefs,
} from './expand-env-var-refs.mts'
import { looksLikeSbtBuild } from './scripts/build-tool.mts'
import { renderResolutionErrorReport } from './scripts/resolution-report-render.mts'
import { runManifestScript } from './scripts/run.mts'
import { accumulateSidecar } from './scripts/sidecar.mts'
Expand Down Expand Up @@ -93,6 +94,14 @@ export async function runManifestFacts({
resolvedJavaHome = expanded.value
}

if (ecosystem === 'sbt' && !looksLikeSbtBuild(cwd)) {
process.exitCode = 1
logger.fail(
`No sbt build found at \`${cwd}\` (expected a build.sbt file or a project directory).`,
)
return null
}

logger.info(
`Generating Socket facts for the ${ecosystem} project at \`${cwd}\` ...`,
)
Expand Down
45 changes: 45 additions & 0 deletions src/commands/manifest/run-manifest-facts.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,48 @@ describe('runManifestFacts - sidecar', () => {
)
})
})

describe('runManifestFacts - sbt build detection', () => {
let cwd = ''

beforeEach(async () => {
cwd = await fs.mkdtemp(path.join(tmpdir(), 'run-manifest-facts-'))
vi.mocked(runManifestScript).mockReset()
process.exitCode = undefined
})
afterEach(async () => {
await fs.rm(cwd, { recursive: true, force: true })
process.exitCode = undefined
})

it('fails without running sbt when the directory has no sbt build', async () => {
const outcome = await runManifestFacts({
...baseArgs,
cwd,
ecosystem: 'sbt',
})

expect(outcome).toBeNull()
expect(process.exitCode).toBe(1)
expect(runManifestScript).not.toHaveBeenCalled()
await expect(
fs.access(path.join(cwd, '.socket.facts.json')),
).rejects.toThrow()
})

it.each(['build.sbt', 'project'])(
'runs sbt when the directory has %s',
async marker => {
if (marker === 'project') {
await fs.mkdir(path.join(cwd, marker))
} else {
await fs.writeFile(path.join(cwd, marker), '')
}
vi.mocked(runManifestScript).mockResolvedValue(okResult())

await runManifestFacts({ ...baseArgs, cwd, ecosystem: 'sbt' })

expect(runManifestScript).toHaveBeenCalledOnce()
},
)
})
10 changes: 10 additions & 0 deletions src/commands/manifest/scripts/build-tool.mts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ const BUILD_TOOL_WRAPPER = {
maven: 'mvnw',
} as unknown as Partial<Record<BuildTool, string>>

// sbt happily runs in any directory, synthesizing a default project from its
// name, so an sbt run outside a build yields a plausible but bogus SBOM. Maven
// and Gradle refuse such a directory themselves.
export function looksLikeSbtBuild(projectDir: string): boolean {
return (
existsSync(resolve(projectDir, 'build.sbt')) ||
existsSync(resolve(projectDir, 'project'))
)
}

export function resolveBuildToolBin(
tool: BuildTool,
projectDir: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ public void run(MavenSession session, List<MavenProject> reactor, File rootDir,
rec(lines, "meta", "maven", mavenVersion, System.getProperty("java.version"));

for (MavenProject module : reactor) {
// No basedir: Maven's stand-in project for a directory without a POM. Skipping it lets Maven's
// own "no POM in this directory" error surface instead of an NPE.
if (module.getBasedir() == null) continue;
String ws = SocketSupport.workspace(rootDir.toPath(), module.getBasedir().toPath());
if (SocketSupport.isExcludedPath(ws, excludes)) continue;
rec(lines, "project", ws, module.getGroupId(), module.getArtifactId(), module.getVersion(), ws);
Expand All @@ -109,6 +112,7 @@ public void run(MavenSession session, List<MavenProject> reactor, File rootDir,
Set<Failure> failures = new LinkedHashSet<>();
int rootIdx = 0;
for (MavenProject module : reactor) {
if (module.getBasedir() == null) continue;
String ws = SocketSupport.workspace(rootDir.toPath(), module.getBasedir().toPath());
// A wholly excluded reactor module is not resolved (matches the project-record skip above).
if (SocketSupport.isExcludedPath(ws, excludes)) continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public static void run(List<MavenProject> reactor, File rootDir, Options opts, S
rec(lines, "meta", "maven", mavenVersion, System.getProperty("java.version"));

for (MavenProject module : reactor) {
// No basedir: Maven's stand-in project for a directory without a POM. Skipping it lets Maven's
// own "no POM in this directory" error surface instead of an NPE.
if (module.getBasedir() == null) continue;
String ws = SocketSupport.workspace(rootDir.toPath(), module.getBasedir().toPath());
if (SocketSupport.isExcludedPath(ws, excludes)) continue;
rec(lines, "project", ws, module.getGroupId(), module.getArtifactId(), module.getVersion(), ws);
Expand Down
Loading