From 99cab134b83af5c8a8b368c390360a70b4a73903 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 21 Oct 2025 14:24:44 -0700 Subject: [PATCH 1/2] Fix an issue with the return type of Executable.waitForExitAsync. --- ...rom-waitForExitAsync_2025-10-21-21-24.json | 10 +++ common/reviews/api/node-core-library.api.md | 12 ++- libraries/node-core-library/src/Executable.ts | 79 ++++++++++++------- libraries/node-core-library/src/index.ts | 1 + .../src/test/Executable.test.ts | 9 ++- 5 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 common/changes/@rushstack/node-core-library/drop-stdout-and-stderr-from-waitForExitAsync_2025-10-21-21-24.json diff --git a/common/changes/@rushstack/node-core-library/drop-stdout-and-stderr-from-waitForExitAsync_2025-10-21-21-24.json b/common/changes/@rushstack/node-core-library/drop-stdout-and-stderr-from-waitForExitAsync_2025-10-21-21-24.json new file mode 100644 index 00000000000..e0105c3c514 --- /dev/null +++ b/common/changes/@rushstack/node-core-library/drop-stdout-and-stderr-from-waitForExitAsync_2025-10-21-21-24.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/node-core-library", + "comment": "Update the return type of `Executable.waitForExitAsync` to omit `stdout` and `stderr` if an `encoding` parameter isn't passed to the options object.", + "type": "patch" + } + ], + "packageName": "@rushstack/node-core-library" +} \ No newline at end of file diff --git a/common/reviews/api/node-core-library.api.md b/common/reviews/api/node-core-library.api.md index a1887f9e036..6133072d1c5 100644 --- a/common/reviews/api/node-core-library.api.md +++ b/common/reviews/api/node-core-library.api.md @@ -108,7 +108,7 @@ export class Executable { static tryResolve(filename: string, options?: IExecutableResolveOptions): string | undefined; static waitForExitAsync(childProcess: child_process.ChildProcess, options: IWaitForExitWithStringOptions): Promise>; static waitForExitAsync(childProcess: child_process.ChildProcess, options: IWaitForExitWithBufferOptions): Promise>; - static waitForExitAsync(childProcess: child_process.ChildProcess, options?: IWaitForExitOptions): Promise>; + static waitForExitAsync(childProcess: child_process.ChildProcess, options?: IWaitForExitOptions): Promise; } // @public @@ -667,13 +667,17 @@ export interface IWaitForExitOptions { } // @public -export interface IWaitForExitResult { - exitCode: number | null; - signal: string | null; +export interface IWaitForExitResult extends IWaitForExitResultWithoutOutput { stderr: T; stdout: T; } +// @public +export interface IWaitForExitResultWithoutOutput { + exitCode: number | null; + signal: string | null; +} + // @public export interface IWaitForExitWithBufferOptions extends IWaitForExitOptions { encoding: 'buffer'; diff --git a/libraries/node-core-library/src/Executable.ts b/libraries/node-core-library/src/Executable.ts index 1d85b97e82c..f7f73603205 100644 --- a/libraries/node-core-library/src/Executable.ts +++ b/libraries/node-core-library/src/Executable.ts @@ -160,21 +160,12 @@ export interface IWaitForExitWithBufferOptions extends IWaitForExitOptions { } /** - * The result of running a process to completion using {@link Executable.(waitForExitAsync:3)}. + * The result of running a process to completion using {@link Executable.(waitForExitAsync:3)}. This + * interface does not include stdout or stderr output because an {@link IWaitForExitOptions.encoding} was not specified. * * @public */ -export interface IWaitForExitResult { - /** - * The process stdout output, if encoding was specified. - */ - stdout: T; - - /** - * The process stderr output, if encoding was specified. - */ - stderr: T; - +export interface IWaitForExitResultWithoutOutput { /** * The process exit code. If the process was terminated, this will be null. */ @@ -188,6 +179,25 @@ export interface IWaitForExitResult { signal: string | null; } +/** + * The result of running a process to completion using {@link Executable.(waitForExitAsync:1)}, + * or {@link Executable.(waitForExitAsync:2)}. + * + * @public + */ +export interface IWaitForExitResult + extends IWaitForExitResultWithoutOutput { + /** + * The process stdout output, if encoding was specified. + */ + stdout: T; + + /** + * The process stderr output, if encoding was specified. + */ + stderr: T; +} + // Common environmental state used by Executable members interface IExecutableContext { currentWorkingDirectory: string; @@ -554,12 +564,12 @@ export class Executable { public static async waitForExitAsync( childProcess: child_process.ChildProcess, options?: IWaitForExitOptions - ): Promise>; + ): Promise; - public static async waitForExitAsync( + public static async waitForExitAsync( childProcess: child_process.ChildProcess, options: IWaitForExitOptions = {} - ): Promise> { + ): Promise | IWaitForExitResultWithoutOutput> { const { throwOnNonZeroExitCode, throwOnSignal, encoding } = options; if (encoding && (!childProcess.stdout || !childProcess.stderr)) { throw new Error( @@ -611,22 +621,31 @@ export class Executable { } ); - let stdout: T | undefined; - let stderr: T | undefined; - if (encoding === 'buffer') { - stdout = Buffer.concat(collectedStdout as Buffer[]) as T; - stderr = Buffer.concat(collectedStderr as Buffer[]) as T; - } else if (encoding !== undefined) { - stdout = collectedStdout.join('') as T; - stderr = collectedStderr.join('') as T; - } + let result: IWaitForExitResult | IWaitForExitResultWithoutOutput; + if (encoding) { + let stdout: T | undefined; + let stderr: T | undefined; + + if (encoding === 'buffer') { + stdout = Buffer.concat(collectedStdout as Buffer[]) as T; + stderr = Buffer.concat(collectedStderr as Buffer[]) as T; + } else if (encoding !== undefined) { + stdout = collectedStdout.join('') as T; + stderr = collectedStderr.join('') as T; + } - const result: IWaitForExitResult = { - stdout: stdout as T, - stderr: stderr as T, - exitCode, - signal - }; + result = { + stdout: stdout as T, + stderr: stderr as T, + exitCode, + signal + }; + } else { + result = { + exitCode, + signal + }; + } return result; } diff --git a/libraries/node-core-library/src/index.ts b/libraries/node-core-library/src/index.ts index 9b359180b4e..648b559353c 100644 --- a/libraries/node-core-library/src/index.ts +++ b/libraries/node-core-library/src/index.ts @@ -32,6 +32,7 @@ export { type IWaitForExitWithBufferOptions, type IWaitForExitWithStringOptions, type IWaitForExitResult, + type IWaitForExitResultWithoutOutput, type IProcessInfo, Executable } from './Executable'; diff --git a/libraries/node-core-library/src/test/Executable.test.ts b/libraries/node-core-library/src/test/Executable.test.ts index d7a2b0ced81..97da9ed6f95 100644 --- a/libraries/node-core-library/src/test/Executable.test.ts +++ b/libraries/node-core-library/src/test/Executable.test.ts @@ -12,7 +12,8 @@ import { parseProcessListOutputAsync, type IProcessInfo, type IExecutableSpawnSyncOptions, - type IWaitForExitResult + type IWaitForExitResult, + type IWaitForExitResultWithoutOutput } from '../Executable'; import { FileSystem } from '../FileSystem'; import { PosixModeBits } from '../PosixModeBits'; @@ -236,11 +237,11 @@ describe('Executable process tests', () => { environment, currentWorkingDirectory: executableFolder }); - const result: IWaitForExitResult = await Executable.waitForExitAsync(childProcess); + const result: IWaitForExitResultWithoutOutput = await Executable.waitForExitAsync(childProcess); expect(result.exitCode).toEqual(0); expect(result.signal).toBeNull(); - expect(result.stderr).toBeUndefined(); - expect(result.stderr).toBeUndefined(); + expect('stdout' in result).toBe(false); + expect('stderr' in result).toBe(false); }); test('Executable.runToCompletion(Executable.spawn("npm-binary-wrapper")) with buffer output', async () => { From 37519fc67948a5fe868083ae6b1dbeb35600b831 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 21 Oct 2025 14:52:39 -0700 Subject: [PATCH 2/2] fixup! Fix an issue with the return type of Executable.waitForExitAsync. --- .../dist/tsdoc-metadata.json | 2 +- ...rom-waitForExitAsync_2025-10-21-21-52.json | 10 ++++++++ libraries/rush-lib/src/utilities/Utilities.ts | 23 +++++++++++++++---- 3 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 common/changes/@microsoft/rush/drop-stdout-and-stderr-from-waitForExitAsync_2025-10-21-21-52.json diff --git a/build-tests/api-extractor-test-05/dist/tsdoc-metadata.json b/build-tests/api-extractor-test-05/dist/tsdoc-metadata.json index a66385c6279..c7877dbdce7 100644 --- a/build-tests/api-extractor-test-05/dist/tsdoc-metadata.json +++ b/build-tests/api-extractor-test-05/dist/tsdoc-metadata.json @@ -5,7 +5,7 @@ "toolPackages": [ { "packageName": "@microsoft/api-extractor", - "packageVersion": "7.53.0" + "packageVersion": "7.53.1" } ] } diff --git a/common/changes/@microsoft/rush/drop-stdout-and-stderr-from-waitForExitAsync_2025-10-21-21-52.json b/common/changes/@microsoft/rush/drop-stdout-and-stderr-from-waitForExitAsync_2025-10-21-21-52.json new file mode 100644 index 00000000000..bd7ff97cb34 --- /dev/null +++ b/common/changes/@microsoft/rush/drop-stdout-and-stderr-from-waitForExitAsync_2025-10-21-21-52.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/libraries/rush-lib/src/utilities/Utilities.ts b/libraries/rush-lib/src/utilities/Utilities.ts index e403ccacbd2..f339fdb5ce9 100644 --- a/libraries/rush-lib/src/utilities/Utilities.ts +++ b/libraries/rush-lib/src/utilities/Utilities.ts @@ -16,7 +16,8 @@ import { SubprocessTerminator, Executable, type IWaitForExitResult, - Async + Async, + type IWaitForExitResultWithoutOutput } from '@rushstack/node-core-library'; import type { RushConfiguration } from '../api/RushConfiguration'; @@ -161,6 +162,11 @@ export type OptionalToUndefined = Omit> & { [K in OptionalKeys]-?: Exclude | undefined; }; +type IExecuteCommandInternalOptions = Omit & { + stdio: child_process.SpawnSyncOptions['stdio']; + captureOutput: boolean; +}; + export class Utilities { public static syncNpmrc: typeof syncNpmrc = syncNpmrc; @@ -781,6 +787,16 @@ export class Utilities { * Executes the command with the specified command-line parameters, and waits for it to complete. * The current directory will be set to the specified workingDirectory. */ + private static async _executeCommandInternalAsync( + options: IExecuteCommandInternalOptions & { captureOutput: true } + ): Promise>; + /** + * Executes the command with the specified command-line parameters, and waits for it to complete. + * The current directory will be set to the specified workingDirectory. This does not capture output. + */ + private static async _executeCommandInternalAsync( + options: IExecuteCommandInternalOptions & { captureOutput: false | undefined } + ): Promise; private static async _executeCommandInternalAsync({ command, args, @@ -791,10 +807,7 @@ export class Utilities { onStdoutStreamChunk, captureOutput, captureExitCodeAndSignal - }: Omit & { - stdio: child_process.SpawnSyncOptions['stdio']; - captureOutput: boolean; - }): Promise { + }: IExecuteCommandInternalOptions): Promise | IWaitForExitResultWithoutOutput> { const options: child_process.SpawnSyncOptions = { cwd: workingDirectory, shell: true,