Skip to content
This repository has been archived by the owner on Oct 19, 2022. It is now read-only.

Commit

Permalink
Merge pull request #47 from pawelgalazka/v3.2.0
Browse files Browse the repository at this point in the history
v3.2.0
  • Loading branch information
Paweł Gałązka authored Mar 22, 2017
2 parents 159a3b9 + ad44dcb commit c99cc32
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 85 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,31 @@ run given command as a child process and log the call in the output.
{
cwd: ..., // current working directory (String)
async: ... // run command asynchronously (true/false), false by default
stdio: ... // 'inherit' (default) or 'pipe' (String)
stdio: ... // 'inherit' (default), 'pipe' or 'ignore'
env: ... // environment key-value pairs (Object)
timeout: ...
}
```

*Examples:*

To get an output from `run` function we need to set `stdio` option to `pipe` otherwise
`output` will be `null`:

```javascript
run('rm -rf ./build')
run('http-server .', {async: true}).then((output) => {
const output = run('ls -la', {stdio: 'pipe'})
run('http-server .', {async: true, stdio: 'pipe'}).then((output) => {
log(output)
}).catch((error) => {
throw error
})
```

For `stdio: 'pipe'` outputs are returned but not forwarded to the parent process thus
not printed out to the terminal. For `stdio: 'inherit'` (default) outputs are passed
to the terminal, but `run` function will resolve (async) / return (sync)
`null`.

#### generate(src, dst, context)

generate a file specified by `dst` path by given template file `src` and `context` object
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "runjs",
"version": "3.1.1",
"version": "3.2.0",
"description": "Minimalistic building tool",
"keywords": [
"build",
Expand Down Expand Up @@ -35,6 +35,7 @@
"homepage": "https://github.com/pawelgalazka/runjs#readme",
"dependencies": {
"chalk": "1.1.3",
"get-parameter-names": "0.3.0",
"lodash.template": "4.4.0"
},
"devDependencies": {
Expand Down
56 changes: 16 additions & 40 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,52 +27,21 @@ export const logger = {
const loggerAlias = logger

function runSync (command, options) {
// Prepare options for execSync command (don't need async and stdio should have default value)
const execOptions = Object.assign({}, options)
delete execOptions.async
if (execOptions.stdio === 'inherit') {
delete execOptions.stdio
}

try {
const execSyncBuffer = childProcess.execSync(command, execOptions)
if (options.stdio === 'inherit') {
// execSync do handle stdio option, but when stdio=inherit, execSync returns null. We can fix that
// by not passing stdio=inherit and writing outcome separately. Thanks to this stdout will be streamed and sync
// run function will still return child process outcome.
process.stdout.write(execSyncBuffer)
// stderr is inherited by default
const buffer = childProcess.execSync(command, options)
if (buffer) {
return buffer.toString()
}
return execSyncBuffer.toString()
return buffer
} catch (error) {
throw new RunJSError(error.message)
}
}

function runAsync (command, options) {
const spawnOptions = Object.assign({}, options, {
shell: true,
stdio: 'pipe'
})
const timeout = spawnOptions.timeout
delete spawnOptions.async

return new Promise((resolve, reject) => {
const asyncProcess = childProcess.spawn(command, spawnOptions)
let output = ''

asyncProcess.stdout.on('data', (buffer) => {
output = buffer.toString()
if (options.stdio === 'inherit') {
process.stdout.write(buffer)
}
})

asyncProcess.stderr.on('data', (buffer) => {
if (options.stdio === 'inherit') {
process.stderr.write(buffer)
}
})
const asyncProcess = childProcess.spawn(command, options)
let output = null

asyncProcess.on('error', (error) => {
reject(new Error(`Failed to start command: ${command}; ${error}`))
Expand All @@ -86,11 +55,17 @@ function runAsync (command, options) {
}
})

if (timeout) {
if (options.stdio === 'pipe') {
asyncProcess.stdout.on('data', (buffer) => {
output = buffer.toString()
})
}

if (options.timeout) {
setTimeout(() => {
asyncProcess.kill()
reject(new Error(`Command timeout: ${command}`))
}, timeout)
}, options.timeout)
}
})
}
Expand All @@ -104,7 +79,8 @@ export function run (command, options = {}, logger = loggerAlias) {
cwd: options.cwd,
async: !!options.async,
stdio: options.stdio || 'inherit',
timeout: options.timeout
timeout: options.timeout,
shell: true
}

// Include in PATH node_modules bin path
Expand Down
17 changes: 12 additions & 5 deletions src/script.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'path'
import fs from 'fs'
import { RunJSError } from './common'
import getParamNames from 'get-parameter-names'

export function requirer (filePath) {
return require(path.resolve(filePath))
Expand Down Expand Up @@ -79,19 +80,25 @@ function parseArgs (args) {

export function describe (obj, logger, namespace) {
if (!namespace) {
logger.log('Available tasks:')
logger.debug('Available tasks:')
}

Object.keys(obj).forEach((key) => {
const value = obj[key]
const doc = value.doc
const nextNamespace = namespace ? `${namespace}:${key}` : key
const doc = value.doc

if (typeof value === 'function') {
let funcParams
try {
funcParams = getParamNames(value)
} catch (error) {
funcParams = []
}
const paramsDoc = funcParams.length ? `[${funcParams.join(' ')}]` : ''
logger.info('\n', nextNamespace, paramsDoc)
if (doc) {
logger.log(nextNamespace, `- ${doc}`)
} else {
logger.log(nextNamespace)
logger.log(` ${doc}`)
}
} else if (typeof value === 'object') {
describe(value, logger, nextNamespace)
Expand Down
96 changes: 75 additions & 21 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,91 @@ describe('api', () => {

describe('run()', () => {
describe('sync version', () => {
it('should execute basic shell commands', () => {
const output = api.run('echo "echo test"', {cwd: './test/sandbox'}, logger)
expect(output).toEqual('echo test\n')
expect(logger.info).toHaveBeenCalledWith('echo "echo test"')
})
describe('with stdio=pipe', () => {
it('should execute basic shell commands', () => {
const output = api.run('echo "echo test"', {stdio: 'pipe'}, logger)
expect(output).toEqual('echo test\n')
expect(logger.info).toHaveBeenCalledWith('echo "echo test"')
})

it('should throw an error if command fails', () => {
expect(() => {
api.run('node ./ghost.js', {stdio: 'pipe'}, logger)
}).toThrow(RunJSError)
it('should throw an error if command fails', () => {
expect(() => {
api.run('node ./ghost.js', {stdio: 'pipe'}, logger)
}).toThrow(RunJSError)
})

it('should have access to environment variables by default', () => {
const output = api.run('echo $RUNJS_TEST', {stdio: 'pipe'}, logger)
expect(output).toEqual('runjs test\n')
})
})

it('should have access to environment variables by default', () => {
const output = api.run('echo $RUNJS_TEST', {}, logger)
expect(output).toEqual('runjs test\n')
describe('with stdio=inherit', () => {
it('should execute basic shell commands', () => {
const output = api.run('echo "echo test"', {}, logger)
expect(output).toEqual(null)
expect(logger.info).toHaveBeenCalledWith('echo "echo test"')
})

it('should throw an error if command fails', () => {
expect(() => {
api.run('node ./ghost.js', {}, logger)
}).toThrow(RunJSError)
})

it('should have access to environment variables by default', () => {
const output = api.run('echo $RUNJS_TEST', {}, logger)
expect(output).toEqual(null)
})
})
})

describe('async version', () => {
it('should execute basic shell commands', (done) => {
api.run('echo "echo test"', {async: true}, logger).then((output) => {
expect(output).toEqual('echo test\n')
expect(logger.info).toHaveBeenCalledWith('echo "echo test"')
done()
describe('with stdio=pipe', () => {
it('should execute basic shell commands', (done) => {
api.run('echo "echo test"', {async: true, stdio: 'pipe'}, logger).then((output) => {
expect(output).toEqual('echo test\n')
expect(logger.info).toHaveBeenCalledWith('echo "echo test"')
done()
})
})

it('should have access to environment variables by default', (done) => {
api.run('echo $RUNJS_TEST', {async: true, stdio: 'pipe'}, logger).then((output) => {
expect(output).toEqual('runjs test\n')
done()
})
})

it('should reject with an error if command fails', (done) => {
api.run('node ./ghost.js', {async: true, stdio: 'pipe'}, logger).catch((error) => {
expect(error.message).toEqual('Command failed: node ./ghost.js with exit code 1')
done()
})
})
})

it('should have access to environment variables by default', (done) => {
api.run('echo $RUNJS_TEST', {async: true}, logger).then((output) => {
expect(output).toEqual('runjs test\n')
done()
describe('with stdio=inherit', () => {
it('should execute basic shell commands', (done) => {
api.run('echo "echo test"', {async: true}, logger).then((output) => {
expect(output).toEqual(null)
expect(logger.info).toHaveBeenCalledWith('echo "echo test"')
done()
})
})

it('should have access to environment variables by default', (done) => {
api.run('echo $RUNJS_TEST', {async: true}, logger).then((output) => {
expect(output).toEqual(null)
done()
})
})

it('should reject with an error if command fails', (done) => {
api.run('node ./ghost.js', {async: true}, logger).catch((error) => {
expect(error.message).toEqual('Command failed: node ./ghost.js with exit code 1')
done()
})
})
})
})
Expand Down
34 changes: 19 additions & 15 deletions test/script.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,20 +145,23 @@ describe('script', () => {

it('should log list of methods available in the object', () => {
script.describe(obj, logger)
expect(logger.log).toHaveBeenCalledTimes(3)
expect(logger.log).toHaveBeenCalledWith('Available tasks:')
expect(logger.log).toHaveBeenCalledWith('a')
expect(logger.log).toHaveBeenCalledWith('b')
expect(logger.debug).toHaveBeenCalledTimes(1)
expect(logger.info).toHaveBeenCalledTimes(2)
expect(logger.debug).toHaveBeenCalledWith('Available tasks:')
expect(logger.info).toHaveBeenCalledWith('\n', 'a', '')
expect(logger.info).toHaveBeenCalledWith('\n', 'b', '')
})

it('should log list of methods from the object with description for each one if provided', () => {
obj.a.doc = 'Description for method a'
obj.b.doc = 'Description for method b'
script.describe(obj, logger)
expect(logger.log).toHaveBeenCalledWith('Available tasks:')
expect(logger.log).toHaveBeenCalledWith('a', '- Description for method a')
expect(logger.log).toHaveBeenCalledWith('b', '- Description for method b')
expect(logger.log).toHaveBeenCalledTimes(3)
expect(logger.debug).toHaveBeenCalledWith('Available tasks:')
expect(logger.info).toHaveBeenCalledWith('\n', 'a', '')
expect(logger.log).toHaveBeenCalledWith(' Description for method a')
expect(logger.info).toHaveBeenCalledWith('\n', 'b', '')
expect(logger.log).toHaveBeenCalledWith(' Description for method b')
expect(logger.info).toHaveBeenCalledTimes(2)
})

it('should log list of name spaced / nested methods', () => {
Expand All @@ -174,13 +177,14 @@ describe('script', () => {
obj.c.e.f.doc = 'Description for method f'

script.describe(obj, logger)
expect(logger.log).toHaveBeenCalledWith('Available tasks:')
expect(logger.log).toHaveBeenCalledWith('a')
expect(logger.log).toHaveBeenCalledWith('b')
expect(logger.log).toHaveBeenCalledWith('c:d')
expect(logger.log).toHaveBeenCalledWith('c:e:f', '- Description for method f')
expect(logger.log).toHaveBeenCalledWith('c:e:g')
expect(logger.log).toHaveBeenCalledTimes(6)
expect(logger.debug).toHaveBeenCalledWith('Available tasks:')
expect(logger.info).toHaveBeenCalledWith('\n', 'a', '')
expect(logger.info).toHaveBeenCalledWith('\n', 'b', '')
expect(logger.info).toHaveBeenCalledWith('\n', 'c:d', '')
expect(logger.info).toHaveBeenCalledWith('\n', 'c:e:f', '')
expect(logger.log).toHaveBeenCalledWith(' Description for method f')
expect(logger.info).toHaveBeenCalledWith('\n', 'c:e:g', '')
expect(logger.info).toHaveBeenCalledTimes(5)
})
})

Expand Down

0 comments on commit c99cc32

Please sign in to comment.