Create pijul.ts for executing commands using the Pijul CLI
[?]
Dec 23, 2020, 10:20 PM
ZGMIJNFVDK7R6AF56FNCA23W5KV3HVBUBPTWMLQADCEPB3MOPELQCDependencies
- [2]
NCBEWRYEInitialize Repository
Change contents
- file addition: pijul.ts[2.104980]
import * as cp from 'child_process';import { EventEmitter } from 'events';import * as iconv from 'iconv-lite-umd';import { CancellationToken } from 'vscode';import { dispose, IDisposable, toDisposable } from './disposableUtils';import { onceEvent } from './eventUtils';export interface IPijulOptions {path: stringversion: string}/*** The Pijul class holds information about the Pijul installation* in use by the extension which it uses for executing commands with* the Pijul CLI. All interactions with the CLI come through this class.*/export class Pijul {readonly path: string;readonly version: string;private readonly _onOutput = new EventEmitter();get onOutput (): EventEmitter { return this._onOutput; }constructor (options: IPijulOptions) {this.path = options.path;this.version = options.version;}/*** Opens an existing Pijul repository at the given location* @param repository The path to the root of the repository* @param dotPijul The path to the repository's .pijul directory*/open (repository: string, dotPijul: string): Repository {return new Repository(this, repository, dotPijul);}/*** Runs the command to initialize a new pijul repository* @param repository The location where the new repository will be created*/async init (repository: string): Promise<void> {await this.exec(repository, ['init']);}/*** Executes a new Pijul command in the given working directory.* @param cwd The current working directory in which the command will be run* @param args The arguments that will be passed to the command* @param options The options for the spawned child process*/async exec (cwd: string, args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {options = Object.assign({ cwd }, options);return await this._exec(args, options);}/*** Executes a new pijul command with the given arguments and decodes the execution result from a buffer into a string* @param args The arguments that will be passed to the command* @param options The options for the spawned child process*/private async _exec (args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {const child = this.spawn(args, options);if (options.onSpawn) {options.onSpawn(child);}if (options.input) {child.stdin!.end(options.input, 'utf8');}const bufferResult = await exec(child, options.cancellationToken);if (options.log !== false && bufferResult.stderr.length > 0) {this.log(`${bufferResult.stderr}\n`);}let encoding = options.encoding ?? 'utf8';encoding = iconv.encodingExists(encoding) ? encoding : 'utf8';const result: IExecutionResult<string> = {exitCode: bufferResult.exitCode,stdout: iconv.decode(bufferResult.stdout, encoding),stderr: bufferResult.stderr};if (bufferResult.exitCode) {return await Promise.reject<IExecutionResult<string>>(new Error('Error executing pijul command'));}return result;}/*** Spawn a Pijul process using the given arguments to utilize the Pijul CLI* @param args The arguments that will be passed to the child process* @param options The options that will be used to spawn the new process*/private spawn (args: string[], options: SpawnOptions = {}): cp.ChildProcess {if (!this.path) {throw new Error('Pijul could not be found in the system.');}if (!options.stdio && !options.input) {options.stdio = ['ignore', null, null]; // Unless provided, ignore stdin and leave default streams for stdout and stderr}if (!options.log) {this.log(`> pijul ${args.join(' ')}\n`);}return cp.spawn(this.path, args, options);}/*** Emit a log message as an event* @param message The message to be emitted*/private log (message: string): void {this._onOutput.emit('log', message);}}/*** The repository class represents a single Pijul repository.* It holds a reference to the Pijul installation which it uses* for interacting with the Pijul CLI. The Repository class exposes* functions for all of the Pijul CLI commands which require an* existing repository to run.*/export class Repository {constructor (private readonly _pijul: Pijul,private readonly repositoryRoot: string,readonly dotPijul: string) {}get pijul (): Pijul {return this._pijul;}get root (): string {return this.repositoryRoot;}}/*** Handles the execution of a child process and the capture of its results, cancelling it if necessary* @param child The child process being executed* @param cancellationToken A cancellation token that can be used to cancel the child process*/async function exec (child: cp.ChildProcess, cancellationToken?: CancellationToken): Promise<IExecutionResult<Buffer>> {if (!child.stdout || !child.stderr) {throw new Error('Failed to get stdout or stderr from git process.');}if (cancellationToken?.isCancellationRequested) {throw new Error('Cancelled');}const disposables: IDisposable[] = [];// Create handles for stdout and stderr eventsconst once = (ee: NodeJS.EventEmitter, name: string, fn: (...args: any[]) => void): void => {ee.once(name, fn);disposables.push(toDisposable(() => ee.removeListener(name, fn)));};const on = (ee: NodeJS.EventEmitter, name: string, fn: (...args: any[]) => void): void => {ee.on(name, fn);disposables.push(toDisposable(() => ee.removeListener(name, fn)));};// Create a promise representing all the different results of the child processlet result = Promise.all<any>([new Promise<number>((resolve, reject) => {once(child, 'error', (err) => (reject(err)));once(child, 'exit', resolve);}),new Promise<Buffer>(resolve => {const buffers: Buffer[] = [];on(child.stdout!, 'data', (b: Buffer) => buffers.push(b));once(child.stdout!, 'close', () => resolve(Buffer.concat(buffers)));}),new Promise<string>(resolve => {const buffers: Buffer[] = [];on(child.stderr!, 'data', (b: Buffer) => buffers.push(b));once(child.stderr!, 'close', () => resolve(Buffer.concat(buffers).toString('utf8')));})]) as Promise<[number, Buffer, string]>;if (cancellationToken) {const cancellationPromise = new Promise<[number, Buffer, string]>((resolve, reject) => {onceEvent(cancellationToken.onCancellationRequested)(() => {try {child.kill();} catch (err) {// Do nothing}reject(new Error('Cancelled'));});});result = Promise.race([result, cancellationPromise]);}try {const [exitCode, stdout, stderr] = await result;return { exitCode, stdout, stderr };} finally {dispose(disposables);}}/*** Interface for representing the results of the execution of a child process*/export interface IExecutionResult<T extends string | Buffer> {exitCode: numberstdout: Tstderr: string}/*** Interface for representing the options for spawning a child process*/export interface SpawnOptions extends cp.SpawnOptions {input?: stringencoding?: stringlog?: booleancancellationToken?: CancellationTokenonSpawn?: (childProcess: cp.ChildProcess) => void}