Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/plugin-axe/src/lib/runner/run-axe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ async function ensureBrowserInstalled(): Promise<void> {
await executeProcess({
command: 'npx',
args: ['playwright-core', 'install', 'chromium'],
silent: true,
});

browserChecked = true;
Expand Down
11 changes: 11 additions & 0 deletions packages/utils/src/lib/execute-process.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
120 changes: 67 additions & 53 deletions packages/utils/src/lib/execute-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
args?: string[];
observer?: ProcessObserver;
ignoreExitCode?: boolean;
silent?: boolean;
};

/**
Expand All @@ -127,6 +128,9 @@
/**
* 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
Expand All @@ -153,62 +157,72 @@
* @param cfg - see {@link ProcessConfig}
*/
export function executeProcess(cfg: ProcessConfig): Promise<ProcessResult> {
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<Writable, Readable, Readable>;

// 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<ProcessResult>((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<Writable, Readable, Readable>;

// 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);
}
Loading