Skip to content

Commit ec863f0

Browse files
committed
feat(fix): add --package-managers flag
Forward Coana's new --package-managers filter (coana-tech/coana-package-manager#2214) through socket fix so users can narrow fix computation to specific package managers within an ecosystem (e.g. only PNPM in a monorepo that mixes pnpm/yarn/npm). The flag accepts comma- or space-separated values, is case-insensitive (normalized to uppercase before validation and forward), and is passed to both `find-vulnerabilities` and `compute-fixes-and-upgrade-purls`. When combined with --ecosystems, both filters must match (Coana intersects them per-artifact). Valid values mirror Coana's getFilterablePackageManagers(): CARGO, COMPOSER, GO, GRADLE, MAVEN, NPM, NUGET, PIPENV, PIP_REQUIREMENTS, PNPM, POETRY, RUBYGEMS, RUSH, SBT, YARN. Out of scope (matching the Coana PR): --package-managers is not added to socket scan reach / the coana run command — that command's --purl-types runs at ecosystem-level pre-install and a per-PM filter would require a larger refactor in Coana.
1 parent 387326b commit ec863f0

6 files changed

Lines changed: 181 additions & 0 deletions

File tree

src/commands/fix/cmd-fix.integration.test.mts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ describe('socket fix', async () => {
183183
--no-apply-fixes Compute fixes only, do not apply them. Logs what upgrades would be applied. If combined with --output-file, the output file will contain the upgrades that would be applied.
184184
--no-major-updates Do not suggest or apply fixes that require major version updates of direct or transitive dependencies
185185
--output-file Path to store upgrades as a JSON file at this path.
186+
--package-managers Limit fix analysis to specific package managers within an ecosystem (e.g. NPM, PNPM, YARN, MAVEN, POETRY). Accepts space- or comma-separated values and is case-insensitive. When combined with --ecosystems, an artifact must satisfy both filters.
186187
--pr-limit Maximum number of pull requests to create in CI mode (default 10). Has no effect in local mode.
187188
--range-style Define how dependency version ranges are updated in package.json (default 'preserve').
188189
Available styles:
@@ -1127,6 +1128,98 @@ describe('socket fix', async () => {
11271128
)
11281129
})
11291130

1131+
describe('--package-managers flag behavior', () => {
1132+
cmdit(
1133+
[
1134+
'fix',
1135+
FLAG_DRY_RUN,
1136+
'--package-managers',
1137+
'NPM',
1138+
FLAG_CONFIG,
1139+
'{"apiToken":"fakeToken"}',
1140+
],
1141+
'should accept --package-managers with single value',
1142+
async cmd => {
1143+
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
1144+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Not saving"`)
1145+
expect(code, 'should exit with code 0').toBe(0)
1146+
},
1147+
)
1148+
1149+
cmdit(
1150+
[
1151+
'fix',
1152+
FLAG_DRY_RUN,
1153+
'--package-managers',
1154+
'npm,pnpm',
1155+
FLAG_CONFIG,
1156+
'{"apiToken":"fakeToken"}',
1157+
],
1158+
'should accept --package-managers comma-separated case-insensitive',
1159+
async cmd => {
1160+
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
1161+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Not saving"`)
1162+
expect(code, 'should exit with code 0').toBe(0)
1163+
},
1164+
)
1165+
1166+
cmdit(
1167+
[
1168+
'fix',
1169+
FLAG_DRY_RUN,
1170+
'--package-managers',
1171+
'NPM',
1172+
'--package-managers',
1173+
'PNPM',
1174+
FLAG_CONFIG,
1175+
'{"apiToken":"fakeToken"}',
1176+
],
1177+
'should accept multiple --package-managers flags',
1178+
async cmd => {
1179+
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
1180+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Not saving"`)
1181+
expect(code, 'should exit with code 0').toBe(0)
1182+
},
1183+
)
1184+
1185+
cmdit(
1186+
[
1187+
'fix',
1188+
FLAG_DRY_RUN,
1189+
'--package-managers',
1190+
'FOO',
1191+
FLAG_CONFIG,
1192+
'{"apiToken":"fakeToken"}',
1193+
],
1194+
'should fail with invalid --package-managers value',
1195+
async cmd => {
1196+
const { code, stderr, stdout } = await spawnSocketCli(binCliPath, cmd)
1197+
const output = stdout + stderr
1198+
expect(output).toContain('Invalid package manager')
1199+
expect(code, 'should exit with non-zero code').not.toBe(0)
1200+
},
1201+
)
1202+
1203+
cmdit(
1204+
[
1205+
'fix',
1206+
FLAG_DRY_RUN,
1207+
'--ecosystems',
1208+
'npm',
1209+
'--package-managers',
1210+
'PNPM',
1211+
FLAG_CONFIG,
1212+
'{"apiToken":"fakeToken"}',
1213+
],
1214+
'should accept --ecosystems combined with --package-managers',
1215+
async cmd => {
1216+
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
1217+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Not saving"`)
1218+
expect(code, 'should exit with code 0').toBe(0)
1219+
},
1220+
)
1221+
})
1222+
11301223
describe('--all flag behavior', () => {
11311224
cmdit(
11321225
['fix', FLAG_DRY_RUN, '--all', FLAG_CONFIG, '{"apiToken":"fakeToken"}'],

src/commands/fix/cmd-fix.mts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import {
2525
getFlagApiRequirementsOutput,
2626
getFlagListOutput,
2727
} from '../../utils/output-formatting.mts'
28+
import {
29+
ALL_PACKAGE_MANAGERS,
30+
isValidPackageManager,
31+
} from '../../utils/package-manager.mts'
2832
import { RangeStyles } from '../../utils/semver.mts'
2933
import { getDefaultOrgSlug } from '../ci/fetch-default-org-slug.mts'
3034

@@ -167,6 +171,13 @@ Available styles:
167171
'Limit fix analysis to specific ecosystems. Can be provided as comma separated values or as multiple flags. Defaults to all ecosystems.',
168172
isMultiple: true,
169173
},
174+
packageManagers: {
175+
type: 'string',
176+
default: [],
177+
description:
178+
'Limit fix analysis to specific package managers within an ecosystem (e.g. NPM, PNPM, YARN, MAVEN, POETRY). Accepts space- or comma-separated values and is case-insensitive. When combined with --ecosystems, an artifact must satisfy both filters.',
179+
isMultiple: true,
180+
},
170181
showAffectedDirectDependencies: {
171182
type: 'boolean',
172183
default: false,
@@ -311,6 +322,7 @@ async function run(
311322
maxSatisfying,
312323
minimumReleaseAge,
313324
outputFile,
325+
packageManagers,
314326
prCheck,
315327
prLimit,
316328
rangeStyle,
@@ -336,6 +348,7 @@ async function run(
336348
minSatisfying: boolean
337349
minimumReleaseAge: string
338350
outputFile: string
351+
packageManagers: string[]
339352
prCheck: boolean
340353
prLimit: number
341354
rangeStyle: RangeStyle
@@ -370,6 +383,24 @@ async function run(
370383
validatedEcosystems.push(ecosystem as PURL_Type)
371384
}
372385

386+
// Process and validate package manager values early, before dry-run check.
387+
// Coana normalizes input to uppercase and rejects unknown values, so do the
388+
// same here for a consistent UX and an early failure when invalid.
389+
const packageManagersRaw = cmdFlagValueToArray(packageManagers).map(s =>
390+
s.toUpperCase(),
391+
)
392+
const validatedPackageManagers: string[] = []
393+
for (const pm of packageManagersRaw) {
394+
if (!isValidPackageManager(pm)) {
395+
logger.fail(
396+
`Invalid package manager: "${pm}". Valid values are: ${joinAnd([...ALL_PACKAGE_MANAGERS])}`,
397+
)
398+
process.exitCode = 1
399+
return
400+
}
401+
validatedPackageManagers.push(pm)
402+
}
403+
373404
// Collect ghsas early to validate --all and --id mutual exclusivity.
374405
const ghsas = arrayUnique([
375406
...cmdFlagValueToArray(cli.flags['id']),
@@ -468,6 +499,7 @@ async function run(
468499
orgSlug,
469500
outputFile,
470501
outputKind,
502+
packageManagers: validatedPackageManagers,
471503
prCheck,
472504
prLimit,
473505
rangeStyle,

src/commands/fix/coana-fix.mts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type DiscoverGhsaIdsOptions = {
5858
coanaVersion?: string | undefined
5959
cwd?: string | undefined
6060
ecosystems?: PURL_Type[] | undefined
61+
packageManagers?: string[] | undefined
6162
silence?: boolean | undefined
6263
spinner?: Spinner | undefined
6364
}
@@ -74,6 +75,7 @@ async function discoverGhsaIds(
7475
const {
7576
cwd = process.cwd(),
7677
ecosystems,
78+
packageManagers,
7779
silence = false,
7880
spinner,
7981
} = {
@@ -88,6 +90,9 @@ async function discoverGhsaIds(
8890
'--manifests-tar-hash',
8991
tarHash,
9092
...(ecosystems?.length ? ['--purl-types', ...ecosystems] : []),
93+
...(packageManagers?.length
94+
? ['--package-managers', ...packageManagers]
95+
: []),
9196
],
9297
orgSlug,
9398
{
@@ -129,6 +134,7 @@ export async function coanaFix(
129134
minimumReleaseAge,
130135
orgSlug,
131136
outputFile,
137+
packageManagers,
132138
prLimit,
133139
showAffectedDirectDependencies,
134140
silence,
@@ -250,6 +256,7 @@ export async function coanaFix(
250256
coanaVersion,
251257
cwd,
252258
ecosystems,
259+
packageManagers,
253260
silence,
254261
spinner,
255262
})
@@ -284,6 +291,9 @@ export async function coanaFix(
284291
...(include.length ? ['--include', ...include] : []),
285292
...(exclude.length ? ['--exclude', ...exclude] : []),
286293
...(ecosystems.length ? ['--purl-types', ...ecosystems] : []),
294+
...(packageManagers.length
295+
? ['--package-managers', ...packageManagers]
296+
: []),
287297
...(!applyFixes ? [FLAG_DRY_RUN] : []),
288298
'--output-file',
289299
tmpFile,
@@ -381,6 +391,7 @@ export async function coanaFix(
381391
coanaVersion,
382392
cwd,
383393
ecosystems,
394+
packageManagers,
384395
silence,
385396
spinner,
386397
})
@@ -442,6 +453,9 @@ export async function coanaFix(
442453
...(include.length ? ['--include', ...include] : []),
443454
...(exclude.length ? ['--exclude', ...exclude] : []),
444455
...(ecosystems.length ? ['--purl-types', ...ecosystems] : []),
456+
...(packageManagers.length
457+
? ['--package-managers', ...packageManagers]
458+
: []),
445459
...(debug ? ['--debug'] : []),
446460
...(disableExternalToolChecks
447461
? ['--disable-external-tool-checks']

src/commands/fix/handle-fix.mts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export async function handleFix({
131131
orgSlug,
132132
outputFile,
133133
outputKind,
134+
packageManagers,
134135
prCheck,
135136
prLimit,
136137
rangeStyle,
@@ -157,6 +158,7 @@ export async function handleFix({
157158
minimumReleaseAge,
158159
outputFile,
159160
outputKind,
161+
packageManagers,
160162
prCheck,
161163
prLimit,
162164
rangeStyle,
@@ -184,6 +186,7 @@ export async function handleFix({
184186
minSatisfying,
185187
orgSlug,
186188
outputFile,
189+
packageManagers,
187190
prCheck,
188191
prLimit,
189192
rangeStyle,

src/commands/fix/types.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type FixConfig = {
1919
minSatisfying: boolean
2020
orgSlug: string
2121
outputFile: string
22+
packageManagers: string[]
2223
prCheck: boolean
2324
prLimit: number
2425
rangeStyle: RangeStyle

src/utils/package-manager.mts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Package manager identifiers accepted by Coana's --package-managers filter.
3+
* Used by `socket fix` to narrow fix computation to specific package managers
4+
* within an ecosystem (e.g. only PNPM artifacts in a mixed pnpm/yarn/npm repo).
5+
*
6+
* Mirrors the list returned by Coana's `getFilterablePackageManagers()` in
7+
* packages/web-compat-utils/src/package-manager-utils.ts.
8+
*/
9+
10+
export const ALL_PACKAGE_MANAGERS = [
11+
'CARGO',
12+
'COMPOSER',
13+
'GO',
14+
'GRADLE',
15+
'MAVEN',
16+
'NPM',
17+
'NUGET',
18+
'PIPENV',
19+
'PIP_REQUIREMENTS',
20+
'PNPM',
21+
'POETRY',
22+
'RUBYGEMS',
23+
'RUSH',
24+
'SBT',
25+
'YARN',
26+
] as const
27+
28+
export type PackageManager = (typeof ALL_PACKAGE_MANAGERS)[number]
29+
30+
const ALL_PACKAGE_MANAGERS_SET = new Set<string>(ALL_PACKAGE_MANAGERS)
31+
32+
export function getPackageManagerChoicesForMeow(): string[] {
33+
return [...ALL_PACKAGE_MANAGERS]
34+
}
35+
36+
export function isValidPackageManager(value: string): value is PackageManager {
37+
return ALL_PACKAGE_MANAGERS_SET.has(value)
38+
}

0 commit comments

Comments
 (0)