BREAKING CHANGE(update the API): update

This commit is contained in:
Philipp Kunz 2018-07-30 16:03:48 +02:00
parent a63ae38437
commit 2ca593297c
7 changed files with 291 additions and 264 deletions

View File

@ -1,32 +0,0 @@
# smartshell
shell actions designed as promises
## Availabililty
[![npm](https://pushrocks.gitlab.io/assets/repo-button-npm.svg)](https://www.npmjs.com/package/smartshell)
[![git](https://pushrocks.gitlab.io/assets/repo-button-git.svg)](https://GitLab.com/pushrocks/smartshell)
[![git](https://pushrocks.gitlab.io/assets/repo-button-mirror.svg)](https://github.com/pushrocks/smartshell)
[![docs](https://pushrocks.gitlab.io/assets/repo-button-docs.svg)](https://pushrocks.gitlab.io/smartshell/)
## Status for master
[![build status](https://GitLab.com/pushrocks/smartshell/badges/master/build.svg)](https://GitLab.com/pushrocks/smartshell/commits/master)
[![coverage report](https://GitLab.com/pushrocks/smartshell/badges/master/coverage.svg)](https://GitLab.com/pushrocks/smartshell/commits/master)
[![npm downloads per month](https://img.shields.io/npm/dm/smartshell.svg)](https://www.npmjs.com/package/smartshell)
[![Dependency Status](https://david-dm.org/pushrocks/smartshell.svg)](https://david-dm.org/pushrocks/smartshell)
[![bitHound Dependencies](https://www.bithound.io/github/pushrocks/smartshell/badges/dependencies.svg)](https://www.bithound.io/github/pushrocks/smartshell/master/dependencies/npm)
[![bitHound Code](https://www.bithound.io/github/pushrocks/smartshell/badges/code.svg)](https://www.bithound.io/github/pushrocks/smartshell)
[![TypeScript](https://img.shields.io/badge/TypeScript-2.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%206.x.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
## Usage
Use TypeScript for best in class instellisense.
For further information read the linked docs at the top of this README.
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
[![repo-footer](https://pushrocks.gitlab.io/assets/repo-footer.svg)](https://push.rocks)

View File

@ -1,23 +1,31 @@
import { expect, tap } from '@pushrocks/tapbundle'; import { expect, tap } from '@pushrocks/tapbundle';
import * as smartshell from '../ts/index'; import * as smartshell from '../ts';
import * as smartpromise from '@pushrocks/smartpromise'; import * as smartpromise from '@pushrocks/smartpromise';
let testSmartshell: smartshell.Smartshell; let testSmartshell: smartshell.Smartshell;
tap.test('smartshell should create a Smartshell instance', async () => {
testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: []
});
expect(testSmartshell).to.be.instanceof(smartshell.Smartshell);
});
tap.test('smartshell should run async', async () => { tap.test('smartshell should run async', async () => {
let execResult = await smartshell.exec('npm -v'); let execResult = await testSmartshell.exec('npm -v');
expect(execResult.stdout).to.match(/[0-9\.]*/); expect(execResult.stdout).to.match(/[0-9\.]*/);
}); });
tap.test('smartshell should run async and silent', async () => { tap.test('smartshell should run async and silent', async () => {
let execResult = await smartshell.execSilent('npm -v'); let execResult = await testSmartshell.execSilent('npm -v');
expect(execResult.stdout).to.match(/[0-9\.]*/); expect(execResult.stdout).to.match(/[0-9\.]*/);
}); });
tap.test('smartshell should stream a shell execution', async () => { tap.test('smartshell should stream a shell execution', async () => {
let done = smartpromise.defer(); let done = smartpromise.defer();
let execStreamingResponse = smartshell.execStreaming('npm -v'); let execStreamingResponse = await testSmartshell.execStreaming('npm -v');
execStreamingResponse.childProcess.stdout.on('data', data => { execStreamingResponse.childProcess.stdout.on('data', data => {
done.resolve(data); done.resolve(data);
}); });
@ -27,17 +35,7 @@ tap.test('smartshell should stream a shell execution', async () => {
}); });
tap.test('it should execute and wait for a line in the output', async () => { tap.test('it should execute and wait for a line in the output', async () => {
await smartshell.execAndWaitForLine('echo "5.0.4"', /5.0.4/); await testSmartshell.execAndWaitForLine('echo "5.0.4"', /5.0.4/);
});
// Smartshell class
tap.test('smartshell should create a Smartshell instance', async () => {
testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: []
});
expect(testSmartshell).to.be.instanceof(smartshell.Smartshell);
}); });
tap.test('smartshell should run async', async () => { tap.test('smartshell should run async', async () => {

View File

@ -1,2 +1 @@
export * from './smartshell.wrap';
export * from './smartshell.classes.smartshell'; export * from './smartshell.classes.smartshell';

View File

@ -0,0 +1,50 @@
export type TExecutor = 'sh' | 'bash';
export interface IShellEnvContructorOptions {
executor: TExecutor;
sourceFilePaths: string[];
}
export class ShellEnv {
executor: TExecutor;
sourceFileArray: string[] = [];
/**
* constructor for the shellenv
*/
constructor(optionsArg: IShellEnvContructorOptions) {
this.executor = optionsArg.executor;
for (let sourceFilePath of optionsArg.sourceFilePaths) {
this.sourceFileArray.push(sourceFilePath);
}
};
/**
* add files that are going to be sourced when running a command
* @param sourceFilePathsArray
*/
addSourceFiles(sourceFilePathsArray: string[]) {
for (let sourceFilePath of sourceFilePathsArray) {
this.sourceFileArray.push(sourceFilePath);
}
}
/**
* cleans the source files array
*/
cleanSourceFiles() {
this.sourceFileArray = [];
}
createEnvExecString(commandArg): string {
if (this.executor === 'bash') {
let sourceString = '';
for (let sourceFilePath of this.sourceFileArray) {
sourceString = sourceString + `source ${sourceFilePath} && `;
}
return `bash -c '${sourceString} ${commandArg}'`;
} else {
return commandArg;
}
}
}

View File

@ -0,0 +1,44 @@
import * as plugins from './smartshell.plugins';
/**
* a log handler for spawned logs
* making sure the process doesn't run out of memory
*/
export class ShellLog {
logStore = Buffer.from('');
/**
* log data to console
* @param dataArg
*/
logToConsole(dataArg: string | Buffer): void {
// make sure we have the data as string
const dataString: string = (() => {
if (Buffer.isBuffer(dataArg)) {
return dataArg.toString();
}
return dataArg;
})();
console.log(dataString);
}
/**
* add data to Buffer for later consumption
* @param dataArg
*/
addToBuffer(dataArg: string | Buffer): void {
// make sure we have the data as Buffer
const dataBuffer: Buffer = (() => {
if (!Buffer.isBuffer(dataArg)) {
return Buffer.from(dataArg);
}
return dataArg;
})();
this.logStore = Buffer.concat([this.logStore,dataBuffer]);
}
logAndAdd(dataArg: string | Buffer): void {
this.logToConsole(dataArg);
this.addToBuffer(dataArg);
}
}

View File

@ -1,64 +1,177 @@
// -- imports --
import * as plugins from './smartshell.plugins'; import * as plugins from './smartshell.plugins';
import * as smartshellWrap from './smartshell.wrap'; import * as smartshellWrap from './smartshell.wrap';
import { ShellEnv, IShellEnvContructorOptions, TExecutor, } from './smartshell.classes.shellenv';
import { ShellLog } from './smartshell.classes.shelllog';
export type TExecutor = 'sh' | 'bash'; import * as cp from "child_process";
import { Deferred } from "@pushrocks/smartpromise";
export interface ISmartshellContructorOptions { // -- interfaces --
executor: TExecutor; /**
sourceFilePaths: string[]; * interface for ExecResult
*/
export interface IExecResult {
exitCode: number;
stdout: string;
} }
/**
* interface for streaming ExecResult
*/
export interface IExecResultStreaming {
childProcess: cp.ChildProcess;
finalPromise: Promise<IExecResult>;
}
// -- SmartShell --
export class Smartshell { export class Smartshell {
executor: TExecutor; shellEnv: ShellEnv;
sourceFileArray: string[] = [];
constructor(optionsArg: ISmartshellContructorOptions) {
this.executor = optionsArg.executor;
for (let sourceFilePath of optionsArg.sourceFilePaths) {
this.sourceFileArray.push(sourceFilePath);
}
}
addSourceFiles(sourceFilePathsArray: string[]) { constructor(optionsArg: IShellEnvContructorOptions) {
for (let sourceFilePath of sourceFilePathsArray) { this.shellEnv = new ShellEnv(optionsArg);
this.sourceFileArray.push(sourceFilePath);
}
}
cleanSourceFiles() {
this.sourceFileArray = [];
} }
/** /**
* executes silently and returns IExecResult * imports path into the shell from env if available and returns it with
* @param commandArg
*/ */
async execSilent(commandArg: string) { private _importEnvVarPath (stringArg): string {
let execCommand = this.createExecString(commandArg); if (process.env.SMARTSHELL_PATH) {
return await smartshellWrap.execSilent(execCommand); let commandResult = `PATH=${process.env.SMARTSHELL_PATH} && ${stringArg}`;
} // console.log(commandResult)
return commandResult;
/**
* executes and returns IExecResult
* @param commandArg
*/
async exec(commandArg: string) {
let execCommand = this.createExecString(commandArg);
return await smartshellWrap.exec(execCommand);
}
/**
* creates the final sourcing string
* @param commandArg
*/
private createExecString(commandArg): string {
if (this.executor === 'bash') {
let sourceString = '';
for (let sourceFilePath of this.sourceFileArray) {
sourceString = sourceString + `source ${sourceFilePath} && `;
}
return `bash -c '${sourceString} ${commandArg}'`;
} else { } else {
return commandArg; return stringArg;
} }
};
/**
* executes a given command async
* @param commandStringArg
*/
private async _exec (
commandStringArg: string,
silentArg: boolean = false,
strictArg = false,
streamingArg = false
): Promise<IExecResult | IExecResultStreaming> {
// flow control promises
const done = plugins.smartpromise.defer<IExecResult | IExecResultStreaming>();
const childProcessEnded = plugins.smartpromise.defer<IExecResult>();
// build commandToExecute
let commandToExecute = commandStringArg
commandToExecute = this.shellEnv.createEnvExecString(commandStringArg);
commandToExecute = this._importEnvVarPath(commandToExecute);
const spawnlogInstance = new ShellLog();
const execChildProcess = cp.spawn(commandToExecute, [], {
shell: true,
env: process.env
});
execChildProcess.stdout.on("data", data => {
if (!silentArg) {
spawnlogInstance.logToConsole(data);
} }
spawnlogInstance.addToBuffer(data);
});
execChildProcess.stderr.on("data", data => {
if (!silentArg) {
spawnlogInstance.logToConsole(data);
}
spawnlogInstance.addToBuffer(data);
});
if(streamingArg) {
done.resolve({
childProcess: execChildProcess,
finalPromise: childProcessEnded.promise
});
}
execChildProcess.on("exit", (code, signal) => {
if(strictArg && code === 1) {
done.reject();
}
const execResult = {
exitCode: code,
stdout: spawnlogInstance.logStore.toString()
}
if(!streamingArg) {
done.resolve(execResult);
}
childProcessEnded.resolve(execResult);
});
const result = await done.promise;
return result;
};
async exec (
commandStringArg: string
): Promise<IExecResult> {
return (await this._exec(commandStringArg, false)) as IExecResult;
};
/**
* executes a given command async and silent
* @param commandStringArg
*/
async execSilent (
commandStringArg: string
): Promise<IExecResult> {
return (await this._exec(commandStringArg, true)) as IExecResult;
};
/**
* executes a command async and strict, meaning it rejects the promise if something happens
*/
async execStrict (
commandStringArg: string
): Promise<IExecResult> {
return (await this._exec(commandStringArg, true, true)) as IExecResult;
};
/**
* executes a command and allows you to stream output
*/
async execStreaming (
commandStringArg: string,
silentArg: boolean = false
): Promise<IExecResultStreaming> {
return (await this._exec(commandStringArg, silentArg, false, true)) as IExecResultStreaming;
};
async execStreamingSilent (commandStringArg: string) {
return (await this.execStreaming(commandStringArg, true)) as IExecResultStreaming;
};
/**
* executes a command and returns promise that will be fullfilled once an putput line matches RegexArg
* @param commandStringArg
* @param regexArg
*/
async execAndWaitForLine (
commandStringArg: string,
regexArg: RegExp,
silentArg: boolean = false
) {
let done = plugins.smartpromise.defer();
let execStreamingResult = await this.execStreaming(commandStringArg, silentArg);
execStreamingResult.childProcess.stdout.on("data", (stdOutChunk: string) => {
if (regexArg.test(stdOutChunk)) {
done.resolve();
}
});
return done.promise;
};
async execAndWaitForLineSilent (
commandStringArg: string,
regexArg: RegExp
) {
this.execAndWaitForLine(commandStringArg, regexArg, true);
};
} }

View File

@ -1,4 +1,5 @@
import * as plugins from "./smartshell.plugins"; import * as plugins from "./smartshell.plugins";
import { ShellLog } from "./smartshell.classes.shelllog";
// interfaces // interfaces
import * as cp from "child_process"; import * as cp from "child_process";
@ -20,174 +21,7 @@ export interface IExecResultStreaming {
finalPromise: Promise<IExecResult>; finalPromise: Promise<IExecResult>;
} }
/**
* imports path into the shell from env if available and returns it with
*/
let importEnvVarPath = (stringArg): string => {
if (process.env.SMARTSHELL_PATH) {
let commandResult = `PATH=${process.env.SMARTSHELL_PATH} && ${stringArg}`;
// console.log(commandResult)
return commandResult;
} else {
return stringArg;
}
};
/**
* executes a given command async
* @param commandStringArg
*/
export let exec = (
commandStringArg: string,
silentArg: boolean = false,
strictArg = false
): Promise<IExecResult> => {
let done = plugins.smartpromise.defer<IExecResult>();
const commandToExecute = importEnvVarPath(commandStringArg);
try {
const execChildProcess = cp.exec(commandToExecute, {
timeout: null,
maxBuffer: 1000000,
env: process.env
});
let logStore = "";
execChildProcess.stdout.on("data", (data: string) => {
if (!silentArg) {
console.log(data);
}
logStore += data;
});
execChildProcess.stderr.on("data", data => {
if (!silentArg) {
console.log(data);
}
logStore += data;
});
execChildProcess.on("exit", (code, signal) => {
done.resolve({
exitCode: code,
stdout: logStore
});
});
} catch (e) {
const error = e;
}
/*plugins.shelljs.exec(importEnvVarPath(commandStringArg), { async: true, silent: silentArg }, (code, stdout, stderr) => {
if (
stderr
&& (stderr !== '')
&& (!silentArg || strictArg)
&& (process.env.DEBUG === 'true')
) {
console.log('StdErr found.')
console.log(stderr)
}
if (strictArg) {
done.reject(new Error(stderr))
return
}
done.resolve({
exitCode: code,
stdout: stdout
})
})*/
return done.promise;
};
/**
* executes a given command async and silent
* @param commandStringArg
*/
export let execSilent = async (
commandStringArg: string
): Promise<IExecResult> => {
return await exec(commandStringArg, true);
};
/**
* executes strict, meaning it rejects the promise if something happens
*/
export let execStrict = async (
commandStringArg: string
): Promise<IExecResult> => {
return await exec(commandStringArg, true, true);
};
/**
* executes a command and allws you to stream output
*/
export let execStreaming = (
commandStringArg: string,
silentArg: boolean = false
) => {
let childProcessEnded = plugins.smartpromise.defer<IExecResult>();
const commandToExecute = importEnvVarPath(commandStringArg);
let execChildProcess = cp.exec(commandToExecute, {
timeout: null,
maxBuffer: 1000000,
env: process.env
});
let logStore = "";
execChildProcess.stdout.on("data", (data: string) => {
if (!silentArg) {
console.log(data);
}
logStore += data;
});
execChildProcess.stderr.on("data", data => {
if (!silentArg) {
console.log(data);
}
logStore += data;
});
execChildProcess.on("exit", (code, signal) => {
childProcessEnded.resolve({
exitCode: code,
stdout: logStore
});
});
return {
childProcess: execChildProcess,
finalPromise: childProcessEnded.promise
};
};
export let execStreamingSilent = (commandStringArg: string) => {
return execStreaming(commandStringArg, true);
};
/**
* executes a command and returns promise that will be fullfilled once an putput line matches RegexArg
* @param commandStringArg
* @param regexArg
*/
export let execAndWaitForLine = (
commandStringArg: string,
regexArg: RegExp,
silentArg: boolean = false
) => {
let done = plugins.smartpromise.defer();
let execStreamingResult = execStreaming(commandStringArg, silentArg);
execStreamingResult.childProcess.stdout.on("data", (stdOutChunk: string) => {
if (regexArg.test(stdOutChunk)) {
done.resolve();
}
});
return done.promise;
};
export let execAndWaitForLineSilent = (
commandStringArg: string,
regexArg: RegExp
) => {
execAndWaitForLine(commandStringArg, regexArg, true);
};
/** /**
* get a path * get a path
@ -202,3 +36,24 @@ export let which = (cmd: string): Promise<string> => {
}); });
return done.promise; return done.promise;
}; };
/* /////////////////////////////////////////////////////////
/**
* executes silently and returns IExecResult
* @param commandArg
async execSilent(commandArg: string) {
let execCommand = this..createExecString(commandArg);
return await smartshellWrap.execSilent(execCommand);
}
/**
* executes and returns IExecResult
* @param commandArg
async exec(commandArg: string) {
let execCommand = this.createExecString(commandArg);
return await smartshellWrap.exec(execCommand);
} */