@@ -28,6 +28,7 @@ import type { PacoteOptions } from 'pacote'
2828import type { CliSubcommand } from '../utils/meow-with-subcommands'
2929import type {
3030 Agent ,
31+ AgentPlusBun ,
3132 StringKeyValueObject
3233} from '../utils/package-manager-detector'
3334
@@ -49,48 +50,35 @@ type GetOverridesResult = {
4950 overrides : Overrides
5051}
5152
52- const getOverridesDataByAgent : Record < Agent , GetOverrides > = {
53+ const getOverridesDataByAgent : Record < AgentPlusBun , GetOverrides > = {
54+ bun ( pkgJson : PackageJsonContent ) {
55+ const overrides = ( pkgJson as any ) ?. resolutions ?? { }
56+ return { type : 'yarn' , overrides }
57+ } ,
5358 // npm overrides documentation:
5459 // https://docs.npmjs.com/cli/v10/configuring-npm/package-json#overrides
55- npm : ( pkgJson : PackageJsonContent ) => {
60+ npm ( pkgJson : PackageJsonContent ) {
5661 const overrides = ( pkgJson as any ) ?. overrides ?? { }
5762 return { type : 'npm' , overrides }
5863 } ,
5964 // pnpm overrides documentation:
6065 // https://pnpm.io/package_json#pnpmoverrides
61- pnpm : ( pkgJson : PackageJsonContent ) => {
66+ pnpm ( pkgJson : PackageJsonContent ) {
6267 const overrides = ( pkgJson as any ) ?. pnpm ?. overrides ?? { }
6368 return { type : 'pnpm' , overrides }
6469 } ,
6570 // Yarn resolutions documentation:
6671 // https://yarnpkg.com/configuration/manifest#resolutions
67- yarn : ( pkgJson : PackageJsonContent ) => {
72+ yarn ( pkgJson : PackageJsonContent ) {
6873 const overrides = ( pkgJson as any ) ?. resolutions ?? { }
6974 return { type : 'yarn' , overrides }
7075 }
7176}
7277
73- type LockIncludes = ( lockSrc : string , name : string ) => boolean
78+ type AgentLockIncludesFn = ( lockSrc : string , name : string ) => boolean
7479
75- const lockIncludesByAgent : Record < Agent , LockIncludes > = {
76- npm : ( lockSrc : string , name : string ) => {
77- // Detects the package name in the following cases:
78- // "name":
79- return lockSrc . includes ( `"${ name } ":` )
80- } ,
81- pnpm : ( lockSrc : string , name : string ) => {
82- const escapedName = escapeRegExp ( name )
83- return new RegExp (
84- // Detects the package name in the following cases:
85- // /name/
86- // 'name'
87- // name:
88- // name@
89- `(?<=^\\s*)(?:(['/])${ escapedName } \\1|${ escapedName } (?=[:@]))` ,
90- 'm'
91- ) . test ( lockSrc )
92- } ,
93- yarn : ( lockSrc : string , name : string ) => {
80+ const lockIncludesByAgent : Record < AgentPlusBun , AgentLockIncludesFn > = ( ( ) => {
81+ const yarn = ( lockSrc : string , name : string ) => {
9482 const escapedName = escapeRegExp ( name )
9583 return new RegExp (
9684 // Detects the package name in the following cases:
@@ -102,14 +90,40 @@ const lockIncludesByAgent: Record<Agent, LockIncludes> = {
10290 'm'
10391 ) . test ( lockSrc )
10492 }
105- }
93+ return {
94+ bun : yarn ,
95+ npm ( lockSrc : string , name : string ) {
96+ // Detects the package name in the following cases:
97+ // "name":
98+ return lockSrc . includes ( `"${ name } ":` )
99+ } ,
100+ pnpm ( lockSrc : string , name : string ) {
101+ const escapedName = escapeRegExp ( name )
102+ return new RegExp (
103+ // Detects the package name in the following cases:
104+ // /name/
105+ // 'name'
106+ // name:
107+ // name@
108+ `(?<=^\\s*)(?:(['/])${ escapedName } \\1|${ escapedName } (?=[:@]))` ,
109+ 'm'
110+ ) . test ( lockSrc )
111+ } ,
112+ yarn
113+ }
114+ } ) ( )
106115
107- type ModifyManifest = (
116+ type AgentModifyManifestFn = (
108117 pkgJson : EditablePackageJson ,
109118 overrides : Overrides
110119) => void
111120
112- const updateManifestByAgent : Record < Agent , ModifyManifest > = {
121+ const updateManifestByAgent : Record < AgentPlusBun , AgentModifyManifestFn > = {
122+ bun ( pkgJson : EditablePackageJson , overrides : Overrides ) {
123+ pkgJson . update ( {
124+ [ RESOLUTIONS_FIELD_NAME ] : < PnpmOrYarnOverrides > overrides
125+ } )
126+ } ,
113127 npm ( pkgJson : EditablePackageJson , overrides : Overrides ) {
114128 pkgJson . update ( {
115129 [ OVERRIDES_FIELD_NAME ] : overrides
@@ -130,6 +144,65 @@ const updateManifestByAgent: Record<Agent, ModifyManifest> = {
130144 }
131145}
132146
147+ type AgentListDepsFn = ( cwd : string , rootPath : string ) => Promise < string >
148+
149+ const lsByAgent : Record < AgentPlusBun , AgentListDepsFn > = {
150+ async bun ( cwd : string , _rootPath : string ) {
151+ try {
152+ return ( await spawn ( 'bun' , [ 'pm' , 'ls' , '--all' ] , { cwd } ) ) . stdout
153+ } catch { }
154+ return ''
155+ } ,
156+ async npm ( cwd : string , rootPath : string ) {
157+ try {
158+ ; (
159+ await spawn (
160+ 'npm' ,
161+ [ 'ls' , '--parseable' , '--include' , 'prod' , '--all' ] ,
162+ { cwd }
163+ )
164+ ) . stdout
165+ . replaceAll ( cwd , '' )
166+ . replaceAll ( rootPath , '' )
167+ } catch { }
168+ return ''
169+ } ,
170+ async pnpm ( cwd : string , rootPath : string ) {
171+ try {
172+ return (
173+ await spawn (
174+ 'pnpm' ,
175+ [ 'ls' , '--parseable' , '--prod' , '--depth' , 'Infinity' ] ,
176+ { cwd }
177+ )
178+ ) . stdout
179+ . replaceAll ( cwd , '' )
180+ . replaceAll ( rootPath , '' )
181+ } catch { }
182+ return ''
183+ } ,
184+ async yarn ( cwd : string , _rootPath : string ) {
185+ try {
186+ return (
187+ await spawn ( 'yarn' , [ 'info' , '--recursive' , '--name-only' ] , { cwd } )
188+ ) . stdout
189+ } catch { }
190+ try {
191+ return ( await spawn ( 'yarn' , [ 'list' , '--prod' ] , { cwd } ) ) . stdout
192+ } catch { }
193+ return ''
194+ }
195+ }
196+
197+ type AgentDepsIncludesFn = ( stdout : string , name : string ) => boolean
198+
199+ const depsIncludesByAgent : Record < AgentPlusBun , AgentDepsIncludesFn > = {
200+ bun : ( stdout : string , name : string ) => stdout . includes ( name ) ,
201+ npm : ( stdout : string , name : string ) => stdout . includes ( name ) ,
202+ pnpm : ( stdout : string , name : string ) => stdout . includes ( name ) ,
203+ yarn : ( stdout : string , name : string ) => stdout . includes ( name )
204+ }
205+
133206function getDependencyEntries ( pkgJson : PackageJsonContent ) {
134207 const {
135208 dependencies,
@@ -207,7 +280,6 @@ function workspaceToGlobPattern(workspace: string): string {
207280
208281type AddOverridesConfig = {
209282 agent : Agent
210- lockIncludes : LockIncludes
211283 lockSrc : string
212284 manifestEntries : ManifestEntry [ ]
213285 pkgJson ?: EditablePackageJson | undefined
@@ -224,7 +296,6 @@ type AddOverridesState = {
224296async function addOverrides (
225297 {
226298 agent,
227- lockIncludes,
228299 lockSrc,
229300 manifestEntries,
230301 pkgJson : editablePkgJson ,
@@ -242,6 +313,12 @@ async function addOverrides(
242313 }
243314 const pkgJson : Readonly < PackageJsonContent > = editablePkgJson . content
244315 const isRoot = pkgPath === rootPath
316+ const thingToScan = isRoot
317+ ? lockSrc
318+ : await lsByAgent [ agent ] ( pkgPath , rootPath )
319+ const thingScanner = isRoot
320+ ? lockIncludesByAgent [ agent ]
321+ : depsIncludesByAgent [ agent ]
245322 const depEntries = getDependencyEntries ( pkgJson )
246323 const workspaces = await getWorkspaces ( agent , pkgPath , pkgJson )
247324 const isWorkspace = ! ! workspaces
@@ -268,14 +345,14 @@ async function addOverrides(
268345 let thisVersion = version
269346 // Add package aliases for direct dependencies to avoid npm EOVERRIDE errors.
270347 // https://docs.npmjs.com/cli/v8/using-npm/package-spec#aliases
271- const specStartsWith = `npm:${ regPkgName } @`
272- const existingVersion = pkgSpec . startsWith ( specStartsWith )
348+ const regSpecStartsLike = `npm:${ regPkgName } @`
349+ const existingVersion = pkgSpec . startsWith ( regSpecStartsLike )
273350 ? ( semver . coerce ( npa ( pkgSpec ) . rawSpec ) ?. version ?? '' )
274351 : ''
275352 if ( existingVersion ) {
276353 thisVersion = existingVersion
277354 } else {
278- pkgSpec = `${ specStartsWith } ^${ version } `
355+ pkgSpec = `${ regSpecStartsLike } ^${ version } `
279356 depObj [ origPkgName ] = pkgSpec
280357 state . added . add ( regPkgName )
281358 }
@@ -285,16 +362,14 @@ async function addOverrides(
285362 } )
286363 }
287364 }
288- if ( ! isRoot ) {
289- return
290- }
291365 // Chunk package names to process them in parallel 3 at a time.
292366 await pEach ( overridesDataObjects , 3 , async ( { overrides, type } ) => {
293367 const overrideExists = hasOwn ( overrides , origPkgName )
294- if ( overrideExists || lockIncludes ( lockSrc , origPkgName ) ) {
368+ if ( overrideExists || thingScanner ( thingToScan , origPkgName ) ) {
295369 const oldSpec = overrideExists ? overrides [ origPkgName ] : undefined
296370 const depAlias = depAliasMap . get ( origPkgName )
297- let newSpec = `npm:${ regPkgName } @^${ pin ? version : major } `
371+ const regSpecStartsLike = `npm:${ regPkgName } @`
372+ let newSpec = `${ regSpecStartsLike } ^${ pin ? version : major } `
298373 let thisVersion = version
299374 if ( depAlias && type === 'npm' ) {
300375 // With npm one may not set an override for a package that one directly
@@ -305,16 +380,23 @@ async function addOverrides(
305380 // of with a $.
306381 // https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides
307382 newSpec = `$${ origPkgName } `
308- } else if ( overrideExists && pin ) {
383+ } else if ( overrideExists ) {
309384 const thisSpec = oldSpec . startsWith ( '$' )
310385 ? ( depAlias ?. id ?? newSpec )
311386 : ( oldSpec ?? newSpec )
312- thisVersion = semver . coerce ( npa ( thisSpec ) . rawSpec ) ?. version ?? version
313- if ( semver . major ( thisVersion ) !== major ) {
314- thisVersion =
315- ( await fetchPackageManifest ( thisSpec ) ) ?. version ?? version
387+ if ( thisSpec . startsWith ( regSpecStartsLike ) ) {
388+ if ( pin ) {
389+ thisVersion =
390+ semver . major (
391+ semver . coerce ( npa ( thisSpec ) . rawSpec ) ?. version ?? version
392+ ) === major
393+ ? version
394+ : ( ( await fetchPackageManifest ( thisSpec ) ) ?. version ?? version )
395+ }
396+ newSpec = `${ regSpecStartsLike } ^${ pin ? thisVersion : semver . major ( thisVersion ) } `
397+ } else {
398+ newSpec = oldSpec
316399 }
317- newSpec = `npm:${ regPkgName } @^${ pin ? thisVersion : semver . major ( thisVersion ) } `
318400 }
319401 if ( newSpec !== oldSpec ) {
320402 if ( overrideExists ) {
@@ -340,7 +422,6 @@ async function addOverrides(
340422 const { added, updated } = await addOverrides ( {
341423 agent,
342424 lockSrc,
343- lockIncludes,
344425 manifestEntries,
345426 pin,
346427 pkgPath : path . dirname ( wsPkgJsonPath ) ,
@@ -448,16 +529,13 @@ export const optimize: CliSubcommand = {
448529 updated : new Set ( )
449530 }
450531 if ( lockSrc ) {
451- const lockIncludes =
452- agent === 'bun' ? lockIncludesByAgent . yarn : lockIncludesByAgent [ agent ]
453532 const nodeRange = `>=${ minimumNodeVersion } `
454533 const manifestEntries = manifestNpmOverrides . filter ( ( { 1 : data } ) =>
455534 semver . satisfies ( semver . coerce ( data . engines . node ) ! , nodeRange )
456535 )
457536 await addOverrides (
458537 {
459538 agent : agent === 'bun' ? 'yarn' : agent ,
460- lockIncludes,
461539 lockSrc,
462540 manifestEntries,
463541 pin,
0 commit comments