| // 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 { ChildProcessWithoutNullStreams, spawn } from 'child_process'; |
| import { DataStreamProcess, JsonStreamProcess } from './process'; |
| import * as logger from './logger'; |
| import { Ffx } from './ffx'; |
| |
| export class Fx { |
| constructor(public fuchsiaDir: string | undefined, public readonly ffx: Ffx) { |
| } |
| |
| /** |
| * Spawns a new process running fx with given arguments. |
| * @param args command line arguments to fx |
| * @returns ChildProcess is returned or undefined if there was a problem |
| */ |
| public runStreaming(args: string[]): ChildProcessWithoutNullStreams | undefined { |
| if (!this.fuchsiaDir) { |
| return; |
| } |
| let env = { ...process.env }; |
| env['PATH'] = `${this.fuchsiaDir}/.jiri_root/bin:${env.PATH}`; |
| if (this.ffx.targetDevice) { |
| env['FUCHSIA_NODENAME'] = this.ffx.targetDevice.nodeName; |
| } |
| const options = { detached: true, cwd: this.fuchsiaDir, env }; |
| return spawn('fx', args, options); |
| } |
| |
| public runAsync( |
| args: string[], |
| onData: (data: Buffer) => void, |
| onError: (data: Buffer) => void): DataStreamProcess | undefined { |
| const process = this.runStreaming(args); |
| if (!process) { |
| return; |
| } |
| return new DataStreamProcess(process, onData, onError); |
| } |
| |
| /** |
| * Return fx response data |
| * @param arg |
| * @returns stdout of command or error with stderr. |
| */ |
| public runFx(args: string[]): Promise<string> { |
| return new Promise<string>((resolve, reject) => { |
| let output = ''; |
| const cmd = this.runStreaming(args); |
| cmd?.stdout.on('data', (data) => { output += data; }); |
| |
| let errorOutput = ''; |
| cmd?.stderr.on('data', (data) => { errorOutput += data; }); |
| |
| // Keep track of the error state so that the 'close' event handler below won't handle the |
| // error again. |
| let hasError = false; |
| cmd?.on('error', err => { |
| logger.warn(`exit: ${err}`, 'ffx'); |
| hasError = true; |
| return reject(err); |
| }); |
| |
| // Listen to 'close' instead of 'exit' event to correctly capture the output of ffx. |
| // The 'exit' event may be emitted when the stdio streams of the child process are still |
| // open, while the 'close' event is emitted after the stdio streams of a child process |
| // have been closed. |
| // See https://nodejs.org/api/child_process.html#class-childprocess for more details. |
| cmd?.on('close', (code, signal) => { |
| if (!hasError) { |
| logger.debug(`exit: ${code}: ${signal}`, 'ffx'); |
| if (code === 0) { |
| return resolve(output); |
| } else { |
| return reject( |
| new Error(`fx returned with non-zero exit code ${code}: ${errorOutput}`)); |
| } |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * Runs fx with given arguments and logs result. |
| * @param args command line arguments to fx |
| * @param onData when data is received this command will be called |
| * @returns fx log process |
| */ |
| public runJsonStreaming( |
| args: string[], |
| onData: (data: Object) => void |
| ): JsonStreamProcess { |
| return new JsonStreamProcess( |
| this.runStreaming(args), |
| onData, |
| (data) => { |
| logger.warn(`Error [fx ${args}]: ${data}`); |
| }); |
| } |
| |
| } |