diff --git a/packages/plugin-axe/src/lib/runner/run-axe.ts b/packages/plugin-axe/src/lib/runner/run-axe.ts index 9f54eb23d..96d079739 100644 --- a/packages/plugin-axe/src/lib/runner/run-axe.ts +++ b/packages/plugin-axe/src/lib/runner/run-axe.ts @@ -81,6 +81,7 @@ async function ensureBrowserInstalled(): Promise { await executeProcess({ command: 'npx', args: ['playwright-core', 'install', 'chromium'], + silent: true, }); browserChecked = true; diff --git a/packages/utils/src/lib/execute-process.int.test.ts b/packages/utils/src/lib/execute-process.int.test.ts index 8c7e5d97c..bc0d1d75b 100644 --- a/packages/utils/src/lib/execute-process.int.test.ts +++ b/packages/utils/src/lib/execute-process.int.test.ts @@ -136,4 +136,15 @@ process:complete { force: true }, ); }); + + it('should successfully execute process with silent flag without spinner', async () => { + const result = await executeProcess({ + command: 'node', + args: ['-v'], + silent: true, + }); + expect(result.code).toBe(0); + expect(result.stdout).toMatch(/v\d{1,2}(\.\d{1,2}){0,2}/); + expect(logger.command).not.toHaveBeenCalled(); + }); }); diff --git a/packages/utils/src/lib/execute-process.ts b/packages/utils/src/lib/execute-process.ts index e05bbf444..855ff3050 100644 --- a/packages/utils/src/lib/execute-process.ts +++ b/packages/utils/src/lib/execute-process.ts @@ -103,6 +103,7 @@ export type ProcessConfig = Omit< args?: string[]; observer?: ProcessObserver; ignoreExitCode?: boolean; + silent?: boolean; }; /** @@ -127,6 +128,9 @@ export type ProcessObserver = { /** * Executes a process and returns a promise with the result as `ProcessResult`. * + * By default, displays a spinner via `logger.command()`. Use `silent: true` to bypass + * the spinner for commands that may run concurrently (e.g., plugin setup steps). + * * @example * * // sync process execution @@ -153,62 +157,72 @@ export type ProcessObserver = { * @param cfg - see {@link ProcessConfig} */ export function executeProcess(cfg: ProcessConfig): Promise { - const { command, args, observer, ignoreExitCode = false, ...options } = cfg; + const { + command, + args, + observer, + ignoreExitCode = false, + silent = false, + ...options + } = cfg; const { onStdout, onStderr, onError, onComplete } = observer ?? {}; const bin = [command, ...(args ?? [])].join(' '); - return logger.command( - bin, - () => - new Promise((resolve, reject) => { - const spawnedProcess = spawn(command, args ?? [], { - // shell:true tells Windows to use shell command for spawning a child process - // https://stackoverflow.com/questions/60386867/node-spawn-child-process-not-working-in-windows - shell: true, - windowsHide: true, - ...options, - }) as ChildProcessByStdio; - - // eslint-disable-next-line functional/no-let - let stdout = ''; - // eslint-disable-next-line functional/no-let - let stderr = ''; - // eslint-disable-next-line functional/no-let - let output = ''; // interleaved stdout and stderr - - spawnedProcess.stdout.on('data', (data: unknown) => { - const message = String(data); - stdout += message; - output += message; - onStdout?.(message, spawnedProcess); - }); - - spawnedProcess.stderr.on('data', (data: unknown) => { - const message = String(data); - stderr += message; - output += message; - onStderr?.(message, spawnedProcess); - }); - - spawnedProcess.on('error', error => { + const worker = () => + new Promise((resolve, reject) => { + const spawnedProcess = spawn(command, args ?? [], { + // shell:true tells Windows to use shell command for spawning a child process + // https://stackoverflow.com/questions/60386867/node-spawn-child-process-not-working-in-windows + shell: true, + windowsHide: true, + ...options, + }) as ChildProcessByStdio; + + // eslint-disable-next-line functional/no-let + let stdout = ''; + // eslint-disable-next-line functional/no-let + let stderr = ''; + // eslint-disable-next-line functional/no-let + let output = ''; // interleaved stdout and stderr + + spawnedProcess.stdout.on('data', (data: unknown) => { + const message = String(data); + stdout += message; + output += message; + onStdout?.(message, spawnedProcess); + }); + + spawnedProcess.stderr.on('data', (data: unknown) => { + const message = String(data); + stderr += message; + output += message; + onStderr?.(message, spawnedProcess); + }); + + spawnedProcess.on('error', error => { + reject(error); + }); + + spawnedProcess.on('close', (code, signal) => { + const result: ProcessResult = { bin, code, signal, stdout, stderr }; + if (code === 0 || ignoreExitCode) { + logger.debug(output); + onComplete?.(); + resolve(result); + } else { + // ensure stdout and stderr are logged to help debug failure + logger.debug(output, { force: true }); + const error = new ProcessError(result); + onError?.(error); reject(error); - }); - - spawnedProcess.on('close', (code, signal) => { - const result: ProcessResult = { bin, code, signal, stdout, stderr }; - if (code === 0 || ignoreExitCode) { - logger.debug(output); - onComplete?.(); - resolve(result); - } else { - // ensure stdout and stderr are logged to help debug failure - logger.debug(output, { force: true }); - const error = new ProcessError(result); - onError?.(error); - reject(error); - } - }); - }), - ); + } + }); + }); + + if (silent) { + return worker(); + } + + return logger.command(bin, worker); }