@@ -3,13 +3,25 @@ import { existsSync, readdirSync } from 'fs';
33import { join , resolve } from 'path' ;
44
55import type { ValidatedCdsCommand } from './types' ;
6+ import { getPlatformInfo } from '../../environment' ;
67import { fileExists } from '../../filesystem' ;
78import { cdsExtractorLog } from '../../logging' ;
89import type { CdsDependencyGraph } from '../parser/types' ;
910
1011/** Default timeout for command execution in milliseconds. **/
1112export const DEFAULT_COMMAND_TIMEOUT_MS = 10000 ;
1213
14+ /**
15+ * Returns the platform-appropriate name of the local cds binary as
16+ * created by `npm install` under `node_modules/.bin/`. On Windows the
17+ * directly-executable file is `cds.cmd`; on Unix it is `cds` (a shell
18+ * script). Using the wrong name causes execFileSync to fail (EINVAL on
19+ * Windows + Node 20+ for shimless paths).
20+ */
21+ function localCdsBinName ( ) : string {
22+ return getPlatformInfo ( ) . isWindows ? 'cds.cmd' : 'cds' ;
23+ }
24+
1325/**
1426 * Cache for CDS command test results to avoid running the same CLI commands repeatedly.
1527 */
@@ -336,7 +348,7 @@ function discoverAvailableCacheDirs(sourceRoot: string): string[] {
336348 for ( const entry of entries ) {
337349 if ( entry . isDirectory ( ) && entry . name . startsWith ( 'cds-' ) ) {
338350 const cacheDir = join ( cacheRootDir , entry . name ) ;
339- const cdsBin = join ( cacheDir , 'node_modules' , '.bin' , 'cds' ) ;
351+ const cdsBin = join ( cacheDir , 'node_modules' , '.bin' , localCdsBinName ( ) ) ;
340352 if ( fileExists ( cdsBin ) ) {
341353 availableDirs . push ( cacheDir ) ;
342354 }
@@ -370,7 +382,7 @@ function getBestCdsCommand(
370382
371383 // If a specific cache directory is provided and valid, prefer it
372384 if ( cacheDir ) {
373- const localCdsBin = join ( cacheDir , 'node_modules' , '.bin' , 'cds' ) ;
385+ const localCdsBin = join ( cacheDir , 'node_modules' , '.bin' , localCdsBinName ( ) ) ;
374386 const command = createCdsCommandForPath ( localCdsBin ) ;
375387 if ( command ) {
376388 const result = testCdsCommand ( command , sourceRoot , true ) ;
@@ -382,7 +394,7 @@ function getBestCdsCommand(
382394
383395 // Try any available cache directories
384396 for ( const availableCacheDir of cdsCommandCache . availableCacheDirs ) {
385- const localCdsBin = join ( availableCacheDir , 'node_modules' , '.bin' , 'cds' ) ;
397+ const localCdsBin = join ( availableCacheDir , 'node_modules' , '.bin' , localCdsBinName ( ) ) ;
386398 const command = createCdsCommandForPath ( localCdsBin ) ;
387399 if ( command ) {
388400 const result = testCdsCommand ( command , sourceRoot , true ) ;
@@ -508,6 +520,10 @@ function testCdsCommand(
508520 timeout : DEFAULT_COMMAND_TIMEOUT_MS , // timeout after 10 seconds
509521 cwd : sourceRoot ,
510522 env : cleanEnv ,
523+ // Required on Windows + Node 20+ to execute .cmd shims (e.g. cds.cmd, npx.cmd).
524+ // All inputs here come from the extractor's own command construction, so there is
525+ // no shell-injection surface from external input.
526+ shell : getPlatformInfo ( ) . isWindows ,
511527 } ,
512528 ) . toString ( ) ;
513529
0 commit comments