Skip to content

Commit 971872d

Browse files
jfblaaclaude
andcommitted
refactor(manifest): one build-file record per tool, per-directory DSI config
- Move build-file names, the POM pattern and its case rule into one BUILD_FILES record; discovery walks narrow globs again and matches after. - Build every discovered POM with its own --file; a user -f/--file in mavenOpts is dropped with a warning during config resolution. - Resolve DSI and wizard settings once per directory. - Require socketConfig for discovery, loaded through loadSocketConfig. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f7ae575 commit 971872d

10 files changed

Lines changed: 308 additions & 180 deletions

‎src/commands/manifest/discover-manifest-roots.mts‎

Lines changed: 40 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from 'node:path'
33

44
import micromatch from 'micromatch'
55

6-
import { isAddressedByFile } from './scripts/build-tool.mts'
6+
import { BUILD_FILES } from './scripts/build-tool.mts'
77
import { realpathOrResolved } from '../../utils/fs.mts'
88
import { globWithGitIgnore } from '../../utils/glob.mts'
99
import { excludePathToScanIgnores } from '../scan/exclude-paths.mts'
@@ -15,31 +15,15 @@ import type { SocketYml } from '@socketsecurity/config'
1515

1616
const BUILD_TOOLS: BuildTool[] = ['gradle', 'maven', 'sbt']
1717

18-
// One glob set per ecosystem's build-descriptor marker(s); `settings.gradle(.kts)`
19-
// covers Kotlin-DSL roots that have no root `build.gradle`. Maven mirrors the
20-
// `pomxml` pattern of the API's supported files, so every POM a scan uploads is
21-
// also a build root here.
22-
const MARKERS_BY_TOOL: Record<BuildTool, string[]> = {
23-
__proto__: null,
24-
gradle: [
25-
'build.gradle',
26-
'build.gradle.kts',
27-
'settings.gradle',
28-
'settings.gradle.kts',
29-
],
30-
maven: ['{*-*.,}pom{.xml,}'],
31-
sbt: ['build.sbt'],
32-
} as unknown as Record<BuildTool, string[]>
33-
34-
// Case-insensitive for POMs, as a scan matches them; Gradle and sbt look for
35-
// their build files by exact name.
36-
const MARKER_MATCHERS_BY_TOOL = new Map(
18+
const BUILD_FILE_MATCHERS = new Map(
3719
BUILD_TOOLS.map(tool => {
38-
const options = { dot: true, nocase: tool === 'maven' }
39-
const matchers = MARKERS_BY_TOOL[tool].map(marker =>
40-
micromatch.matcher(marker, options),
41-
)
42-
return [tool, (basename: string) => matchers.some(m => m(basename))]
20+
const files = BUILD_FILES[tool]
21+
return [
22+
tool,
23+
files.byFile
24+
? micromatch.matcher(files.nocaseGlob, { dot: true, nocase: true })
25+
: (basename: string) => files.names.includes(basename),
26+
]
4327
}),
4428
)
4529

@@ -51,11 +35,11 @@ export function buildRootPath(root: BuildRoot): string {
5135
return path.join(root.dir, root.entryFile ?? '')
5236
}
5337

54-
function markerTool(
38+
function buildFileTool(
5539
tools: readonly BuildTool[],
5640
basename: string,
5741
): BuildTool | undefined {
58-
return tools.find(tool => MARKER_MATCHERS_BY_TOOL.get(tool)?.(basename))
42+
return tools.find(tool => BUILD_FILE_MATCHERS.get(tool)?.(basename))
5943
}
6044

6145
// A disabled ecosystem is skipped entirely by the glob scan below, so a
@@ -115,12 +99,11 @@ export function isBuildRootCovered(
11599
tool: BuildTool,
116100
root: BuildRoot,
117101
): boolean {
118-
if (root.entryFile) {
119-
return covered.has(buildRootPath(root))
102+
const files = BUILD_FILES[tool]
103+
if (files.byFile) {
104+
return !!root.entryFile && covered.has(buildRootPath(root))
120105
}
121-
return MARKERS_BY_TOOL[tool].some(marker =>
122-
covered.has(path.join(root.dir, marker)),
123-
)
106+
return files.names.some(name => covered.has(path.join(root.dir, name)))
124107
}
125108

126109
function compareStrings(a: string, b: string): number {
@@ -156,7 +139,7 @@ export async function findBuildToolCandidates({
156139
excludePaths?: string[] | undefined
157140
// The socket.yml a scan applies to its upload, so a build file a scan would
158141
// not upload is not built either.
159-
socketConfig?: SocketYml | undefined
142+
socketConfig: SocketYml | undefined
160143
sockJson: SocketJson
161144
}): Promise<Map<BuildTool, BuildRoot[]>> {
162145
const enabledTools = BUILD_TOOLS.filter(
@@ -170,35 +153,36 @@ export async function findBuildToolCandidates({
170153
const additionalIgnores = (excludePaths ?? []).flatMap(
171154
excludePathToScanIgnores,
172155
)
173-
// Markers are matched in the filter, after the ignores, so --exclude-paths
174-
// and .gitignore stay case-sensitive as they are for a scan.
175-
const hits = await globWithGitIgnore(['**/*'], {
176-
absolute: true,
177-
additionalIgnores,
178-
cwd,
179-
filter: hit => !!markerTool(enabledTools, path.basename(hit)),
180-
socketConfig,
181-
})
156+
// Build files are matched after the walk, as a scan matches them after its
157+
// ignores, so --exclude-paths and .gitignore stay case-sensitive.
158+
const hits = await globWithGitIgnore(
159+
enabledTools.flatMap(tool => {
160+
const files = BUILD_FILES[tool]
161+
return (files.byFile ? [files.walkGlob] : files.names).map(
162+
glob => `**/${glob}`,
163+
)
164+
}),
165+
{ absolute: true, additionalIgnores, cwd, socketConfig },
166+
)
182167

183168
const rootsByTool = new Map<BuildTool, Map<string, BuildRoot>>(
184169
enabledTools.map(tool => [tool, new Map<string, BuildRoot>()]),
185170
)
186-
const resolvedHits = await Promise.all(
187-
hits.map(async hit => {
188-
const basename = path.basename(hit)
189-
return {
190-
basename,
171+
const matched = hits.flatMap(hit => {
172+
const basename = path.basename(hit)
173+
const tool = buildFileTool(enabledTools, basename)
174+
return tool ? [{ basename, hit, tool }] : []
175+
})
176+
const roots = await Promise.all(
177+
matched.map(async ({ basename, hit, tool }) => ({
178+
root: {
191179
dir: await realpathOrResolved(path.dirname(hit)),
192-
// Set: the glob filter kept only marker hits.
193-
tool: markerTool(enabledTools, basename) as BuildTool,
194-
}
195-
}),
180+
entryFile: BUILD_FILES[tool].byFile ? basename : undefined,
181+
},
182+
tool,
183+
})),
196184
)
197-
for (const { basename, dir, tool } of resolvedHits) {
198-
const root = {
199-
dir,
200-
entryFile: isAddressedByFile(tool) ? basename : undefined,
201-
}
185+
for (const { root, tool } of roots) {
202186
rootsByTool.get(tool)?.set(buildRootPath(root), root)
203187
}
204188

‎src/commands/manifest/discover-manifest-roots.test.mts‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
sortByDepthThenPath,
1212
} from './discover-manifest-roots.mts'
1313
import { testPath } from '../../../test/utils.mts'
14-
import { findSocketYmlSync } from '../../utils/config.mts'
14+
import { loadSocketConfig } from '../../utils/config.mts'
1515

1616
import type { BuildRoot } from './discover-manifest-roots.mts'
1717
import type { SocketJson } from '../../utils/socket-json.mts'
@@ -35,6 +35,7 @@ async function pomNamesRoots(excludePaths?: string[]): Promise<string[]> {
3535
const candidates = await findBuildToolCandidates({
3636
cwd: pomNames,
3737
excludePaths,
38+
socketConfig: undefined,
3839
sockJson: {} as SocketJson,
3940
})
4041
return (candidates.get('maven') ?? []).map(r =>
@@ -57,10 +58,9 @@ async function rootsBesideIgnoredPom(
5758
)
5859
await fs.writeFile(path.join(cwd, 'pom.xml'), '<project/>')
5960
await fs.writeFile(path.join(cwd, ignoreFile.name), ignoreFile.content)
60-
const socketYml = findSocketYmlSync(cwd)
6161
const candidates = await findBuildToolCandidates({
6262
cwd,
63-
socketConfig: socketYml.ok ? socketYml.data?.parsed : undefined,
63+
socketConfig: loadSocketConfig(cwd),
6464
sockJson: {} as SocketJson,
6565
})
6666
return candidates
@@ -79,6 +79,7 @@ describe('findBuildToolCandidates', () => {
7979
it('discovers maven, gradle, and sbt candidates in depth order', async () => {
8080
const candidates = await findBuildToolCandidates({
8181
cwd: monorepo,
82+
socketConfig: undefined,
8283
sockJson: {} as SocketJson,
8384
})
8485

@@ -104,6 +105,7 @@ describe('findBuildToolCandidates', () => {
104105
it('lists a reactor root before its own members (depth-ascending)', async () => {
105106
const candidates = await findBuildToolCandidates({
106107
cwd: monorepo,
108+
socketConfig: undefined,
107109
sockJson: {} as SocketJson,
108110
})
109111
const maven = dirsOf(candidates.get('maven'))
@@ -118,6 +120,7 @@ describe('findBuildToolCandidates', () => {
118120
it('includes a gradle project nested inside a maven candidate directory tree', async () => {
119121
const candidates = await findBuildToolCandidates({
120122
cwd: monorepo,
123+
socketConfig: undefined,
121124
sockJson: {} as SocketJson,
122125
})
123126
expect(dirsOf(candidates.get('gradle'))).toContain(
@@ -128,6 +131,7 @@ describe('findBuildToolCandidates', () => {
128131
it('lists a dual-marker directory on both ecosystems', async () => {
129132
const candidates = await findBuildToolCandidates({
130133
cwd: monorepo,
134+
socketConfig: undefined,
131135
sockJson: {} as SocketJson,
132136
})
133137
const dualDir = path.join(monorepo, 'dual-marker-dir')
@@ -138,6 +142,7 @@ describe('findBuildToolCandidates', () => {
138142
it('drops a disabled ecosystem entirely', async () => {
139143
const candidates = await findBuildToolCandidates({
140144
cwd: monorepo,
145+
socketConfig: undefined,
141146
sockJson: {
142147
defaults: { manifest: { maven: { disabled: true } } },
143148
} as SocketJson,
@@ -150,6 +155,7 @@ describe('findBuildToolCandidates', () => {
150155
const candidates = await findBuildToolCandidates({
151156
cwd: monorepo,
152157
excludePaths: ['reactor'],
158+
socketConfig: undefined,
153159
sockJson: {} as SocketJson,
154160
})
155161
expect(candidates.get('maven')).toEqual([
@@ -164,6 +170,7 @@ describe('findBuildToolCandidates', () => {
164170
it('lists a gradle directory once despite several markers', async () => {
165171
const candidates = await findBuildToolCandidates({
166172
cwd: monorepo,
173+
socketConfig: undefined,
167174
sockJson: {} as SocketJson,
168175
})
169176
const standalone = path.join(monorepo, 'standalone-gradle')

0 commit comments

Comments
 (0)