Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
9ba5447e72 | |||
6cb7ab8f19 | |||
efdb80ddba | |||
db712b2223 | |||
0199160013 | |||
199fa19899 | |||
23a3230f07 | |||
ad0b7ac21c | |||
c8c0cbd47a | |||
213dac474e | |||
a4c188051a | |||
62195b63d6 | |||
16ba3803a4 | |||
613241cdcb | |||
f5df21e144 | |||
ba8c2d06d8 | |||
9bb0999f0e | |||
1beb3a0bbe | |||
c1133e9131 | |||
48dd3c3cca | |||
f0a5be41ea | |||
970d28a4ad | |||
76f2126eb3 | |||
a6f164602c | |||
f8457ad58d | |||
8ceac74a1f | |||
f63c4456bf | |||
32c33cad5b | |||
0758e58fa4 | |||
e981ca6bdd | |||
2853a48708 | |||
389f8f4a52 | |||
c1a537f052 | |||
4701341c0c | |||
d5d347721a | |||
6a047d6333 | |||
6e7b4dd18e | |||
debc5f1524 | |||
8d03561762 | |||
f516a6e753 | |||
1c4f6a8bf9 | |||
4fe3775904 | |||
dcee2d6233 | |||
39d457382f | |||
e6ed457501 | |||
0a6484762c | |||
baa3e4e6e9 | |||
0d68361381 | |||
286d80328c | |||
2a71f00ab0 | |||
80a44a43cb | |||
5ea780acda | |||
7c9cf6e70d | |||
697f789b55 | |||
f452964779 | |||
f2db8dc41f | |||
2ca593297c | |||
a63ae38437 | |||
03d442bf60 |
20
.gitignore
vendored
20
.gitignore
vendored
@ -1,4 +1,20 @@
|
||||
.nogit/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
node_modules/
|
||||
pages/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
151
.gitlab-ci.yml
151
.gitlab-ci.yml
@ -1,16 +1,16 @@
|
||||
# gitzone standard
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
# gitzone ci_default
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .npmci_cache/
|
||||
key: "$CI_BUILD_STAGE"
|
||||
- .npmci_cache/
|
||||
key: '$CI_BUILD_STAGE'
|
||||
|
||||
stages:
|
||||
- security
|
||||
- test
|
||||
- release
|
||||
- metadata
|
||||
- security
|
||||
- test
|
||||
- release
|
||||
- metadata
|
||||
|
||||
# ====================
|
||||
# security stage
|
||||
@ -18,108 +18,115 @@ stages:
|
||||
mirror:
|
||||
stage: security
|
||||
script:
|
||||
- npmci git mirror
|
||||
- npmci git mirror
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
snyk:
|
||||
auditProductionDependencies:
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
stage: security
|
||||
script:
|
||||
- npmci command npm install -g snyk
|
||||
- npmci command npm install --ignore-scripts
|
||||
- npmci command snyk test
|
||||
- npmci npm prepare
|
||||
- npmci command npm install --production --ignore-scripts
|
||||
- npmci command npm config set registry https://registry.npmjs.org
|
||||
- npmci command npm audit --audit-level=high --only=prod --production
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- docker
|
||||
allow_failure: true
|
||||
|
||||
auditDevDependencies:
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
stage: security
|
||||
script:
|
||||
- npmci npm prepare
|
||||
- npmci command npm install --ignore-scripts
|
||||
- npmci command npm config set registry https://registry.npmjs.org
|
||||
- npmci command npm audit --audit-level=high --only=dev
|
||||
tags:
|
||||
- docker
|
||||
allow_failure: true
|
||||
|
||||
# ====================
|
||||
# test stage
|
||||
# ====================
|
||||
testLEGACY:
|
||||
stage: test
|
||||
script:
|
||||
- npmci node install legacy
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
allow_failure: true
|
||||
|
||||
testLTS:
|
||||
testStable:
|
||||
stage: test
|
||||
script:
|
||||
- npmci node install lts
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
testSTABLE:
|
||||
- docker
|
||||
|
||||
testBuild:
|
||||
stage: test
|
||||
script:
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
- npmci npm prepare
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci command npm run build
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- docker
|
||||
|
||||
release:
|
||||
stage: release
|
||||
script:
|
||||
- npmci node install stable
|
||||
- npmci npm publish
|
||||
- npmci node install stable
|
||||
- npmci npm publish
|
||||
only:
|
||||
- tags
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# metadata stage
|
||||
# ====================
|
||||
codequality:
|
||||
stage: metadata
|
||||
image: docker:stable
|
||||
allow_failure: true
|
||||
services:
|
||||
- docker:stable-dind
|
||||
only:
|
||||
- tags
|
||||
script:
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- docker run
|
||||
--env SOURCE_CODE="$PWD"
|
||||
--volume "$PWD":/code
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock
|
||||
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
||||
artifacts:
|
||||
paths: [codeclimate.json]
|
||||
- npmci command npm install -g tslint typescript
|
||||
- npmci npm prepare
|
||||
- npmci npm install
|
||||
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
|
||||
tags:
|
||||
- docker
|
||||
- priv
|
||||
- lossless
|
||||
- docker
|
||||
- priv
|
||||
|
||||
trigger:
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci trigger
|
||||
- npmci trigger
|
||||
only:
|
||||
- tags
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
pages:
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci command npm install -g npmpage
|
||||
- npmci command npmpage
|
||||
- npmci node install lts
|
||||
- npmci command npm install -g @gitzone/tsdoc
|
||||
- npmci npm prepare
|
||||
- npmci npm install
|
||||
- npmci command tsdoc
|
||||
tags:
|
||||
- lossless
|
||||
- docker
|
||||
- notpriv
|
||||
only:
|
||||
@ -127,15 +134,5 @@ pages:
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
||||
allow_failure: true
|
||||
|
||||
windowsCompatibility:
|
||||
image: stefanscherer/node-windows:10-build-tools
|
||||
stage: metadata
|
||||
script:
|
||||
- npm install & npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- windows
|
||||
- public
|
||||
allow_failure: true
|
||||
|
29
.vscode/launch.json
vendored
Normal file
29
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "current file",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"${relativeFile}"
|
||||
],
|
||||
"runtimeArgs": ["-r", "@gitzone/tsrun"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"protocol": "inspector",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "test.ts",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"test/test.ts"
|
||||
],
|
||||
"runtimeArgs": ["-r", "@gitzone/tsrun"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"protocol": "inspector",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
}
|
||||
]
|
||||
}
|
26
.vscode/settings.json
vendored
Normal file
26
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"npmci": {
|
||||
"type": "object",
|
||||
"description": "settings for npmci"
|
||||
},
|
||||
"gitzone": {
|
||||
"type": "object",
|
||||
"description": "settings for gitzone",
|
||||
"properties": {
|
||||
"projectType": {
|
||||
"type": "string",
|
||||
"enum": ["website", "element", "service", "npm", "wcc"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
32
README.md
32
README.md
@ -1,32 +0,0 @@
|
||||
# smartshell
|
||||
|
||||
shell actions designed as promises
|
||||
|
||||
## Availabililty
|
||||
|
||||
[](https://www.npmjs.com/package/smartshell)
|
||||
[](https://GitLab.com/pushrocks/smartshell)
|
||||
[](https://github.com/pushrocks/smartshell)
|
||||
[](https://pushrocks.gitlab.io/smartshell/)
|
||||
|
||||
## Status for master
|
||||
|
||||
[](https://GitLab.com/pushrocks/smartshell/commits/master)
|
||||
[](https://GitLab.com/pushrocks/smartshell/commits/master)
|
||||
[](https://www.npmjs.com/package/smartshell)
|
||||
[](https://david-dm.org/pushrocks/smartshell)
|
||||
[](https://www.bithound.io/github/pushrocks/smartshell/master/dependencies/npm)
|
||||
[](https://www.bithound.io/github/pushrocks/smartshell)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](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)
|
||||
|
||||
[](https://push.rocks)
|
@ -1,32 +0,0 @@
|
||||
# smartshell
|
||||
|
||||
shell actions designed as promises
|
||||
|
||||
## Availabililty
|
||||
|
||||
[](https://www.npmjs.com/package/smartshell)
|
||||
[](https://GitLab.com/pushrocks/smartshell)
|
||||
[](https://github.com/pushrocks/smartshell)
|
||||
[](https://pushrocks.gitlab.io/smartshell/)
|
||||
|
||||
## Status for master
|
||||
|
||||
[](https://GitLab.com/pushrocks/smartshell/commits/master)
|
||||
[](https://GitLab.com/pushrocks/smartshell/commits/master)
|
||||
[](https://www.npmjs.com/package/smartshell)
|
||||
[](https://david-dm.org/pushrocks/smartshell)
|
||||
[](https://www.bithound.io/github/pushrocks/smartshell/master/dependencies/npm)
|
||||
[](https://www.bithound.io/github/pushrocks/smartshell)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](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)
|
||||
|
||||
[](https://push.rocks)
|
@ -1,8 +1,18 @@
|
||||
{
|
||||
"npmci": {
|
||||
"npmGlobalTools": [
|
||||
"npmts",
|
||||
"ts-node"
|
||||
]
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public",
|
||||
"npmRegistryUrl": "registry.npmjs.org"
|
||||
},
|
||||
"gitzone": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "pushrocks",
|
||||
"gitrepo": "smartshell",
|
||||
"shortDescription": "shell actions designed as promises",
|
||||
"npmPackagename": "@pushrocks/smartshell",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
25177
package-lock.json
generated
25177
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@pushrocks/smartshell",
|
||||
"private": false,
|
||||
"version": "1.0.20",
|
||||
"version": "2.0.28",
|
||||
"description": "shell actions designed as promises",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "(tsrun test/test.ts)",
|
||||
"build": "(npmts)"
|
||||
"test": "(tstest test/)",
|
||||
"build": "(tsbuild --web)"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -24,13 +24,35 @@
|
||||
},
|
||||
"homepage": "https://gitlab.com/pushrocks/smartshell#README",
|
||||
"devDependencies": {
|
||||
"@gitzone/tsrun": "^1.1.9",
|
||||
"@pushrocks/tapbundle": "^3.0.1",
|
||||
"@types/node": "^10.5.2"
|
||||
"@gitzone/tsbuild": "^2.1.26",
|
||||
"@gitzone/tsrun": "^1.2.17",
|
||||
"@gitzone/tstest": "^1.0.54",
|
||||
"@pushrocks/tapbundle": "^3.2.14",
|
||||
"@types/node": "^16.6.1",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pushrocks/smartpromise": "^2.0.5",
|
||||
"@types/which": "^1.3.1",
|
||||
"which": "^1.3.1"
|
||||
}
|
||||
"@pushrocks/smartdelay": "^2.0.13",
|
||||
"@pushrocks/smartexit": "^1.0.19",
|
||||
"@pushrocks/smartpromise": "^3.1.6",
|
||||
"@types/which": "^2.0.1",
|
||||
"tree-kill": "^1.2.2",
|
||||
"which": "^2.0.2"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
"ts_web/**/*",
|
||||
"dist/**/*",
|
||||
"dist_*/**/*",
|
||||
"dist_ts/**/*",
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
]
|
||||
}
|
||||
|
40
readme.md
Normal file
40
readme.md
Normal file
@ -0,0 +1,40 @@
|
||||
# @pushrocks/smartshell
|
||||
shell actions designed as promises
|
||||
|
||||
## Availabililty and Links
|
||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/smartshell)
|
||||
* [gitlab.com (source)](https://gitlab.com/pushrocks/smartshell)
|
||||
* [github.com (source mirror)](https://github.com/pushrocks/smartshell)
|
||||
* [docs (typedoc)](https://pushrocks.gitlab.io/smartshell/)
|
||||
|
||||
## Status for master
|
||||
|
||||
Status Category | Status Badge
|
||||
-- | --
|
||||
GitLab Pipelines | [](https://lossless.cloud)
|
||||
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
||||
npm | [](https://lossless.cloud)
|
||||
Snyk | [](https://lossless.cloud)
|
||||
TypeScript Support | [](https://lossless.cloud)
|
||||
node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
Code Style | [](https://lossless.cloud)
|
||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
||||
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
||||
Platform support | [](https://lossless.cloud) [](https://lossless.cloud)
|
||||
|
||||
## Usage
|
||||
|
||||
Use TypeScript for best in class instellisense.
|
||||
|
||||
|
||||
## Contribution
|
||||
|
||||
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
|
||||
|
||||
For further information read the linked docs at the top of this readme.
|
||||
|
||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
||||
|
||||
[](https://maintainedby.lossless.com)
|
44
test/test.ts
44
test/test.ts
@ -1,24 +1,32 @@
|
||||
import { expect, tap } from '@pushrocks/tapbundle';
|
||||
|
||||
import * as smartshell from '../ts/index';
|
||||
import * as smartshell from '../ts';
|
||||
import * as smartpromise from '@pushrocks/smartpromise';
|
||||
|
||||
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 () => {
|
||||
let execResult = await smartshell.exec('npm -v');
|
||||
let execResult = await testSmartshell.exec('npm -v');
|
||||
expect(execResult.stdout).to.match(/[0-9\.]*/);
|
||||
});
|
||||
|
||||
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\.]*/);
|
||||
});
|
||||
|
||||
tap.test('smartshell should stream a shell execution', async () => {
|
||||
let done = smartpromise.defer();
|
||||
let execStreamingResponse = smartshell.execStreaming('npm -v');
|
||||
execStreamingResponse.childProcess.stdout.on('data', data => {
|
||||
let execStreamingResponse = await testSmartshell.execStreaming('npm -v');
|
||||
execStreamingResponse.childProcess.stdout.on('data', (data) => {
|
||||
done.resolve(data);
|
||||
});
|
||||
let data = await done.promise;
|
||||
@ -27,25 +35,23 @@ tap.test('smartshell should stream a shell execution', 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/);
|
||||
});
|
||||
|
||||
// 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);
|
||||
await testSmartshell.execAndWaitForLine('echo "5.0.4"', /5.0.4/);
|
||||
});
|
||||
|
||||
tap.test('smartshell should run async', async () => {
|
||||
return testSmartshell.execSilent('sleep 1 && npm -v').then(async execResult => {
|
||||
return testSmartshell.execSilent('sleep 1 && npm -v').then(async (execResult) => {
|
||||
console.log(execResult.stdout);
|
||||
});
|
||||
});
|
||||
|
||||
tap.start({
|
||||
throwOnError: true
|
||||
tap.test('should be able to find git', async () => {
|
||||
await testSmartshell.exec('git --version');
|
||||
});
|
||||
|
||||
tap.test('should spawn an interactive cli', async () => {
|
||||
// await testSmartshell.execInteractive('echo "hi"');
|
||||
});
|
||||
|
||||
tap.start({
|
||||
throwOnError: true,
|
||||
});
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './smartshell.wrap';
|
||||
export * from './smartshell.classes.smartshell';
|
||||
|
103
ts/smartshell.classes.shellenv.ts
Normal file
103
ts/smartshell.classes.shellenv.ts
Normal file
@ -0,0 +1,103 @@
|
||||
export type TExecutor = 'sh' | 'bash';
|
||||
|
||||
export interface IShellEnvContructorOptions {
|
||||
executor: TExecutor;
|
||||
sourceFilePaths?: string[];
|
||||
pathDirectories?: string[];
|
||||
}
|
||||
|
||||
export class ShellEnv {
|
||||
executor: TExecutor;
|
||||
sourceFileArray: string[] = [];
|
||||
pathDirArray: string[] = [];
|
||||
|
||||
/**
|
||||
* constructor for the shellenv
|
||||
*/
|
||||
constructor(optionsArg: IShellEnvContructorOptions) {
|
||||
this.executor = optionsArg.executor;
|
||||
|
||||
// add sourcefiles
|
||||
if (optionsArg.sourceFilePaths) {
|
||||
this.sourceFileArray = this.sourceFileArray.concat(optionsArg.sourceFilePaths);
|
||||
}
|
||||
|
||||
// add pathDirectories
|
||||
if (optionsArg.pathDirectories) {
|
||||
this.pathDirArray = this.pathDirArray.concat(optionsArg.pathDirectories);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* imports path into the shell from env if available and returns it with
|
||||
*/
|
||||
private _setPath(commandStringArg: string): string {
|
||||
let commandResult = commandStringArg;
|
||||
let commandPaths: string[] = [];
|
||||
commandPaths = commandPaths.concat(process.env.PATH.split(':'));
|
||||
if (process.env.SMARTSHELL_PATH) {
|
||||
commandPaths = commandPaths.concat(process.env.SMARTSHELL_PATH.split(':'));
|
||||
}
|
||||
|
||||
// lets filter for unwanted paths
|
||||
// Windows WSL
|
||||
commandPaths = commandPaths.filter((commandPathArg) => {
|
||||
const filterResult =
|
||||
!commandPathArg.startsWith('/mnt/c/') &&
|
||||
!commandPathArg.startsWith('Files/1E') &&
|
||||
!commandPathArg.includes(' ');
|
||||
if (!filterResult) {
|
||||
// console.log(`${commandPathArg} will be filtered!`);
|
||||
}
|
||||
return filterResult;
|
||||
});
|
||||
|
||||
commandResult = `PATH=${commandPaths.join(':')} && ${commandStringArg}`;
|
||||
return commandResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [];
|
||||
}
|
||||
|
||||
public createEnvExecString(commandArg: string): string {
|
||||
let commandResult = '';
|
||||
let sourceString = '';
|
||||
|
||||
// deal with sourcestring
|
||||
for (const sourceFilePath of this.sourceFileArray) {
|
||||
sourceString = sourceString + `source ${sourceFilePath} && `;
|
||||
}
|
||||
|
||||
// deal with avaiable path
|
||||
let pathString = 'PATH=$PATH';
|
||||
for (const pathDir of this.pathDirArray) {
|
||||
pathString += `:${pathDir}`;
|
||||
}
|
||||
pathString += ` && `;
|
||||
|
||||
switch (this.executor) {
|
||||
case 'bash':
|
||||
commandResult = `bash -c '${pathString}${sourceString}${commandArg}'`;
|
||||
break;
|
||||
case 'sh':
|
||||
commandResult = `${pathString}${sourceString}${commandArg}`;
|
||||
break;
|
||||
}
|
||||
commandResult = this._setPath(commandResult);
|
||||
return commandResult;
|
||||
}
|
||||
}
|
38
ts/smartshell.classes.shelllog.ts
Normal file
38
ts/smartshell.classes.shelllog.ts
Normal file
@ -0,0 +1,38 @@
|
||||
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 {
|
||||
public logStore = Buffer.from('');
|
||||
|
||||
/**
|
||||
* log data to console
|
||||
* @param dataArg
|
||||
*/
|
||||
public writeToConsole(dataArg: string | Buffer): void {
|
||||
// make sure we have the data as string
|
||||
process.stdout.write(dataArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* add data to Buffer for later consumption
|
||||
* @param dataArg
|
||||
*/
|
||||
public 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]);
|
||||
}
|
||||
|
||||
public logAndAdd(dataArg: string | Buffer): void {
|
||||
this.writeToConsole(dataArg);
|
||||
this.addToBuffer(dataArg);
|
||||
}
|
||||
}
|
@ -1,64 +1,214 @@
|
||||
// -- imports --
|
||||
import * as plugins from './smartshell.plugins';
|
||||
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 {
|
||||
executor: TExecutor;
|
||||
sourceFilePaths: string[];
|
||||
// -- interfaces --
|
||||
/**
|
||||
* interface for ExecResult
|
||||
*/
|
||||
export interface IExecResult {
|
||||
exitCode: number;
|
||||
stdout: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* interface for streaming ExecResult
|
||||
*/
|
||||
export interface IExecResultStreaming {
|
||||
childProcess: cp.ChildProcess;
|
||||
finalPromise: Promise<IExecResult>;
|
||||
/**
|
||||
* sends SIGKILL
|
||||
*/
|
||||
kill: () => void;
|
||||
/**
|
||||
* sends SIGTERM
|
||||
*/
|
||||
terminate: () => void;
|
||||
}
|
||||
|
||||
// -- SmartShell --
|
||||
export class Smartshell {
|
||||
executor: TExecutor;
|
||||
sourceFileArray: string[] = [];
|
||||
constructor(optionsArg: ISmartshellContructorOptions) {
|
||||
this.executor = optionsArg.executor;
|
||||
for (let sourceFilePath of optionsArg.sourceFilePaths) {
|
||||
this.sourceFileArray.push(sourceFilePath);
|
||||
}
|
||||
}
|
||||
public shellEnv: ShellEnv;
|
||||
public smartexit = new plugins.smartexit.SmartExit();
|
||||
|
||||
addSourceFiles(sourceFilePathsArray: string[]) {
|
||||
for (let sourceFilePath of sourceFilePathsArray) {
|
||||
this.sourceFileArray.push(sourceFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
cleanSourceFiles() {
|
||||
this.sourceFileArray = [];
|
||||
constructor(optionsArg: IShellEnvContructorOptions) {
|
||||
this.shellEnv = new ShellEnv(optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* executes silently and returns IExecResult
|
||||
* @param commandArg
|
||||
* executes a given command async
|
||||
* @param commandStringArg
|
||||
*/
|
||||
async execSilent(commandArg: string) {
|
||||
let execCommand = this.createExecString(commandArg);
|
||||
return await smartshellWrap.execSilent(execCommand);
|
||||
}
|
||||
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);
|
||||
const spawnlogInstance = new ShellLog();
|
||||
const execChildProcess = cp.spawn(commandToExecute, [], {
|
||||
shell: true,
|
||||
env: process.env,
|
||||
detached: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* executes and returns IExecResult
|
||||
* @param commandArg
|
||||
*/
|
||||
async exec(commandArg: string) {
|
||||
let execCommand = this.createExecString(commandArg);
|
||||
return await smartshellWrap.exec(execCommand);
|
||||
}
|
||||
this.smartexit.addProcess(execChildProcess);
|
||||
|
||||
/**
|
||||
* 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} && `;
|
||||
execChildProcess.stdout.on('data', (data) => {
|
||||
if (!silentArg) {
|
||||
spawnlogInstance.writeToConsole(data);
|
||||
}
|
||||
return `bash -c '${sourceString} ${commandArg}'`;
|
||||
} else {
|
||||
return commandArg;
|
||||
spawnlogInstance.addToBuffer(data);
|
||||
});
|
||||
execChildProcess.stderr.on('data', (data) => {
|
||||
if (!silentArg) {
|
||||
spawnlogInstance.writeToConsole(data);
|
||||
}
|
||||
spawnlogInstance.addToBuffer(data);
|
||||
});
|
||||
|
||||
execChildProcess.on('exit', (code, signal) => {
|
||||
this.smartexit.removeProcess(execChildProcess);
|
||||
if (strictArg && code === 1) {
|
||||
done.reject();
|
||||
}
|
||||
|
||||
const execResult = {
|
||||
exitCode: code,
|
||||
stdout: spawnlogInstance.logStore.toString(),
|
||||
};
|
||||
|
||||
if (!streamingArg) {
|
||||
done.resolve(execResult);
|
||||
}
|
||||
childProcessEnded.resolve(execResult);
|
||||
});
|
||||
|
||||
if (streamingArg) {
|
||||
done.resolve({
|
||||
childProcess: execChildProcess,
|
||||
finalPromise: childProcessEnded.promise,
|
||||
kill: () => {
|
||||
// this notation with the - kills the whole process group
|
||||
console.log(`running tree kill with SIGKILL on process ${execChildProcess.pid}`);
|
||||
plugins.treeKill(execChildProcess.pid, 'SIGKILL');
|
||||
},
|
||||
terminate: () => {
|
||||
// this notation with the - kills the whole process group
|
||||
console.log(`running tree kill with SIGTERM on process ${execChildProcess.pid}`);
|
||||
plugins.treeKill(execChildProcess.pid, 'SIGTERM');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const result = await done.promise;
|
||||
return result;
|
||||
}
|
||||
|
||||
public async exec(commandStringArg: string): Promise<IExecResult> {
|
||||
return (await this._exec(commandStringArg, false)) as IExecResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* executes a given command async and silent
|
||||
* @param commandStringArg
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public async execStrict(commandStringArg: string): Promise<IExecResult> {
|
||||
return (await this._exec(commandStringArg, false, true)) as IExecResult;
|
||||
}
|
||||
|
||||
public async execStrictSilent(commandStringArg: string): Promise<IExecResult> {
|
||||
return (await this._exec(commandStringArg, true, true)) as IExecResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* executes a command and allows you to stream output
|
||||
*/
|
||||
public async execStreaming(
|
||||
commandStringArg: string,
|
||||
silentArg: boolean = false
|
||||
): Promise<IExecResultStreaming> {
|
||||
return (await this._exec(commandStringArg, silentArg, false, true)) as IExecResultStreaming;
|
||||
}
|
||||
|
||||
public 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
|
||||
*/
|
||||
public 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;
|
||||
}
|
||||
|
||||
public async execAndWaitForLineSilent(commandStringArg: string, regexArg: RegExp) {
|
||||
this.execAndWaitForLine(commandStringArg, regexArg, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* execs an command and then enters interactive CLI
|
||||
* @param commandStringArg
|
||||
* @param regexArg
|
||||
*/
|
||||
public async execInteractive(commandStringArg: string) {
|
||||
if (process.env.CI) {
|
||||
return;
|
||||
}
|
||||
const done = plugins.smartpromise.defer();
|
||||
const shell = cp.spawn('sh', [], { stdio: 'pipe' });
|
||||
this.smartexit.addProcess(shell);
|
||||
const shellLog = new ShellLog();
|
||||
const stdInStream = process.stdin.pipe(shell.stdin);
|
||||
const stdOutStream = shell.stdout.pipe(process.stdout);
|
||||
shell.on('close', (code) => {
|
||||
console.log(`interactive shell terminated with code ${code}`);
|
||||
stdInStream.removeAllListeners();
|
||||
stdInStream.uncork();
|
||||
stdOutStream.removeAllListeners();
|
||||
stdOutStream.unpipe();
|
||||
shell.kill('SIGTERM');
|
||||
process.stdin.pause();
|
||||
done.resolve();
|
||||
});
|
||||
let commandString = commandStringArg;
|
||||
if (process.env.CI) {
|
||||
commandString += ' && exit';
|
||||
}
|
||||
commandString += '\n';
|
||||
|
||||
shell.stdin.write(commandString);
|
||||
await done.promise;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
import * as smartdelay from '@pushrocks/smartdelay';
|
||||
import * as smartexit from '@pushrocks/smartexit';
|
||||
import * as smartpromise from '@pushrocks/smartpromise';
|
||||
import * as which from 'which';
|
||||
|
||||
export { smartpromise, which };
|
||||
export { smartdelay, smartexit, smartpromise, which };
|
||||
|
||||
// third party
|
||||
import treeKill from 'tree-kill';
|
||||
|
||||
export { treeKill };
|
||||
|
@ -1,204 +0,0 @@
|
||||
import * as plugins from "./smartshell.plugins";
|
||||
|
||||
// interfaces
|
||||
import * as cp from "child_process";
|
||||
import { Deferred } from "@pushrocks/smartpromise";
|
||||
|
||||
/**
|
||||
* interface for ExecResult
|
||||
*/
|
||||
export interface IExecResult {
|
||||
exitCode: number;
|
||||
stdout: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* interface for streaming ExecResult
|
||||
*/
|
||||
export interface IExecResultStreaming {
|
||||
childProcess: cp.ChildProcess;
|
||||
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
|
||||
*/
|
||||
export let which = (cmd: string): Promise<string> => {
|
||||
let done = plugins.smartpromise.defer<string>();
|
||||
plugins.which(cmd, (err, path: string) => {
|
||||
if (err) {
|
||||
done.reject(err);
|
||||
}
|
||||
done.resolve(path);
|
||||
});
|
||||
return done.promise;
|
||||
};
|
16
tslint.json
16
tslint.json
@ -1,3 +1,17 @@
|
||||
{
|
||||
"extends": "tslint-config-standard"
|
||||
"extends": ["tslint:latest", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"ordered-imports": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"member-ordering": {
|
||||
"options":{
|
||||
"order": [
|
||||
"static-method"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
}
|
||||
|
Reference in New Issue
Block a user