@@ -30,6 +30,7 @@ export type SuggestedFixTarget = {
3030 coveredPaths ?: string [ ] [ ] ;
3131 remainingPaths ?: string [ ] [ ] ;
3232 usage ?: { imported : boolean ; files : string [ ] } | null ;
33+ isDev ?: boolean ;
3334} ;
3435
3536export type SuggestedFixSkip = {
@@ -83,6 +84,11 @@ export function buildSuggestedFixCommandPlan(
8384 }
8485 }
8586
87+ const devLookup = new Map < string , boolean > ( ) ;
88+ for ( const pkg of scanInput . packages ) {
89+ devLookup . set ( `${ pkg . name } @${ pkg . version } ` , pkg . dev ?? false ) ;
90+ }
91+
8692 const prioritizedFindings = [ ...findings ]
8793 . filter ( f => f . severity === "critical" || f . severity === "high" )
8894 . sort ( ( a , b ) => {
@@ -176,6 +182,7 @@ export function buildSuggestedFixCommandPlan(
176182 reason : `Direct upgrade target for ${ finding . pkg . name } @${ finding . pkg . version } ` ,
177183 usage : finding . usage ?? null ,
178184 workspaces : pkgWorkspaces . length > 0 ? pkgWorkspaces : undefined ,
185+ isDev : finding . pkg . dev ?? false ,
179186 } ) ;
180187 } else if ( finding . fixVersionValidationNote ) {
181188 skippedByKey . set ( `${ finding . relationship } :${ finding . pkg . name } @${ finding . pkg . version } ` , {
@@ -234,6 +241,7 @@ export function buildSuggestedFixCommandPlan(
234241 reason : finding . recommendedNpmTransitiveRemediation . reason ,
235242 command : buildNpmUpdateCommand ( finding . recommendedNpmTransitiveRemediation . package , finding . recommendedNpmTransitiveRemediation . workspaces ) ,
236243 usage : finding . usage ?? null ,
244+ isDev : devLookup . get ( `${ finding . recommendedNpmTransitiveRemediation . package } @${ finding . recommendedNpmTransitiveRemediation . currentVersion } ` ) ?? false ,
237245 } ) ;
238246 continue ;
239247 }
@@ -268,6 +276,7 @@ export function buildSuggestedFixCommandPlan(
268276 coveredPaths : coverage . coveredPaths ,
269277 remainingPaths : coverage . remainingPaths ,
270278 usage : finding . usage ?? null ,
279+ isDev : devLookup . get ( `${ finding . recommendedNpmTransitiveRemediation . package } @${ finding . recommendedNpmTransitiveRemediation . currentVersion } ` ) ?? false ,
271280 } ) ;
272281 continue ;
273282 }
@@ -300,6 +309,7 @@ export function buildSuggestedFixCommandPlan(
300309 coveredPaths : coverage . coveredPaths ,
301310 remainingPaths : coverage . remainingPaths ,
302311 usage : finding . usage ?? null ,
312+ isDev : devLookup . get ( `${ finding . recommendedParentUpgrade . package } @${ finding . recommendedParentUpgrade . currentVersion } ` ) ?? false ,
303313 } ) ;
304314 continue ;
305315 }
@@ -448,6 +458,10 @@ function commandPrefix(packageManager: SuggestedFixPackageManager): string {
448458 return "yarn add" ;
449459}
450460
461+ function devFlag ( packageManager : SuggestedFixPackageManager ) : string {
462+ return packageManager === "bun" ? "--dev" : "-D" ;
463+ }
464+
451465export function findSuggestedCommandForFinding (
452466 plan : SuggestedFixCommandPlan ,
453467 finding : Finding ,
@@ -482,7 +496,8 @@ export function findSuggestedCommandForFinding(
482496 const commands = buildWorkspaceInstallCommands ( [ target ] , plan . packageManager ) ;
483497 if ( commands . length > 0 ) return commands . join ( " && " ) ;
484498 }
485- return `${ commandPrefix ( plan . packageManager ) } ${ target . package } @${ target . targetVersion } ` ;
499+ const flag = target . isDev ? ` ${ devFlag ( plan . packageManager ) } ` : "" ;
500+ return `${ commandPrefix ( plan . packageManager ) } ${ flag } ${ target . package } @${ target . targetVersion } ` ;
486501}
487502
488503function packageManagerSourceLabel ( scanInput : ScanInput ) : string {
@@ -520,6 +535,7 @@ function upsertTarget(
520535 : "parent-upgrade" ,
521536 displayTargetVersion : existing . displayTargetVersion ?? next . displayTargetVersion ,
522537 command : existing . command ?? next . command ,
538+ isDev : ( existing . isDev ?? false ) && ( next . isDev ?? false ) ,
523539 workspaces : mergeStringArrays ( existing . workspaces , next . workspaces ) ,
524540 coverage : existing . coverage === "partial" || next . coverage === "partial"
525541 ? "partial"
@@ -700,28 +716,35 @@ function buildWorkspaceInstallCommands(
700716 const commands : string [ ] = [ ] ;
701717
702718 for ( const [ wsKey , groupTargets ] of groups ) {
703- const pkgArgs = groupTargets . map ( t => `${ t . package } @${ t . targetVersion } ` ) . join ( " " ) ;
704719 const workspaces = wsKey ? wsKey . split ( "\0" ) : [ ] ;
705-
706- if ( packageManager === "npm" ) {
707- const wsFlags = workspaces . map ( ws => `-w ${ ws } ` ) . join ( " " ) ;
708- commands . push ( `npm install${ wsFlags ? " " + wsFlags : "" } ${ pkgArgs } ` ) ;
709- } else if ( packageManager === "pnpm" ) {
710- const wsFlags = workspaces . map ( ws => `--filter ./${ ws } ` ) . join ( " " ) ;
711- commands . push ( `pnpm add${ wsFlags ? " " + wsFlags : "" } ${ pkgArgs } ` ) ;
712- } else if ( packageManager === "yarn" ) {
713- if ( workspaces . length === 0 ) {
714- commands . push ( `yarn add ${ pkgArgs } ` ) ;
715- } else {
716- for ( const ws of workspaces ) {
717- commands . push ( `yarn workspace ${ ws } add ${ pkgArgs } ` ) ;
720+ const devTargets = groupTargets . filter ( t => t . isDev ) ;
721+ const prodTargets = groupTargets . filter ( t => ! t . isDev ) ;
722+
723+ for ( const [ targets , isDev ] of [ [ prodTargets , false ] , [ devTargets , true ] ] as const ) {
724+ if ( targets . length === 0 ) continue ;
725+ const pkgArgs = targets . map ( t => `${ t . package } @${ t . targetVersion } ` ) . join ( " " ) ;
726+ const flag = isDev ? ` ${ devFlag ( packageManager ) } ` : "" ;
727+
728+ if ( packageManager === "npm" ) {
729+ const wsFlags = workspaces . map ( ws => `-w ${ ws } ` ) . join ( " " ) ;
730+ commands . push ( `npm install${ flag } ${ wsFlags ? " " + wsFlags : "" } ${ pkgArgs } ` ) ;
731+ } else if ( packageManager === "pnpm" ) {
732+ const wsFlags = workspaces . map ( ws => `--filter ./${ ws } ` ) . join ( " " ) ;
733+ commands . push ( `pnpm add${ flag } ${ wsFlags ? " " + wsFlags : "" } ${ pkgArgs } ` ) ;
734+ } else if ( packageManager === "yarn" ) {
735+ if ( workspaces . length === 0 ) {
736+ commands . push ( `yarn add${ flag } ${ pkgArgs } ` ) ;
737+ } else {
738+ for ( const ws of workspaces ) {
739+ commands . push ( `yarn workspace ${ ws } add${ flag } ${ pkgArgs } ` ) ;
740+ }
718741 }
742+ } else if ( packageManager === "bun" ) {
743+ const wsFlags = workspaces . map ( ws => `--filter ${ ws } ` ) . join ( " " ) ;
744+ commands . push ( `bun add${ flag } ${ wsFlags ? " " + wsFlags : "" } ${ pkgArgs } ` ) ;
745+ } else {
746+ commands . push ( `${ commandPrefix ( packageManager ) } ${ flag } ${ pkgArgs } ` ) ;
719747 }
720- } else if ( packageManager === "bun" ) {
721- const wsFlags = workspaces . map ( ws => `--filter ${ ws } ` ) . join ( " " ) ;
722- commands . push ( `bun add${ wsFlags ? " " + wsFlags : "" } ${ pkgArgs } ` ) ;
723- } else {
724- commands . push ( `${ commandPrefix ( packageManager ) } ${ pkgArgs } ` ) ;
725748 }
726749 }
727750
@@ -754,9 +777,14 @@ function buildCommandForTargets(
754777 if ( hasWorkspaces ) {
755778 commandParts . push ( ...buildWorkspaceInstallCommands ( installTargets , packageManager ) ) ;
756779 } else {
757- commandParts . push (
758- `${ commandPrefix ( packageManager ) } ${ installTargets . map ( target => `${ target . package } @${ target . targetVersion } ` ) . join ( " " ) } ` ,
759- ) ;
780+ const prodInstall = installTargets . filter ( t => ! t . isDev ) ;
781+ const devInstall = installTargets . filter ( t => t . isDev ) ;
782+ if ( prodInstall . length > 0 ) {
783+ commandParts . push ( `${ commandPrefix ( packageManager ) } ${ prodInstall . map ( t => `${ t . package } @${ t . targetVersion } ` ) . join ( " " ) } ` ) ;
784+ }
785+ if ( devInstall . length > 0 ) {
786+ commandParts . push ( `${ commandPrefix ( packageManager ) } ${ devFlag ( packageManager ) } ${ devInstall . map ( t => `${ t . package } @${ t . targetVersion } ` ) . join ( " " ) } ` ) ;
787+ }
760788 }
761789 }
762790
0 commit comments