peasant
Version:
Opinionated ES6 module build tool helper
353 lines (290 loc) • 8.31 kB
JavaScript
;
var chalk = require('chalk');
var fs = require('fs');
var glob = require('glob');
var path = require('path');
var LINT_CONFIG = '// Generated by Peasant\n{\n "extends": "airbnb/base",\n "env": {\n "mocha": true\n }\n}\n';
var BABEL_CONFIG = '// Generated by Peasant\n{\n "presets": ["env"]\n}\n';
/*
* Print out a message with our icon and your message.
*/
function log(message) {
console.log(chalk.magenta('🔎 ' + message));
}
/*
* Retruns true if the source is newer than the destination filename.
*/
function newer(source, dest) {
try {
var sTime = fs.statSync(source).mtime;
var dTime = fs.statSync(dest).mtime;
return sTime > dTime;
} catch (err) {
return true;
}
}
/*
* Initialize a repository to use peasant.
*/
function init(done) {
var exec = require('child_process').exec;
var pkgPath = path.join(process.cwd(), 'package.json');
log('Installing dependencies...');
exec('npm install --save-dev peasant', function(err, stdout, stderr) {
if (stdout) console.log(stdout);
if (stderr) console.error(stderr);
if (err) {
return done(err);
}
log('Modifying package.json scripts...');
var pkg = require(pkgPath);
if (pkg.scripts === undefined) {
pkg.scripts = {};
}
pkg.scripts.lint = 'peasant lint'
pkg.scripts.test = 'peasant test'
pkg.scripts.build = 'peasant -s build'
pkg.scripts.web = 'peasant -w build'
pkg.scripts.cover = 'peasant cover'
pkg.scripts.peasant = 'peasant'
pkg.scripts.ci = 'peasant -s lint test'
pkg.scripts.prepublish = 'npm run ci && npm run build && npm run web'
fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8', function (err) {
if (err) {
return done(err);
}
log('Writing linter configuration...');
fs.writeFile(path.join(process.cwd(), '.eslintrc'), LINT_CONFIG, 'utf8', function (err) {
if (err) {
return done(err);
}
log('Creating babel configuration...');
fs.writeFile(path.join(process.cwd(), '.babelrc'), BABEL_CONFIG, 'utf8', function (err) {
if (err) {
return done(err);
}
log('Creating linter links...');
link(done);
});
});
});
});
}
/*
* Create symlinks to make lint plugins, configs, and text editors work.
*/
function link(done) {
var base = path.join(process.cwd(), 'node_modules');
var links = [
path.join('.bin', 'eslint'),
'eslint',
'babel-preset-env',
'babel-eslint',
'eslint-config-airbnb',
'eslint-plugin-import',
'eslint-plugin-react',
'eslint-plugin-jsx-a11y',
];
for (var i = 0; i < links.length; i++) {
var target = path.join(base, 'peasant', 'node_modules', links[i]);
var filename = path.join(base, links[i]);
var exists = true;
try {
var stat = fs.lstatSync(filename)
} catch (err) {
exists = false;
}
if (!exists) {
//console.log(path.join('node_modules', links[i]));
fs.symlinkSync(target, filename);
}
}
done();
}
/*
* Lint all sources against the Airbnb style guide.
*/
function lint(done) {
log('Linting sources...');
link(function (err) {
if (err) {
return done(err);
}
var eslint = require('eslint');
var linter = new eslint.CLIEngine({
cache: true
});
var results = linter.executeOnFiles(glob.sync('{bin,src,test}/**/*.{js,es6}'));
var output = linter.getFormatter('stylish')(results.results)
if (output) {
console.log(output);
}
setTimeout(function () {
done(results.errorCount > 0);
}, 0);
});
}
/*
* Generate transpiled sources for deployment.
*/
function build(options, done) {
log('Building...');
if (options.web) {
return web(options, done);
}
var babel = require('babel-core');
var mkdirp = require('mkdirp').sync;
var path = require('path');
var babelOpts = {
ast: false
};
if (options.sourcemaps) {
babelOpts.sourceMaps = true;
}
glob.sync('src/**/*.{js,es6}').forEach(function(filename) {
var newname = filename.replace(/^src/, 'lib').replace(/es6$/, 'js');
var result;
if (newer(filename, newname)) {
console.log(filename + ' -> ' + newname);
result = babel.transformFileSync(filename, babelOpts);
mkdirp(path.dirname(newname));
if (options.sourcemaps) {
var sourcemap = newname.replace(/js$/, 'map');
result.code += '\n//# sourceMappingURL=' + path.basename(sourcemap);
fs.writeFileSync(sourcemap, JSON.stringify(result.map), 'utf8');
}
fs.writeFileSync(newname, result.code, 'utf8');
}
});
done();
}
/*
* Generate transpiled sources for web deployment.
*/
function web(options, done) {
var mkdirp = require('mkdirp').sync;
var webpack = require('webpack');
var Uglify = require('uglifyjs-webpack-plugin');
var library = JSON.parse(fs.readFileSync('package.json', 'utf8'));
mkdirp(path.resolve('./lib'));
webpack({
entry: path.resolve(library.module),
devtool: 'source-map',
output: {
path: path.resolve('./lib'),
filename: library.name + '.min.js',
library: library.name,
libraryTarget: 'umd',
umdNamedDefine: true
},
module: {
loaders: [
{
test: /(\.jsx|\.js)$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/
}
]
},
plugins: [
new Uglify()
],
resolve: {
modules: [path.resolve('./src'), 'node_modules'],
extensions: ['.js']
}
}, function (err, stats) {
if (err) console.log(err);
done();
});
}
/*
* Run mocha tests on all code. Supports ES6, so your tests should just
* import directly from the `src` directory instead of the transpiled `lib`.
*/
function test(options, done) {
log('Running tests...');
try {
require('babel-core/register');
} catch (err) {
// Babel may have already been imported...
}
if (options.reporter === undefined) {
options.reporter = require('mocha-better-spec-reporter')
}
var Mocha = require('mocha');
var mocha = new Mocha(options);
glob.sync('test/**/*.{js,es6}').forEach(function(filename) {
//console.log('Adding ' + filename);
mocha.addFile(filename);
});
mocha.run(done);
}
/*
* Write test coverage reports.
*/
function cover(done) {
var Module = require('module');
var path = require('path');
var isparta = require('isparta');
// Preload require hook before we override it with the transformer below.
require('babel-core/register');
var reporter = new isparta.Reporter();
reporter.add('text-summary');
reporter.add('lcov');
isparta.matcherFor({
root: process.cwd(),
includes: ['src/**/*.{js,es6}']
}, function(err, matchFn) {
if (err) {
return done(err);
}
try {
var coverageVar = '$$coverage$$';
var instrumenter = new isparta.Instrumenter({
coverageVariable: coverageVar
});
var transformer = function(code, fileName) {
// Comment out shebang line if it exists.
if (code[0] === '#') {
code = '//' + code.substr(2);
}
return instrumenter.instrumentSync(code, fileName);
}
isparta.hook.hookRequire(matchFn, transformer);
global[coverageVar] = {};
process.once('exit', function(code) {
if (code) {
process.exit(code);
}
if (Object.keys(global[coverageVar]).length === 0) {
console.log('No coverage information collected.');
return;
}
log('Writing coverage report...');
var collector = new isparta.Collector();
collector.add(global[coverageVar]);
reporter.write(collector, true, function (err) {
if (err) {
console.log(err);
process.exit(1);
}
});
});
var peasant = path.join(__dirname, 'bin', 'peasant.js');
process.argv = ['node', peasant, 'test'];
process.env.running_under_istanbul = true;
test({reporter: 'dot'}, done);
} catch (err) {
done(err);
}
});
}
module.exports = {
init: init,
link: link,
lint: lint,
build: build,
test: test,
cover: cover
}