Skip to content

Commit ef364d3

Browse files
authored
fix: node 14 vs 15+ promisify warning on child_process.exec (#9374)
1 parent 1607820 commit ef364d3

File tree

2 files changed

+19
-12
lines changed

2 files changed

+19
-12
lines changed

.changeset/pretty-maps-take.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"app-builder-lib": patch
3+
---
4+
5+
fix: node 14 vs 15+ promisify warning on child_process.exec

packages/app-builder-lib/src/node-module-collector/nodeModulesCollector.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import { exists, log, retry, TmpDir } from "builder-util"
2+
import { spawn as spawnProcess } from "child_process"
23
import { CancellationToken } from "builder-util-runtime"
3-
import { exec, spawn } from "child_process"
44
import * as fs from "fs-extra"
55
import { createWriteStream, readJson } from "fs-extra"
66
import { Lazy } from "lazy-val"
77
import * as path from "path"
8-
import { promisify } from "util"
98
import { hoist, type HoisterResult, type HoisterTree } from "./hoist"
109
import { createModuleCache, type ModuleCache } from "./moduleCache"
1110
import { getPackageManagerCommand, PM } from "./packageManager"
1211
import type { Dependency, DependencyGraph, NodeModuleInfo, PackageJson } from "./types"
1312

14-
const execAsync = promisify(exec)
15-
1613
export 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

Comments
 (0)