Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract functions #22

Merged
merged 9 commits into from
Apr 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ npm-debug.log

results
coverage
.nyc_output

*.swp
.idea
55 changes: 16 additions & 39 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,35 @@
const Assert = require('assert')
const Child = require('child_process')
const FS = require('fs')
const Parallel = require('run-parallel')
const Path = require('path')
const Parallel = require('run-parallel')
const { command, project } = require('./lib')

// get details about package and environment
module.exports = (options, done) => {
if (typeof options === 'function') {
done = options
options = {}
}
module.exports = (commands, done) => {
if (isFunction(commands)) [ commands, done ] = [ {}, commands ]

Assert.equal(typeof options, 'object', 'Options must be an object')
Assert.equal(typeof done, 'function', 'Must pass in a callback function')
Assert(isObject(commands), '`commands` must be an object')
Assert(isFunction(done), 'Must pass in a callback function')

Parallel([
packageDetails,
(next) => process.nextTick(next, null, { env: process.env.NODE_ENV }),
(next) => {
const cmds = Object.assign({ node: 'node -v', npm: 'npm -v' }, options)

commandDetails(cmds, next)
}
(next) => project(Path.dirname(require.main.filename), next),
(next) => commandDetails(commands, next)
], (err, results) => {
if (err) done(err)
else done(null, Object.assign({}, ...results))
done(err, Object.assign({}, ...results))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done to get all details regardless of errors right?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually err never exists, all the tasks yield an {} when things go wrong. So, mostly this is just a simpler way to avoid adding // eslint-disable-line handle-callback-error and still get 100% coverage.

})
}

// get name and version from package
const packageDetails = (done) => {
const packagePath = Path.join(process.cwd(), 'package.json')

FS.readFile(packagePath, 'utf8', (err, data) => {
if (err) return done(err)

const { name, version } = JSON.parse(data)
const isObject = (value) => typeof value === 'object' && !Array.isArray(value)

done(null, { name, version })
})
}
const isFunction = (value) => typeof value === 'function'

// execute all passed commands and yield results
const commandDetails = (cmds, done) => {
const commandDetails = (commands, done) => {
const cmds = Object.assign({ node: 'node -v', npm: 'npm -v' }, commands)
const tasks = Object.keys(cmds)
.reduce((acc, key) => Object.assign(acc, { [key]: proc(cmds[key]) }), {})
.reduce((acc, key) => Object.assign(acc, {
[key]: (next) => command(cmds[key], next)
}), {})

Parallel(tasks, done)
}

// curry command, return function that yields error string or result
const proc = (cmd) => (done) => {
Child.exec(cmd, (err, stdout) => {
if (err) done(null, err.toString())
else done(null, stdout.replace(/\n/g, ''))
})
}
24 changes: 24 additions & 0 deletions lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { exec } = require('child_process')
const ReadPkgUp = require('read-pkg-up')

// yield name and version from closest package.json, or empty object
exports.project = (cwd, done) => {
const resolved = ({ pkg }) => {
if (!pkg) return done(null, {})

const { name, version } = pkg

done(null, { name, version })
}
const rejected = () => done(null, {})

ReadPkgUp({ cwd }).then(resolved, rejected)
}

// execute command and yield trimmed output
exports.command = (cmd, done) => {
exec(cmd, (err, stdout, stderr) => {
if (err || stderr) done(null, err ? err.toString() : stderr.trim())
else done(null, stdout ? stdout.trim() : '')
})
}
17 changes: 10 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
"Fran Varney <fran.varn@gmail.com>"
],
"dependencies": {
"read-pkg-up": "2.0.0",
"run-parallel": "1.1.6"
},
"devDependencies": {
"code": "2.0.1",
"lab": "10.9.0",
"sinon": "1.17.2",
"standard": "~10.0.2"
"code": "~4.0.0",
"mkdirp": "~0.5.1",
"standard": "~10.0.2",
"tap": "~10.3.2",
"temporary-directory": "~1.0.2",
"testdouble": "~2.1.2"
},
"engines": {
"node": "^6"
Expand All @@ -36,9 +39,9 @@
"url": "git+https://github.com/jackboberg/knock-knock.git"
},
"scripts": {
"coverage": "lab --coverage --reporter lcov --output coverage/lcov.info",
"cov": "npm run unit -- --cov --coverage-report=lcov",
"lint": "standard -v",
"posttest": "npm run lint",
"test": "lab -cvv"
"test": "npm run lint && npm run unit",
"unit": "tap test/*.js -R spec --100"
}
}
71 changes: 71 additions & 0 deletions test/command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const TD = require('testdouble')
const { EOL } = require('os')
const { exec } = TD.replace('child_process')
const { expect } = require('code')
const { test: describe } = require('tap')

const { command } = require('../lib')

describe('lib.command', ({ afterEach, beforeEach, plan, test }) => {
const cmd = 'test'
const output = `\toutput${EOL}`

plan(5)

beforeEach((done) => {
TD.when(exec(cmd)).thenCallback(null, output)
done()
})

afterEach((done) => {
TD.reset()
done()
})

test('executes the given command', (t) => {
command(cmd, () => {
const { callCount, calls } = TD.explain(exec)

expect(callCount).to.equal(1)
expect(calls[0].args).to.include(cmd)
t.end()
})
})

test('yields trimmed stdout', (t) => {
command(cmd, (err, out) => {
expect(err).to.not.exist()
expect(out).to.equal('output')
t.end()
})
})

test('yields trimmed stderr', (t) => {
TD.when(exec(cmd)).thenCallback(null, null, output)
command(cmd, (err, out) => {
expect(err).to.not.exist()
expect(out).to.equal('output')
t.end()
})
})

test('when errors yields the error string', (t) => {
const error = new Error('fail')

TD.when(exec(cmd)).thenCallback(error)
command(cmd, (err, out) => {
expect(err).to.not.exist()
expect(out).to.equal('Error: fail')
t.end()
})
})

test('whithout output yields an empty string', (t) => {
TD.when(exec(cmd)).thenCallback()
command(cmd, (err, out) => {
expect(err).to.not.exist()
expect(out).to.equal('')
t.end()
})
})
})
12 changes: 12 additions & 0 deletions test/fixtures/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@test/module",
"version": "1.2.3",
"description": "an empty test module",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Jack Boberg <hello@jackboberg.info> (http://jackboberg.info/)",
"license": "MIT"
}