blob: 377236002f08878b478e5afff4267eb6a050413b [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { type ChildProcessWithoutNullStreams } from 'child_process';
import JsonParser from 'jsonparse';
export class DataStreamProcess {
private outputChunks: Buffer[] = [];
constructor(
private process: ChildProcessWithoutNullStreams,
onData: (data: Buffer) => void,
onError: (data: Buffer) => void,
) {
this.process.stdout.on('data', (chunk: unknown) => {
const buffer = Buffer.isBuffer(chunk) ?
chunk :
Buffer.from(typeof chunk === 'string' ? chunk : String(chunk));
this.outputChunks.push(buffer);
onData(buffer);
});
this.process.stderr.on('data', (chunk: unknown) => {
const buffer = Buffer.isBuffer(chunk) ?
chunk :
Buffer.from(typeof chunk === 'string' ? chunk : String(chunk));
this.outputChunks.push(buffer);
onError(buffer);
});
}
/**
* Returns the combined stdout and stderr that the process has written so far.
*/
public get output(): string {
return this.outputChunks.map(chunk => chunk.toString()).join('');
}
/**
* A future that resolves when the process exits.
*/
public get exitCode(): Promise<number | null> {
return new Promise((resolve) => {
this.process.on('exit', resolve);
});
}
/**
* Kills the underlying process and all its children.
*/
public stop() {
const pid = this.process?.pid;
if (pid) {
try {
// This handles the in-tree use case where `ffx` launches a subprocess `fx ffx`.
process.kill(-pid);
} catch {
// If the process with the given `pid` didn't exist, this error will rise, which we can
// just swallow.
}
}
}
}
/**
* A stream of JSON Data coming from a process.
*/
export class JsonStreamProcess {
private process: DataStreamProcess | undefined;
private parser: JsonParser;
constructor(
process: ChildProcessWithoutNullStreams | undefined,
private onData: (data: object) => void,
onError: (data: Buffer) => void,
) {
this.parser = new JsonParser();
if (process) {
this.process = new DataStreamProcess(process, (chunk) => {
this.parser.write(chunk);
}, onError);
}
this.parser.onValue = (value: unknown) => {
if (this.parser.stack.length === 0) {
this.onData(value as object);
}
};
}
/**
* Kills the underlying process and all its children.
*/
public stop() {
this.process?.stop();
}
/**
* Returns the raw combined stdout and stderr that the process has written so far.
*/
public get rawOutput(): string {
return this.process?.output ?? '';
}
/**
* A future that resolves when the process exits.
*/
public get exitCode(): Promise<number | null> {
return (async () => {
const exitCode = await this.process?.exitCode;
if (typeof exitCode === 'undefined') {
return null;
}
return exitCode;
})();
}
}