Skip to content

Commit ca51b61

Browse files
committed
Fix dlx-binary Windows shell execution by adding cache dir to PATH
When spawning .cmd/.bat files on Windows with shell: true, cmd.exe uses PATH + PATHEXT to locate executables. Since dlx downloads to a custom cache directory not in PATH (unlike system package managers), we must prepend the cache directory to PATH so cmd.exe can find the binary after spawn strips it to basename.
1 parent 52b894f commit ca51b61

1 file changed

Lines changed: 28 additions & 5 deletions

File tree

registry/src/lib/dlx-binary.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,12 +294,35 @@ export async function dlxBinary(
294294
}
295295

296296
// Execute the binary.
297-
// On Windows, script files (.bat, .cmd, .ps1) require shell: true.
297+
// On Windows, script files (.bat, .cmd, .ps1) require shell: true because
298+
// they are not executable on their own and must be run through cmd.exe.
298299
// Note: .exe files are actual binaries and don't need shell mode.
299-
const finalSpawnOptions =
300-
WIN32 && /\.(?:bat|cmd|ps1)$/i.test(binaryPath)
301-
? { ...spawnOptions, shell: true }
302-
: spawnOptions
300+
const needsShell = WIN32 && /\.(?:bat|cmd|ps1)$/i.test(binaryPath)
301+
// Windows cmd.exe PATH resolution behavior:
302+
// When shell: true on Windows with .cmd/.bat/.ps1 files, spawn will automatically
303+
// strip the full path down to just the basename without extension (e.g.,
304+
// C:\cache\test.cmd becomes just "test"). Windows cmd.exe then searches for "test"
305+
// in directories listed in PATH, trying each extension from PATHEXT environment
306+
// variable (.COM, .EXE, .BAT, .CMD, etc.) until it finds a match.
307+
//
308+
// Since our binaries are downloaded to a custom cache directory that's not in PATH
309+
// (unlike system package managers like npm/pnpm/yarn which are already in PATH),
310+
// we must prepend the cache directory to PATH so cmd.exe can locate the binary.
311+
//
312+
// This approach is consistent with how other tools handle Windows command execution:
313+
// - npm's promise-spawn: uses which.sync() to find commands in PATH
314+
// - cross-spawn: spawns cmd.exe with escaped arguments
315+
// - Node.js spawn with shell: true: delegates to cmd.exe which uses PATH
316+
const finalSpawnOptions = needsShell
317+
? {
318+
...spawnOptions,
319+
env: {
320+
...spawnOptions?.env,
321+
PATH: `${cacheEntryDir}${path.delimiter}${process.env['PATH'] || ''}`,
322+
},
323+
shell: true,
324+
}
325+
: spawnOptions
303326
const spawnPromise = spawn(binaryPath, args, finalSpawnOptions, spawnExtra)
304327

305328
return {

0 commit comments

Comments
 (0)