Skip to content

Commit be9278d

Browse files
committed
Use agent ls to scan workspace packages
1 parent d3254a5 commit be9278d

File tree

1 file changed

+125
-47
lines changed

1 file changed

+125
-47
lines changed

src/commands/optimize.ts

Lines changed: 125 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type { PacoteOptions } from 'pacote'
2828
import type { CliSubcommand } from '../utils/meow-with-subcommands'
2929
import 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+
133206
function getDependencyEntries(pkgJson: PackageJsonContent) {
134207
const {
135208
dependencies,
@@ -207,7 +280,6 @@ function workspaceToGlobPattern(workspace: string): string {
207280

208281
type AddOverridesConfig = {
209282
agent: Agent
210-
lockIncludes: LockIncludes
211283
lockSrc: string
212284
manifestEntries: ManifestEntry[]
213285
pkgJson?: EditablePackageJson | undefined
@@ -224,7 +296,6 @@ type AddOverridesState = {
224296
async 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

Comments
 (0)