Created
May 26, 2016 15:24
-
-
Save MattMcFarland/e34c357cbf8c21d147b5d08768bf4efa to your computer and use it in GitHub Desktop.
node js watch, run mocha on __tests__, run eslint on files, syntax checking
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sane from 'sane'; | |
import { resolve as resolvePath } from 'path'; | |
import { spawn, fork } from 'child_process'; | |
import { existsSync as fileExists} from 'fs'; | |
import { | |
red, green, yellow, blue, | |
magenta, cyan, white, gray | |
} from 'chalk'; | |
process.env.PATH += ':./node_modules/.bin'; | |
const cmd = resolvePath(__dirname); | |
const srcDir = cmd; | |
function exec(command, options) { | |
return new Promise(function (resolve, reject) { | |
var child = spawn(command, options, { | |
cmd, | |
env: process.env, | |
stdio: 'inherit' | |
}); | |
// Checking steps | |
child.on('exit', function (code) { | |
if (code === 0) { | |
resolve(true); | |
} else { | |
reject(new Error('Error code: ' + code)); | |
} | |
}); | |
}); | |
} | |
var watcher = sane(cmd, { glob: ['src/**/*.*'] }) | |
.on('ready', startWatch) | |
.on('add', changeFile) | |
.on('delete', deleteFile) | |
.on('change', changeFile); | |
process.on('exit', function () { | |
watcher.close(); | |
}); | |
process.on('SIGINT', function () { | |
console.log(' ', CLEARLINE + yellow(invert('Watch Ended'))); | |
watcher.close(); | |
}); | |
process.on('uncaughtException', (err) => { | |
console.log(' ', CLEARLINE + red(invert(`Caught exception: ${err}`))); | |
watcher.close(); | |
process.exit(1); | |
}); | |
function startWatch() { | |
process.stdout.write( | |
green(invert('Watching...') | |
+ '\n') | |
); | |
} | |
var isChecking; | |
var needsCheck; | |
var toCheck = {}; | |
var timeout; | |
var resetServer = false; | |
var shouldCopy = false; | |
var haltOperation = false; | |
var shouldUpdateSchema = false; | |
function changeFile(filepath, root, stat) { | |
if (!stat.isDirectory()) { | |
toCheck[filepath] = true; | |
debouncedCheck(); | |
} | |
} | |
// logging | |
function logTask(str) { | |
console.log('\n', yellow('=========='), str, '\n'); | |
} | |
function logError(msg) { | |
haltOperation = true; | |
console.log(yellow('\n An exception has been caught!\n')); | |
console.log(red('\t', msg)); | |
} | |
function logWaiting() { | |
console.log('\n', green(invert('Waiting...'))); | |
} | |
function deleteFile(filepath) { | |
delete toCheck[filepath]; | |
debouncedCheck(); | |
} | |
function debouncedCheck() { | |
haltOperation = false; | |
needsCheck = true; | |
clearTimeout(timeout); | |
timeout = setTimeout(guardedCheck, 250); | |
} | |
function guardedCheck() { | |
if (isChecking || !needsCheck) { | |
return; | |
} | |
isChecking = true; | |
var filepaths = Object.keys(toCheck); | |
toCheck = {}; | |
needsCheck = false; | |
checkFiles(filepaths).then(() => { | |
isChecking = false; | |
process.nextTick(guardedCheck); | |
}); | |
} | |
function checkFiles(filepaths) { | |
console.log('\u001b[2J'); | |
return parseFiles(filepaths) | |
.then(() => runTests(filepaths)) | |
.then(testSuccess => lintFiles(filepaths) | |
.then(lintSuccess => | |
testSuccess && lintSuccess)).catch(err => logError(err)) | |
.then(() => logWaiting()) | |
.catch(err => logError(err)); | |
} | |
function parseFiles(filepaths) { | |
if (haltOperation) { | |
filepaths = [] | |
} | |
logTask('Checking Syntax'); | |
return Promise.all(filepaths.map(filepath => { | |
if (isJS(filepath) && !isTest(filepath)) { | |
return exec('babel', [ | |
'--optional', 'runtime', | |
'--out-file', '/dev/null', | |
srcPath(filepath) | |
]); | |
} | |
})); | |
} | |
function runTests(filepaths) { | |
if (haltOperation) { | |
filepaths = []; | |
} | |
logTask('Running Tests'); | |
return exec('mocha', [ | |
'--require', 'scripts/mocha-bootload' | |
].concat( | |
allTests(filepaths) ? | |
filepaths.map(srcPath) : | |
[ | |
'src/**/__tests__/**/*.js' | |
] | |
)).catch(() => logError('Unit tests failed')); | |
} | |
function lintFiles(filepaths) { | |
if (haltOperation) { | |
filepaths = []; | |
} | |
logTask('Linting Code'); | |
return filepaths.reduce((prev, filepath) => prev.then(prevSuccess => { | |
process.stdout.write(' ' + filepath + ' ...'); | |
return exec('eslint', [ | |
srcPath(filepath)]) | |
.catch(() => logError('Linting Code failed')) | |
.then(success => { | |
console.log(CLEARLINE + ' ' + (success ? CHECK : X) + ' ' + filepath); | |
return prevSuccess && success; | |
}); | |
}), Promise.resolve(true)); | |
} | |
// Filepath | |
function srcPath(filepath) { | |
if(~filepath.indexOf('__tests__')){ | |
return resolvePath(srcDir, filepath); | |
} | |
return getTestFilePath(filepath); | |
} | |
// Predicates | |
function isJS(filepath) { | |
return filepath.indexOf('.js') === filepath.length - 3; | |
} | |
function allTests(filepaths) { | |
return filepaths.length > 0 && filepaths.every(isTest); | |
} | |
function getTestFilePath(filepath) { | |
var parts = filepath.split('/'); | |
return resolvePath( | |
srcDir, | |
parts.slice(0, parts.length - 1).join('/'), | |
'__tests__', | |
parts[parts.length - 1].replace('.', '.test.')); | |
} | |
function isTest(filepath) { | |
return isJS(filepath) && ( ~filepath.indexOf('__tests__/') || fileExists(getTestFilePath(filepath))); | |
} | |
var CLEARSCREEN = '\u001b[2J'; | |
var CLEARLINE = '\r\x1B[K'; | |
var CHECK = green('\u2713'); | |
var X = red('\u2718'); | |
function invert(str) { | |
return `\u001b[7m ${str} \u001b[27m`; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment