From 1970369ad6b105ea999d2bcc773534c62cb7ad9a Mon Sep 17 00:00:00 2001 From: 3rd-Eden Date: Tue, 8 Jul 2014 23:10:11 +0200 Subject: [PATCH] [minor] initial release --- .gitignore | 1 + bin/illuminati | 8 ++ browserify.js | 46 ++++++++ index.html | 20 ++++ index.js | 297 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 39 +++++++ 6 files changed, 411 insertions(+) create mode 100644 .gitignore create mode 100755 bin/illuminati create mode 100644 browserify.js create mode 100644 index.html create mode 100644 index.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/bin/illuminati b/bin/illuminati new file mode 100755 index 0000000..432cf1c --- /dev/null +++ b/bin/illuminati @@ -0,0 +1,8 @@ +#!/usr/bin/env node + +'use strict'; + +var Illuminati = require('../') + , illuminati = new Illuminati(process.cwd()); + +illuminati.run(); diff --git a/browserify.js b/browserify.js new file mode 100644 index 0000000..7384fd8 --- /dev/null +++ b/browserify.js @@ -0,0 +1,46 @@ +'use strict'; + +var convert = require('convert-source-map') + , browserify = require('browserify') + , path = require('path') + , fs = require('fs'); + +/** + * Compile all the things. + * + * @param {Array} files Files to be included in the bundle. + * @param {Object} options Options for browserify. + * @param {Function} fn Completion callback. + * @api public + */ +module.exports = function compile(files, options, fn) { + if ('function' === typeof options) { + fn = options; + options = {}; + } + + var b = browserify(options); + + // + // Introduce our `assume` library by default in to the package so you only + // need Illuminati to start testing your applications and add all other files + // that are needed to test all the things. + // + b.require('assume'); + files.forEach(b.add.bind(b)); + + b.bundle({ + debug: true // Ensure that browserify is compiled with source-maps. + }, function bundled(err, source) { + if (err) return fn(err); + + // + // PhantomJS does not understand base64 encoded source maps so we have to + // convert the created sourcemap to a JSON file which we can serve from our + // server. + // + var map = convert.fromSource(source); + + fn(undefined, convert.removeComments(source), map ? map.toObject() : undefined); + }); +}; diff --git a/index.html b/index.html new file mode 100644 index 0000000..6e6595d --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + +
+ + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..9ddd34b --- /dev/null +++ b/index.js @@ -0,0 +1,297 @@ +'use strict'; + +var browserify = require('./browserify') + , minimatch = require("minimatch") + , child = require('child_process') + , dot = require('dot-component') + , fusing = require('fusing') + , path = require('path') + , fs = require('fs'); + +/** + * Illuminati: Secret society of testers. + * + * @constructor + * @param {String} dir Directory we're loaded in. + * @param {Object} options Optional options. + * @api public + */ +function Illuminati(dir, options) { + if (!(this instanceof Illuminati)) return new Illuminati(dir, options); + var self = this; + + this.fuse(); + this.root = dir; // Root directory + + if (fs.existsSync(path.join(dir, 'package.json'))) { + (function merge(data) { + Object.keys(data || {}).forEach(function each(key) { + self.conf[key] = +data[key] || data[key]; + }); + })(require(path.join(dir, 'package.json')).illuminati); + } +} + +fusing(Illuminati, require('eventemitter3')); + +/** + * Find the configuration for the Illuminati runner. We assume that the + * configuration is added in a special `illuminati` key in the users + * `package.json` which can be used to configure various of things in the + * project. This configuration will be merged with our default illuminati + * configuration from our own `package.json` file. + * + * @type {Object} + * @public + */ +Illuminati.writable('conf', require('./package.json').illuminati); + +/** + * Command line arguments. + * + * @type {Object} + * @public + */ +Illuminati.readable('argv', require('argh').argv); + +/** + * The glob pattern we use for searching valid test files. + * + * @type {String} + * @public + */ +Illuminati.readable('glob', '*.test.js'); + +/** + * The assets that need to be served. + * + * @type {Array} + * @public + */ +Illuminati.writable('assets', [ + './node_modules/mocha/mocha.js', + './node_modules/mocha/mocha.css', + './index.html' +]); + +/** + * Run the actual test suites. + * + * @api public + */ +Illuminati.readable('run', function run() { + var illuminati = this; + + /** + * The tests have run. + * + * @param {Error} err + * @api private + */ + function ran(err) { + if (err) return process.exit(1); + return process.exit(0); + } + + if (!this.argv.phantom) return this.mocha(ran); + + this.server(function listening(err) { + illuminati.phantomjs(ran); + }); +}); + +/** + * Create our HTTP server and start listening on the provide port number. + * + * @param {Function} fn Completion callback, server is listening + * @api private + */ +Illuminati.readable('server', function server(fn) { + var app = require('http').createServer(this.incoming.bind(this)) + , illuminati = this; + + this.assets = this.assets.map(this.map); + + browserify(this.files, this.conf.browserify || { + basedir: this.root + }, function (err, source, map) { + source += '//# sourceMappingURL=/illuminati.json'; + map.file = '/illuminati.js'; + + illuminati.assets.push({ + data: source, + url: '/illuminati.js', + type: 'text/javascript' + }, { + data: map, + url: '/illuminati.json', + type: 'application/json' + }); + + require('connected')(app, illuminati.conf.port, fn); + }); + + // + // We don't want our HTTP server to hold up the destruction of the world. So + // we unref it. + // + if (app.unref) app.unref(); + + return this; +}); + +/** + * Run the tests on PhantomJS. + * + * @param {Number} port The port number our server is listening on. + * @param {Function} fn Completion callback. + * @api private + */ +Illuminati.readable('phantomjs', function phantomjs(fn) { + var phantom = child.spawn( + path.join(__dirname, 'node_modules', '.bin', 'mocha-phantomjs'), [ + 'http://localhost:'+ this.conf.port + ], { + stdio: 'inherit' + }); + + phantom.on('close', function exit(code) { + if (code) { + return fn(new Error('Tests failed to run, returned exit code: '+ code)); + } + + fn(); + }); + + return this; +}); + +/** + * Run the tests against the regular mocha. + * + * @param {Function} fn Completion function. + * @api private + */ +Illuminati.readable('mocha', function mochas(fn) { + var mocha = child.spawn( + path.join(__dirname, 'node_modules', '.bin', 'mocha'), [ + '--reporter', this.conf.reporter, + '--ui', this.conf.ui + ].concat(this.files), { + stdio: 'inherit' + }); + + mocha.on('close', function exit(code) { + if (code) { + return fn(new Error('Tests failed to run, returned exit code: '+ code)); + } + + fn(); + }); + + return this; +}); + +/** + * Find the files that we need to test for. + * + * @type {Array} + * @public + */ +Illuminati.get('files', function files() { + if (this.argv.argv) return this.argv.argv; + + var illuminati = this; + + return [ + path.join(illuminati.root, 'tests'), + path.join(illuminati.root, 'test') + ].reduce(function reduce(files, dir) { + var folder; + + try { folder = fs.readdirSync(dir); } + catch (e) { return files; } + + Array.prototype.push.apply(files, folder.filter(function filter(file) { + return minimatch(file, illuminati.glob); + }).map(function map(file) { + return path.join(dir, file); + })); + + return files; + }, []); +}); + +/** + * Introduce template tags into the given template. + * + * @param {Object} data Information to merge. + * @param {String} template Template to replace + * @returns {String} The template + * @api private + */ +Illuminati.readable('introduce', function introduce(data, template) { + var key; template = template.toString(); + + while (key = /{illuminati:([^{]+?)}/gm.exec(template)) { + key = key[0]; + template = template.replace(key, dot.get(data, key.slice(12, -1))); + } + + return template; +}); + +/** + * Map assets to an object that we can serve. + * + * @param {String} file Filename/address. + * @returns {Object} Serve-able object + * @api public + */ +Illuminati.readable('map', function map(file) { + if ('string' !== typeof file) { + return file; // Already processed, do not give a fuck + } + + return { + data: fs.readFileSync(path.join(__dirname, file), 'utf-8'), + url: '/'+ path.basename(file), + type: { + js: 'text/javascript', + css: 'text/css', + html: 'text/html', + json: 'application/json' + }[path.extname(file).slice(1)] || 'text/plain' + }; +}); + +/** + * Answer HTTP requests to our incoming test server. + * + * @param {Request} req HTTP request. + * @param {Response} res HTTP response. + * @api private + */ +Illuminati.readable('incoming', function incoming(req, res) { + res.statusCode = 200; + + var illuminati = this; + + if (req.url === '/') req.url = '/index.html'; + if (illuminati.assets.some(function some(asset) { + if (asset.url !== req.url) return false; + + res.setHeader('Content-Type', asset.type); + res.end(illuminati.introduce(illuminati.conf, asset.data)); + + return true; + })) return; + + res.statusCode = 404; + res.end('404: Please read the documentation on: '+ illuminati.conf.homepage); +}); + +// +// Expose the module. +// +module.exports = Illuminati; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a55de49 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "illuminati", + "version": "0.0.0", + "description": "secret society", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "illuminati" + ], + "author": "Arnout Kazemier", + "license": "MIT", + "bin": { + "illuminati": "./bin/illuminati" + }, + "illuminati": { + "homepage": "https://github.com/illuminati", + "port": 1333, + "reporter": "spec", + "ui": "bdd" + }, + "homepage": "https://github.com/3rd-eden/illuminati", + "dependencies": { + "argh": "0.1.x", + "assume": "0.0.x", + "browserify": "4.2.x", + "connected": "0.0.x", + "convert-source-map": "0.3.x", + "dot-component": "0.1.x", + "eventemitter3": "0.1.x", + "fusing": "0.3.x", + "minimatch": "0.3.x", + "mocha": "1.20.x", + "mocha-phantomjs": "3.5.x", + "phantomjs": "1.9.x", + "pre-commit": "0.0.x" + } +}