├── .gitattributes ├── .gitignore ├── cli.js ├── index.js ├── jsconfig.json ├── package.json └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var meow = require("meow"); 4 | var run = require("./"); 5 | 6 | var cli = meow( 7 | "Here's some really unhelpful help:" + "\n" + 8 | "Params:" + "\n" + 9 | "\t" + "--webpack-config : Path to your webpack config" + "\n" + 10 | "\t" + "[--polyfill] : Path to your polyfill (e.g. babel-polyfill)" + "\n" + 11 | "\n" + 12 | "Flags:" + "\n" + 13 | "\t" + "[--tap] : Output test results in tap format (useful for chaining in a reporter e.g. tap-teamcity)" + "\n" + 14 | "\t" + "[--debug] : Enable debug mode (no flushing of temp. files and prints the webpack stats object)" 15 | ); 16 | 17 | run(cli.input[0], cli.flags, cli.showHelp); 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var webpack = require('webpack'); 4 | var glob = require('glob'); 5 | var findup = require('findup-sync'); 6 | var cuid = require('cuid'); 7 | var rimraf = require('rimraf'); 8 | var nodeExternals = require('webpack-node-externals'); 9 | var objectAssign = require('object-assign'); 10 | var Promise = require('bluebird'); 11 | 12 | var exec = require('npm-run').exec; 13 | 14 | function findWebpackConfig() { 15 | return findup("webpack.config.js"); 16 | } 17 | 18 | function getFiles(pattern) { 19 | return glob.sync(pattern, { ignore: ["node_modules/**"] }); 20 | } 21 | 22 | function getFileNameFromPath(filePath) { 23 | return filePath 24 | .replace(/\.\//g, '') 25 | .replace(/\//g, '.') 26 | .split('.') 27 | .slice(0, -1) 28 | .join('_'); 29 | } 30 | 31 | function getFileHash(files, polyfillPath) { 32 | return files.reduce(function (prev, next) { 33 | var path = "./" + next; 34 | prev[getFileNameFromPath(next)] = polyfillPath ? [ polyfillPath, path ] : path; 35 | return prev; 36 | }, {}); 37 | } 38 | 39 | function generateRunId() { 40 | return cuid(); 41 | } 42 | 43 | function getWebpackConfig(config, fileHash, path) { 44 | return objectAssign({}, config, { 45 | entry: fileHash, 46 | output: { 47 | filename: '[name].test.js', 48 | path 49 | }, 50 | target: 'node', 51 | externals: [nodeExternals()] 52 | }); 53 | } 54 | 55 | function runWebpack(config) { 56 | return new Promise(function (resolve, reject) { 57 | webpack(config).run(function (err, stats) { 58 | if(err) { 59 | reject(err); 60 | return; 61 | } 62 | resolve(stats); 63 | }); 64 | }); 65 | } 66 | 67 | function runAva(emittedFiles, tap) { 68 | return new Promise(function (resolve, reject) { 69 | exec('ava ' + (tap ? '--tap ' : '') + emittedFiles.join(' '), {}, function (err, stdout, stderr) { 70 | var output = tap ? stdout : stderr; 71 | 72 | if(err) { 73 | reject(output); 74 | return; 75 | } 76 | 77 | resolve(output); 78 | }); 79 | }); 80 | } 81 | 82 | function complete(output, isError, shouldClean) { 83 | if(shouldClean) { 84 | rimraf.sync('.ava-webpack'); 85 | } 86 | 87 | if(isError) { 88 | console.log(output); 89 | process.exit(1); 90 | return; 91 | } 92 | 93 | console.log(output); 94 | process.exit(0); 95 | } 96 | 97 | function run(input, flags, showHelp) { 98 | var webpackConfigPath = flags.webpackConfig || findWebpackConfig(); 99 | var webpackConfigResolvedPath = webpackConfigPath && path.resolve(process.cwd(), webpackConfigPath); 100 | 101 | var testDiscoveryPattern = input || '**/*.test.{js,jsx,ts,tsx}'; 102 | 103 | var runId = generateRunId(); 104 | var outDir = './.ava-webpack/' + runId; 105 | 106 | var existingWebpackConfig = {}; 107 | 108 | var cleanOutput = !flags.debug || flags.clean; 109 | 110 | if(webpackConfigPath) { 111 | try { 112 | existingWebpackConfig = require(webpackConfigResolvedPath); 113 | existingWebpackConfig = existingWebpackConfig.default || existingWebpackConfig; 114 | } 115 | catch(e) { 116 | console.error("Webpack config not found. Using default."); 117 | } 118 | } 119 | 120 | var testFiles = getFiles(testDiscoveryPattern); 121 | var webpackEntries = getFileHash(testFiles, flags.polyfill); 122 | 123 | var webpackConfig = getWebpackConfig( 124 | existingWebpackConfig, 125 | webpackEntries, 126 | outDir 127 | ); 128 | 129 | runWebpack(webpackConfig).then( 130 | function(stats) { 131 | var jsonStats = stats.toJson(); 132 | 133 | if (jsonStats.errors.length > 0) { 134 | for (var i = 0; i < jsonStats.errors.length; i++) { 135 | console.error(jsonStats.errors[i]); 136 | } 137 | return complete(null, true, cleanOutput); 138 | } 139 | 140 | var emittedFiles = jsonStats.assets.map(function(asset) { 141 | return path.join(outDir, asset.name); 142 | }); 143 | 144 | if(flags.debug) { 145 | console.log(stats.toString({ colors: true })); 146 | } 147 | 148 | runAva(emittedFiles, flags.tap).then( 149 | function(res) { return complete(res, false, cleanOutput); }, 150 | function(err) { return complete(err, true, cleanOutput); } 151 | ) 152 | }, 153 | function(err) { return complete(err, true, cleanOutput); } 154 | ); 155 | } 156 | 157 | module.exports = run; 158 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es5", 6 | "module": "commonjs" 7 | }, 8 | "exclude": [ 9 | "node_modules", 10 | "bower_components", 11 | "jspm_packages", 12 | "tmp", 13 | "temp" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ava-webpack", 3 | "version": "1.1.2", 4 | "description": "Crude webpack enabled runner for AVA", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "author": { 8 | "name": "Thomas Andresen", 9 | "email": "thomas@ndrsn.no" 10 | }, 11 | "url": "https://github.com/thrandre/ava-webpack/issues", 12 | "license": "ISC", 13 | "bin": "cli.js", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/thrandre/ava-webpack.git" 17 | }, 18 | "dependencies": { 19 | "meow": "^3.7.0", 20 | "bluebird": "^3.4.3", 21 | "cuid": "^1.3.8", 22 | "findup-sync": "^0.4.2", 23 | "node-glob": "^1.2.0", 24 | "npm-run": "^4.1.0", 25 | "object-assign": "^4.1.0", 26 | "rimraf": "^2.5.4", 27 | "webpack-node-externals": "^1.3.3" 28 | }, 29 | "devDependencies": { 30 | "ava": "^0.16.0", 31 | "webpack": "> 1.13.0 <= 2.1.0-beta.21" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ava-webpack 2 | 3 | > Crude webpack enabled runner for AVA 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install ava-webpack --save-dev 9 | ``` 10 | 11 | ## Usage example (almost as crude as the implementation - sorry) 12 | 13 | *webpack.config-test.js* (using [awesome-typescript-loader](https://github.com/s-panferov/awesome-typescript-loader)) 14 | 15 | ``` 16 | var path = require('path'); 17 | 18 | module.exports = { 19 | resolve: { 20 | root: [ 21 | path.resolve(__dirname, 'apps'), 22 | path.resolve(__dirname, 'common') 23 | ], 24 | extensions: ['', '.ts', '.tsx', '.js'] 25 | }, 26 | devtool: 'eval', 27 | module: { 28 | loaders: [ 29 | { 30 | test: /\.tsx?$/, 31 | loader: 'awesome-typescript-loader' 32 | } 33 | ] 34 | } 35 | }; 36 | 37 | ``` 38 | 39 | *package.json* 40 | 41 | ``` 42 | { 43 | "dependencies": { 44 | "babel-polyfill": "^6.9.1", 45 | }, 46 | "devDependencies": { 47 | "@types/enzyme": "^2.4.30", 48 | "ava": "^0.16.0", 49 | "ava-webpack": "^1.0.6", 50 | "awesome-typescript-loader": "2.0.2", 51 | "babel-core": "^6.10.4", 52 | "babel-preset-es2015": "^6.9.0", 53 | "babel-preset-react": "^6.11.1", 54 | "enzyme": "^2.4.1", 55 | "rimraf": "^2.5.3", 56 | "tap-teamcity": "^1.2.0", 57 | "typescript": "^2.0.0", 58 | "webpack": "2.1.0-beta.20" 59 | }, 60 | "scripts": { 61 | "test": "ava-webpack --webpack-config ./webpack.config-test.js --polyfill babel-polyfill --clean", 62 | "test-ci": "ava-webpack --webpack-config ./webpack.config-test.js --polyfill babel-polyfill --clean --tap | tap-teamcity" 63 | }, 64 | "ava": { 65 | "concurrency": 5, 66 | "require": [ 67 | "babel-register" 68 | ], 69 | "babel": "inherit" 70 | } 71 | } 72 | 73 | ``` --------------------------------------------------------------------------------