@@ -31,7 +31,7 @@ import browserslist from 'browserslist'
3131import semver from 'semver'
3232
3333import { parse as parseBunLockb } from '@socketregistry/hyrious__bun.lockb/index.cjs'
34- import { whichBin } from '@socketsecurity/registry/lib/bin'
34+ import { resolveBinPathSync , whichBin } from '@socketsecurity/registry/lib/bin'
3535import { debugDir , debugFn } from '@socketsecurity/registry/lib/debug'
3636import { readFileBinary , readFileUtf8 } from '@socketsecurity/registry/lib/fs'
3737import { Logger } from '@socketsecurity/registry/lib/logger'
@@ -239,20 +239,43 @@ const LOCKS: Record<string, Agent> = {
239239 [ `${ NODE_MODULES } /${ DOT_PACKAGE_LOCK_JSON } ` ] : NPM ,
240240}
241241
242+ function preferWindowsCmdShim ( binPath : string , binName : string ) : string {
243+ if ( ! constants . WIN32 ) {
244+ return binPath
245+ }
246+ if ( ! path . isAbsolute ( binPath ) ) {
247+ return binPath
248+ }
249+ if ( path . extname ( binPath ) !== '' ) {
250+ return binPath
251+ }
252+ if ( path . basename ( binPath ) . toLowerCase ( ) !== binName . toLowerCase ( ) ) {
253+ return binPath
254+ }
255+ const cmdShim = path . join ( path . dirname ( binPath ) , `${ binName } .cmd` )
256+ return existsSync ( cmdShim ) ? cmdShim : binPath
257+ }
258+
242259async function getAgentExecPath ( agent : Agent ) : Promise < string > {
243260 const binName = binByAgent . get ( agent ) !
244261 if ( binName === NPM ) {
245262 // Try to use constants.npmExecPath first, but verify it exists.
246- const npmPath = constants . npmExecPath
263+ const npmPath = preferWindowsCmdShim ( constants . npmExecPath , NPM )
247264 if ( existsSync ( npmPath ) ) {
248265 return npmPath
249266 }
250267 // If npmExecPath doesn't exist, try common locations.
251268 // Check npm in the same directory as node.
252269 const nodeDir = path . dirname ( process . execPath )
270+ if ( constants . WIN32 ) {
271+ const npmCmdInNodeDir = path . join ( nodeDir , `${ NPM } .cmd` )
272+ if ( existsSync ( npmCmdInNodeDir ) ) {
273+ return npmCmdInNodeDir
274+ }
275+ }
253276 const npmInNodeDir = path . join ( nodeDir , NPM )
254277 if ( existsSync ( npmInNodeDir ) ) {
255- return npmInNodeDir
278+ return preferWindowsCmdShim ( npmInNodeDir , NPM )
256279 }
257280 // Fall back to whichBin.
258281 return ( await whichBin ( binName , { nothrow : true } ) ) ?? binName
@@ -278,22 +301,48 @@ async function getAgentVersion(
278301 const quotedCmd = `\`${ agent } ${ FLAG_VERSION } \``
279302 debugFn ( 'stdio' , `spawn: ${ quotedCmd } ` )
280303 try {
304+ let stdout : string
305+
306+ // Some package manager "executables" may resolve to non-executable wrapper scripts
307+ // (e.g. the extensionless `npm` shim on Windows). Resolve the underlying entrypoint
308+ // and run it with Node when it is a JS file.
309+ let shouldRunWithNode : string | null = null
310+ try {
311+ const resolved = resolveBinPathSync ( agentExecPath )
312+ const ext = path . extname ( resolved ) . toLowerCase ( )
313+ if ( ext === '.js' || ext === '.cjs' || ext === '.mjs' ) {
314+ shouldRunWithNode = resolved
315+ }
316+ } catch ( e ) {
317+ debugFn ( 'warn' , `Failed to resolve bin path for ${ agentExecPath } , falling back to direct spawn.` )
318+ debugDir ( 'error' , e )
319+ }
320+
321+ if ( shouldRunWithNode ) {
322+ stdout = (
323+ await spawn (
324+ constants . execPath ,
325+ [ ...constants . nodeNoWarningsFlags , shouldRunWithNode , FLAG_VERSION ] ,
326+ { cwd } ,
327+ )
328+ ) . stdout
329+ } else {
330+ stdout = (
331+ await spawn ( agentExecPath , [ FLAG_VERSION ] , {
332+ cwd,
333+ // On Windows, package managers are often .cmd files that require shell execution.
334+ // The spawn function from @socketsecurity /registry will handle this properly
335+ // when shell is true.
336+ shell : constants . WIN32 ,
337+ } )
338+ ) . stdout
339+ }
340+
281341 result =
282342 // Coerce version output into a valid semver version by passing it through
283343 // semver.coerce which strips leading v's, carets (^), comparators (<,<=,>,>=,=),
284344 // and tildes (~).
285- semver . coerce (
286- // All package managers support the "--version" flag.
287- (
288- await spawn ( agentExecPath , [ FLAG_VERSION ] , {
289- cwd,
290- // On Windows, package managers are often .cmd files that require shell execution.
291- // The spawn function from @socketsecurity /registry will handle this properly
292- // when shell is true.
293- shell : constants . WIN32 ,
294- } )
295- ) . stdout ,
296- ) ?? undefined
345+ semver . coerce ( stdout ) ?? undefined
297346 } catch ( e ) {
298347 debugFn ( 'error' , `Package manager command failed: ${ quotedCmd } ` )
299348 debugDir ( 'inspect' , { cmd : quotedCmd } )
0 commit comments