11import { exists , log , retry , TmpDir } from "builder-util"
2+ import { spawn as spawnProcess } from "child_process"
23import { CancellationToken } from "builder-util-runtime"
3- import { exec , spawn } from "child_process"
44import * as fs from "fs-extra"
55import { createWriteStream , readJson } from "fs-extra"
66import { Lazy } from "lazy-val"
77import * as path from "path"
8- import { promisify } from "util"
98import { hoist , type HoisterResult , type HoisterTree } from "./hoist"
109import { createModuleCache , type ModuleCache } from "./moduleCache"
1110import { getPackageManagerCommand , PM } from "./packageManager"
1211import type { Dependency , DependencyGraph , NodeModuleInfo , PackageJson } from "./types"
1312
14- const execAsync = promisify ( exec )
15-
1613export abstract class NodeModulesCollector < ProdDepType extends Dependency < ProdDepType , OptionalDepType > , OptionalDepType > {
1714 private nodeModules : NodeModuleInfo [ ] = [ ]
1815 protected allDependencies : Map < string , ProdDepType > = new Map ( )
@@ -96,7 +93,7 @@ export abstract class NodeModulesCollector<ProdDepType extends Dependency<ProdDe
9693
9794 return retry (
9895 async ( ) => {
99- await this . streamCollectorCommandToJsonFile ( command , args , this . rootDir , tempOutputFile )
96+ await this . streamCollectorCommandToFile ( command , args , this . rootDir , tempOutputFile )
10097 const shellOutput = await fs . readFile ( tempOutputFile , { encoding : "utf8" } )
10198 return await this . parseDependenciesTree ( shellOutput )
10299 } ,
@@ -323,20 +320,25 @@ export abstract class NodeModulesCollector<ProdDepType extends Dependency<ProdDe
323320 }
324321
325322 async asyncExec ( command : string , args : string [ ] , cwd : string = this . rootDir ) : Promise < { stdout : string | undefined ; stderr : string | undefined } > {
326- const payload = await execAsync ( [ command , ...args ] . join ( " " ) , { cwd, maxBuffer : 100 * 1024 * 1024 , encoding : "utf8" } ) . catch ( err => {
327- log . error ( { err } , "failed to execute command" )
328- return { stdout : undefined , stderr : err . message }
329- } )
330- return { stdout : payload . stdout ?. trim ( ) ?? undefined , stderr : payload . stderr ?. trim ( ) ?? undefined }
323+ const file = await this . tempDirManager . getTempFile ( { prefix : "exec-" , suffix : ".txt" } )
324+ try {
325+ await this . streamCollectorCommandToFile ( command , args , cwd , file )
326+ const result = await fs . readFile ( file , { encoding : "utf8" } )
327+ return { stdout : result ?. trim ( ) , stderr : undefined }
328+ } catch ( error : any ) {
329+ log . debug ( { error : error . message } , "failed to execute command" )
330+ return { stdout : undefined , stderr : error . message }
331+ }
331332 }
332333
333- async streamCollectorCommandToJsonFile ( command : string , args : string [ ] , cwd : string , tempOutputFile : string ) {
334+ async streamCollectorCommandToFile ( command : string , args : string [ ] , cwd : string , tempOutputFile : string ) {
334335 const execName = path . basename ( command , path . extname ( command ) )
335336 const isWindowsScriptFile = process . platform === "win32" && path . extname ( command ) . toLowerCase ( ) === ".cmd"
336337 if ( isWindowsScriptFile ) {
337338 // If the command is a Windows script file (.cmd), we need to wrap it in a .bat file to ensure it runs correctly with cmd.exe
338339 // This is necessary because .cmd files are not directly executable in the same way as .bat files.
339340 // We create a temporary .bat file that calls the .cmd file with the provided arguments. The .bat file will be executed by cmd.exe.
341+ // Note: This is a workaround for Windows command execution quirks for specifically when `shell: false`
340342 const tempBatFile = await this . tempDirManager . getTempFile ( {
341343 prefix : execName ,
342344 suffix : ".bat" ,
@@ -350,7 +352,7 @@ export abstract class NodeModulesCollector<ProdDepType extends Dependency<ProdDe
350352 await new Promise < void > ( ( resolve , reject ) => {
351353 const outStream = createWriteStream ( tempOutputFile )
352354
353- const child = spawn ( command , args , {
355+ const child = spawnProcess ( command , args , {
354356 cwd,
355357 shell : false , // required to prevent console logs polution from shell profile loading when `true`
356358 } )
0 commit comments