Skip to content

Commit 16eec24

Browse files
committed
Use npm query to resolve non-dev deps
1 parent f21220a commit 16eec24

File tree

1 file changed

+55
-62
lines changed

1 file changed

+55
-62
lines changed

src/commands/optimize.ts

Lines changed: 55 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ const updateManifestByAgent: Record<Agent, AgentModifyManifestFn> = (() => {
169169

170170
type AgentListDepsOptions = {
171171
npmExecPath?: string
172-
rootPath?: string
173172
}
174173
type AgentListDepsFn = (
175174
agentExecPath: string,
@@ -178,32 +177,39 @@ type AgentListDepsFn = (
178177
) => Promise<string>
179178

180179
const lsByAgent = (() => {
181-
function cleanupParseable(
182-
stdout: string,
183-
cwd: string,
184-
rootPath?: string
185-
): string {
186-
stdout = stdout.trim()
187-
stdout = stdout.replaceAll(cwd, '')
188-
if (rootPath && rootPath !== cwd) {
189-
stdout = stdout.replaceAll(rootPath, '')
180+
function cleanupQueryStdout(stdout: string): string {
181+
if (stdout === '') {
182+
return ''
183+
}
184+
let pkgs
185+
try {
186+
pkgs = JSON.stringify(stdout)
187+
} catch {}
188+
if (!Array.isArray(pkgs)) {
189+
return ''
190+
}
191+
const names = new Set<string>()
192+
for (const { name, pkgid = '' } of pkgs) {
193+
// `npm query` results may not have a "name" property, in which case we
194+
// fallback to "pkgid".
195+
// `vlt ls --view json` results always have a "name" property.
196+
const resolvedName = name ?? pkgid.slice(0, pkgid.indexOf('@', 1))
197+
if (resolvedName) {
198+
names.add(resolvedName)
199+
}
190200
}
191-
return stdout.replaceAll('\\', '/')
201+
return JSON.stringify([...names], null, 2)
192202
}
193203

194-
async function npmLs(npmExecPath: string, cwd: string, rootPath?: string) {
195-
return cleanupParseable(
196-
(
197-
await spawn(
198-
npmExecPath,
199-
['ls', '--parseable', '--omit', 'dev', '--all'],
200-
{ cwd }
201-
)
202-
).stdout,
203-
cwd,
204-
rootPath
205-
)
204+
async function npmQuery(npmExecPath: string, cwd: string): Promise<string> {
205+
let stdout = ''
206+
try {
207+
stdout = (await spawn(npmExecPath, ['query', ':not(.dev)'], { cwd }))
208+
.stdout
209+
} catch {}
210+
return cleanupQueryStdout(stdout)
206211
}
212+
207213
return <Record<Agent, AgentListDepsFn>>{
208214
async bun(agentExecPath: string, cwd: string) {
209215
try {
@@ -214,61 +220,48 @@ const lsByAgent = (() => {
214220
} catch {}
215221
return ''
216222
},
217-
async npm(
218-
agentExecPath: string,
219-
cwd: string,
220-
options: AgentListDepsOptions
221-
) {
222-
const { rootPath } = <AgentListDepsOptions>{ __proto__: null, ...options }
223-
try {
224-
return await npmLs(agentExecPath, cwd, rootPath)
225-
} catch {}
226-
return ''
223+
async npm(agentExecPath: string, cwd: string) {
224+
return await npmQuery(agentExecPath, cwd)
227225
},
228226
async pnpm(
229227
agentExecPath: string,
230228
cwd: string,
231229
options: AgentListDepsOptions
232230
) {
233-
const { npmExecPath, rootPath } = <AgentListDepsOptions>{
231+
const { npmExecPath } = <AgentListDepsOptions>{
234232
__proto__: null,
235233
...options
236234
}
237-
let stdout = ''
238235
if (npmExecPath && npmExecPath !== 'npm') {
239-
try {
240-
stdout = await npmLs(npmExecPath, cwd, rootPath)
241-
} catch (e: any) {
242-
if (e?.stderr?.includes('code ELSPROBLEMS')) {
243-
stdout = e?.stdout
244-
}
236+
const result = await npmQuery(npmExecPath, cwd)
237+
if (result) {
238+
return result
245239
}
246-
} else {
247-
try {
248-
stdout = cleanupParseable(
249-
(
250-
await spawn(
251-
agentExecPath,
252-
['ls', '--parseable', '--prod', '--depth', 'Infinity'],
253-
{ cwd }
254-
)
255-
).stdout,
256-
cwd,
257-
rootPath
258-
)
259-
} catch {}
260240
}
261-
return stdout
241+
try {
242+
const { stdout } = await spawn(
243+
agentExecPath,
244+
['ls', '--parseable', '--prod', '--depth', 'Infinity'],
245+
{ cwd }
246+
)
247+
// Convert the parseable stdout into a json array of unique names.
248+
// The matchAll regexp looks for forward or backward slash followed by
249+
// one or more non-slashes until the newline.
250+
const names = new Set(stdout.matchAll(/(?<=[/\\])[^/\\]+(?=\n)/g))
251+
return JSON.stringify([...names], null, 2)
252+
} catch {}
253+
return ''
262254
},
263255
async vlt(agentExecPath: string, cwd: string) {
256+
let stdout = ''
264257
try {
265-
return (
266-
await spawn(agentExecPath!, ['ls', '--view', 'human', ':not(.dev)'], {
258+
stdout = (
259+
await spawn(agentExecPath, ['ls', '--view', 'human', ':not(.dev)'], {
267260
cwd
268261
})
269262
).stdout
270263
} catch {}
271-
return ''
264+
return cleanupQueryStdout(stdout)
272265
},
273266
async 'yarn/berry'(agentExecPath: string, cwd: string) {
274267
try {
@@ -303,8 +296,8 @@ type AgentDepsIncludesFn = (stdout: string, name: string) => boolean
303296

304297
const depsIncludesByAgent: Record<Agent, AgentDepsIncludesFn> = {
305298
bun: (stdout: string, name: string) => stdout.includes(` ${name}@`),
306-
npm: (stdout: string, name: string) => stdout.includes(`/${name}\n`),
307-
pnpm: (stdout: string, name: string) => stdout.includes(`/${name}\n`),
299+
npm: (stdout: string, name: string) => stdout.includes(`"${name}"`),
300+
pnpm: (stdout: string, name: string) => stdout.includes(`"${name}"`),
308301
vlt: (stdout: string, name: string) => stdout.includes(` ${name}@`),
309302
'yarn/berry': (stdout: string, name: string) => stdout.includes(` ${name}@`),
310303
'yarn/classic': (stdout: string, name: string) => stdout.includes(` ${name}@`)
@@ -453,7 +446,7 @@ async function addOverrides(
453446
}
454447
const thingToScan = isLockScanned
455448
? lockSrc
456-
: await lsByAgent[agent](agentExecPath, pkgPath, { npmExecPath, rootPath })
449+
: await lsByAgent[agent](agentExecPath, pkgPath, { npmExecPath })
457450
const thingScanner = isLockScanned
458451
? lockIncludesByAgent[agent]
459452
: depsIncludesByAgent[agent]

0 commit comments

Comments
 (0)