@@ -28,6 +28,7 @@ import type { PacoteOptions } from 'pacote'
28
28
import type { CliSubcommand } from '../utils/meow-with-subcommands'
29
29
import type {
30
30
Agent ,
31
+ AgentPlusBun ,
31
32
StringKeyValueObject
32
33
} from '../utils/package-manager-detector'
33
34
@@ -49,48 +50,35 @@ type GetOverridesResult = {
49
50
overrides : Overrides
50
51
}
51
52
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
+ } ,
53
58
// npm overrides documentation:
54
59
// https://docs.npmjs.com/cli/v10/configuring-npm/package-json#overrides
55
- npm : ( pkgJson : PackageJsonContent ) => {
60
+ npm ( pkgJson : PackageJsonContent ) {
56
61
const overrides = ( pkgJson as any ) ?. overrides ?? { }
57
62
return { type : 'npm' , overrides }
58
63
} ,
59
64
// pnpm overrides documentation:
60
65
// https://pnpm.io/package_json#pnpmoverrides
61
- pnpm : ( pkgJson : PackageJsonContent ) => {
66
+ pnpm ( pkgJson : PackageJsonContent ) {
62
67
const overrides = ( pkgJson as any ) ?. pnpm ?. overrides ?? { }
63
68
return { type : 'pnpm' , overrides }
64
69
} ,
65
70
// Yarn resolutions documentation:
66
71
// https://yarnpkg.com/configuration/manifest#resolutions
67
- yarn : ( pkgJson : PackageJsonContent ) => {
72
+ yarn ( pkgJson : PackageJsonContent ) {
68
73
const overrides = ( pkgJson as any ) ?. resolutions ?? { }
69
74
return { type : 'yarn' , overrides }
70
75
}
71
76
}
72
77
73
- type LockIncludes = ( lockSrc : string , name : string ) => boolean
78
+ type AgentLockIncludesFn = ( lockSrc : string , name : string ) => boolean
74
79
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 ) => {
94
82
const escapedName = escapeRegExp ( name )
95
83
return new RegExp (
96
84
// Detects the package name in the following cases:
@@ -102,14 +90,40 @@ const lockIncludesByAgent: Record<Agent, LockIncludes> = {
102
90
'm'
103
91
) . test ( lockSrc )
104
92
}
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
+ } ) ( )
106
115
107
- type ModifyManifest = (
116
+ type AgentModifyManifestFn = (
108
117
pkgJson : EditablePackageJson ,
109
118
overrides : Overrides
110
119
) => void
111
120
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
+ } ,
113
127
npm ( pkgJson : EditablePackageJson , overrides : Overrides ) {
114
128
pkgJson . update ( {
115
129
[ OVERRIDES_FIELD_NAME ] : overrides
@@ -130,6 +144,65 @@ const updateManifestByAgent: Record<Agent, ModifyManifest> = {
130
144
}
131
145
}
132
146
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
+
133
206
function getDependencyEntries ( pkgJson : PackageJsonContent ) {
134
207
const {
135
208
dependencies,
@@ -207,7 +280,6 @@ function workspaceToGlobPattern(workspace: string): string {
207
280
208
281
type AddOverridesConfig = {
209
282
agent : Agent
210
- lockIncludes : LockIncludes
211
283
lockSrc : string
212
284
manifestEntries : ManifestEntry [ ]
213
285
pkgJson ?: EditablePackageJson | undefined
@@ -224,7 +296,6 @@ type AddOverridesState = {
224
296
async function addOverrides (
225
297
{
226
298
agent,
227
- lockIncludes,
228
299
lockSrc,
229
300
manifestEntries,
230
301
pkgJson : editablePkgJson ,
@@ -242,6 +313,12 @@ async function addOverrides(
242
313
}
243
314
const pkgJson : Readonly < PackageJsonContent > = editablePkgJson . content
244
315
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 ]
245
322
const depEntries = getDependencyEntries ( pkgJson )
246
323
const workspaces = await getWorkspaces ( agent , pkgPath , pkgJson )
247
324
const isWorkspace = ! ! workspaces
@@ -268,14 +345,14 @@ async function addOverrides(
268
345
let thisVersion = version
269
346
// Add package aliases for direct dependencies to avoid npm EOVERRIDE errors.
270
347
// 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 )
273
350
? ( semver . coerce ( npa ( pkgSpec ) . rawSpec ) ?. version ?? '' )
274
351
: ''
275
352
if ( existingVersion ) {
276
353
thisVersion = existingVersion
277
354
} else {
278
- pkgSpec = `${ specStartsWith } ^${ version } `
355
+ pkgSpec = `${ regSpecStartsLike } ^${ version } `
279
356
depObj [ origPkgName ] = pkgSpec
280
357
state . added . add ( regPkgName )
281
358
}
@@ -285,16 +362,14 @@ async function addOverrides(
285
362
} )
286
363
}
287
364
}
288
- if ( ! isRoot ) {
289
- return
290
- }
291
365
// Chunk package names to process them in parallel 3 at a time.
292
366
await pEach ( overridesDataObjects , 3 , async ( { overrides, type } ) => {
293
367
const overrideExists = hasOwn ( overrides , origPkgName )
294
- if ( overrideExists || lockIncludes ( lockSrc , origPkgName ) ) {
368
+ if ( overrideExists || thingScanner ( thingToScan , origPkgName ) ) {
295
369
const oldSpec = overrideExists ? overrides [ origPkgName ] : undefined
296
370
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 } `
298
373
let thisVersion = version
299
374
if ( depAlias && type === 'npm' ) {
300
375
// With npm one may not set an override for a package that one directly
@@ -305,16 +380,23 @@ async function addOverrides(
305
380
// of with a $.
306
381
// https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides
307
382
newSpec = `$${ origPkgName } `
308
- } else if ( overrideExists && pin ) {
383
+ } else if ( overrideExists ) {
309
384
const thisSpec = oldSpec . startsWith ( '$' )
310
385
? ( depAlias ?. id ?? newSpec )
311
386
: ( 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
316
399
}
317
- newSpec = `npm:${ regPkgName } @^${ pin ? thisVersion : semver . major ( thisVersion ) } `
318
400
}
319
401
if ( newSpec !== oldSpec ) {
320
402
if ( overrideExists ) {
@@ -340,7 +422,6 @@ async function addOverrides(
340
422
const { added, updated } = await addOverrides ( {
341
423
agent,
342
424
lockSrc,
343
- lockIncludes,
344
425
manifestEntries,
345
426
pin,
346
427
pkgPath : path . dirname ( wsPkgJsonPath ) ,
@@ -448,16 +529,13 @@ export const optimize: CliSubcommand = {
448
529
updated : new Set ( )
449
530
}
450
531
if ( lockSrc ) {
451
- const lockIncludes =
452
- agent === 'bun' ? lockIncludesByAgent . yarn : lockIncludesByAgent [ agent ]
453
532
const nodeRange = `>=${ minimumNodeVersion } `
454
533
const manifestEntries = manifestNpmOverrides . filter ( ( { 1 : data } ) =>
455
534
semver . satisfies ( semver . coerce ( data . engines . node ) ! , nodeRange )
456
535
)
457
536
await addOverrides (
458
537
{
459
538
agent : agent === 'bun' ? 'yarn' : agent ,
460
- lockIncludes,
461
539
lockSrc,
462
540
manifestEntries,
463
541
pin,
0 commit comments