diff --git a/.gitignore b/.gitignore index 3c3629e..c6556c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ node_modules +coverage +wixToolset.zip +output/ +test-tmp/ diff --git a/Gulpfile.js b/Gulpfile.js index a7d2b10..a5d7a39 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -1,13 +1,54 @@ +//require('babel-core/register'); var gulp = require('gulp'); var unzip = require('gulp-unzip'); var request = require('request'); var fs = require('fs'); +var mocha = require('gulp-mocha'); +var istanbul = require('gulp-istanbul'); +var isparta = require('isparta') +var tap = require('gulp-tap'); +var coveralls = require('gulp-coveralls'); +var babel = require('gulp-babel'); + gulp.task('download', function () { return request('http://wixtoolset.org/downloads/v3.11.0.129/wix311-binaries.zip').pipe(fs.createWriteStream('wixToolset.zip')); }); -gulp.task('getwix',['download'], function(){ +gulp.task('getwix',['download', 'prepublish'], function(){ return gulp.src("wixToolset.zip") .pipe(unzip()) - .pipe(gulp.dest('./lib/wixFiles')); + .pipe(gulp.dest('./lib/wixFiles')) + .pipe(gulp.dest('./test-tmp/wixFiles')); +}); +gulp.task('pre-test', function () { + return gulp.src('src/**/*.js') + // Covering files + .pipe(istanbul({Instrumenter: isparta.Instrumenter, includeUntested: true}), {read: false}) + // Force `require` to return covered files + .pipe(gulp.dest('test-tmp/')) + .pipe(istanbul.hookRequire()); +}); + +gulp.task('test', ['pre-test', 'getwix'], function () { + return gulp.src(['test/**/*.js']) + .pipe(mocha()) + // Creating the reports after tests ran + .pipe(istanbul.writeReports()) + // Enforce a coverage of at least 90% + .pipe(istanbul.enforceThresholds({ thresholds: { lines: 70 } })); +}); + +//todo use babel +gulp.task('prepublish', function(){ +gulp.src('src/**/*.js') +.pipe(babel({ + presets: ['es2015'] +})) +.pipe(gulp.dest('lib')); + +}); + +gulp.task('coveralls', ['test'], function(){ + gulp.src('coverage/**/lcov.info') + .pipe(coveralls()) }); diff --git a/README.md b/README.md index e172961..9a74f1e 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,12 @@ Default: `undefined` Sets the BUILD_VERSION environment variable to version before calling heat, candle, and light +#### suppressValidation + +Type: `bool` +Default: `false` + +If true this will supress ICE validation checks during the linking process. ## License diff --git a/package.json b/package.json index b175340..8254d5a 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,40 @@ { "name": "hydrocarbon", "description": "making windows installers great again", - "version": "0.1.2", + "version": "0.3.0", "main": "index.js", "author": "tparnell8", - "repository": "tparnell8/HydroCarbon", + "repository": "http://github.com/tparnell8/Hydrocarbon", "license": "MIT", "scripts": { - "compile": "babel src --out-dir lib", "coveralls": "cat ./coverage/lcov.info | coveralls", - "prepublish": "npm run compile", - "test": "babel-node ./node_modules/.bin/isparta cover _mocha" + "prepublish": "./node_modules/.bin/gulp getwix", + "test": "./node_modules/.bin/gulp test" }, - "dependencies": { - "underscore": "^1.8.3" + "dependencies": { + "child-process-promise": "^1.1.0", + "lodash": "^4.6.1", + "q": "^1.4.1" }, "devDependencies": { "babel-cli": "*", + "babel-core": "^6.6.5", + "babel-preset-es2015": "^6.6.0", "babel-preset-es2015-node4": "*", - "coveralls": "*", "chai": "*", - "isparta": "*", - "mocha": "*", - "sinon": "*", + "coveralls": "*", "gulp": "^3.9.1", + "gulp-babel": "^6.1.2", + "gulp-coveralls": "^0.1.4", + "gulp-istanbul": "^0.10.3", + "gulp-mocha": "^2.2.0", + "gulp-tap": "^0.1.3", "gulp-unzip": "^0.1.3", - "request": "^2.69.0" - + "isparta": "^4.0.0", + "jscover": "^1.0.0", + "mocha": "*", + "mocha-lcov-reporter": "^1.2.0", + "request": "^2.69.0", + "sinon": "*" } } diff --git a/src/CommandBuilder.js b/src/CommandBuilder.js new file mode 100644 index 0000000..49fa7d7 --- /dev/null +++ b/src/CommandBuilder.js @@ -0,0 +1,50 @@ +'use strict'; +var fs = require('fs'), + _ = require('lodash'), + path = require('path'); + +var calculateCommands = function(options){ + var commands = { + heatPath: options.heatPath || path.normalize(__dirname + "/wixFiles/heat.exe"), + lightPath: options.lightPath || path.normalize(__dirname + "/wixFiles/light.exe"), + candlePath: options.candlePath || path.normalize(__dirname + "/wixFiles/candle.exe") + } + + if(options.version){ + process.env.BUILD_VERSION = version; + } + if(options.heatCommands && _.isArray(options.heatCommands)){ + commands.heatCommands = options.heatCommands + } + else{ + if(options.heatFiles && _.isArray(options.heatFiles) && options.heatFiles.length > 0 ){ + commands.heatCommands = _.map(options.heatFiles, (file)=>`@${path.normalize(file)}`) //heat commands can be empty as heat is a harvester and thus optional + } + } + + if(options.candleCommands && _.isArray(options.candleCommands)){ + commands.candleCommands = options.candleCommands + } + else{ + if(!options.candleFiles || !_.isArray(options.candleFiles) || options.candleFiles.length < 1 ){ + throw "light files are required if light commands are not specified"; + } + commands.candleCommands = _.map(options.candleFiles, (file)=>`@${path.normalize(file)}`) + } + + if(options.lightCommands && _.isArray(options.lightCommands)){ + commands.lightCommands = options.lightCommands + } + else{ + if(!options.lightFiles || !_.isArray(options.lightFiles) || options.lightFiles.length < 1 ){ + throw "light files are required if light commands are not specified"; + } + commands.lightCommands = _.map(options.lightFiles, (file)=>`@${path.normalize(file)}`) + } + if(options.suppressValidation){ + commands.lightCommands.unshift('-sval'); + } + return commands; +}; + +module.exports = calculateCommands; diff --git a/src/exec.js b/src/exec.js index 9f5b2d5..45cdfd7 100644 --- a/src/exec.js +++ b/src/exec.js @@ -3,128 +3,43 @@ /* jshint -W097 */ 'use strict'; var fs = require('fs'), - _ = require('underscore'), + _ = require('lodash'), path = require('path'), - child_process = require('child_process'); + commandBuilder = require('./CommandBuilder.js'), + spawn = require('child-process-promise').spawn, + Q = require('q'), + processConsole = require('./processConsole.js'); -var processResults = function (stdout, stderr) { - if(stdout && _.isArray(stdout)){ - _.chain(stdout) - .filter((item)=>item && item.length > 0) - .each((item)=>console.log(item)) - .value(); - } - else if(stdout && _.isString(stdout) && stdout.length > 0){ - console.log(stdout); - } - if(stderr && _.isString(stderr) && stderr.length > 0){ - console.log(stderr); - } +var processError = function(err, cb){ + if(cb && _.isFunction(cb) && err){ + cb(err) + } + else if(err){ + throw err.message; + } +} -}; var main = function (options, callback) { - var heatFiles = options.heatFiles; - var candleFiles = options.candleFiles; - var lightFiles = options.lightFiles; - var heatCommands = options.heatCommands || null; - var candleCommands = options.candleCommands || null; - var lightCommands = options.lightCommands || null; - var heatPath = options.heatPath || __dirname + "/wixFiles/heat.exe"; - var lightPath = options.lightPath || __dirname + "/wixFiles/light.exe"; - var candlePath = options.candlePath || __dirname + "/wixFiles/candle.exe"; - var cb = callback; - var version = options.version; - if(version){ - process.env.BUILD_VERSION = version; - } - if(!heatCommands){ - if(!heatFiles || !_.isArray(heatFiles) || heatFiles.length < 1 ){ - throw "heat files are required if no commands are passed"; - } - checkFiles(heatFiles); - } + var commands = commandBuilder(options); + var heat = null; + if(commands.heatCommands){ + console.log(commands.heatPath, commands.heatCommands); + heat = spawn(commands.heatPath, commands.heatCommands) + .progress(processConsole); + } + heat = heat || Q.Promise(); - if(!candleCommands){ - - if(!candleFiles || !_.isArray(candleFiles) || candleFiles.length < 1 ){ - throw "candle files are required"; + return Q.all([heat]) + .then(()=>spawn(commands.candlePath, commands.candleCommands), (err)=>processError(err, callback)) + .progress(processConsole) + .then(()=>spawn(commands.lightPath, commands.lightCommands), (err)=>processError(err, callback)) + .progress(processConsole) + .fail((err)=>processError(err, callback)) + .then(()=>{ + if(callback){ + callback(); } - - checkFiles(candleCommands); - - } - - if(!lightCommands){ - - if(!lightFiles || !_.isArray(lightFiles) || lightFiles.length < 1 ){ - throw "light files are required"; - } - - checkFiles(lightFiles); - - } - - return child_process.execFile(path.normalize(heatPath), heatCommands? heatCommands: _.map(heatFiles, (file)=>`@${path.normalize(file)}`), (err, stdout, stderr)=>{ - processResults(stdout, stderr); - if(err){ - if(cb){ - return cb(err); - }else{ - throw err; - } - - } - - return child_process.execFile(path.normalize(candlePath), candleCommands? candleCommands: _.map(candleFiles, (file)=>`@${path.normalize(file)}`), (err, stdout, stderr)=>{ - processResults(stdout, stderr); - if(err){ - if(cb){ - return cb(err); - }else{ - throw err; - } - - } - - return child_process.execFile(path.normalize(lightPath), lightCommands? lightCommands: _.map(lightFiles, (file)=>`@${path.normalize(file)}`), (err, stdout, stderr)=>{ - processResults(stdout, stderr); - if(err){ - if(cb){ - return cb(err); - }else{ - throw err; - } - - } - if(cb){ - return cb(); - } - - }); - - }); - }); }; -var checkFiles = function(files){ - _.each(files, (file)=>{ - if(!checkFile(file)){ - throw "error finding file" + file; - } - }); -}; -var checkFile = function (file) { - if (!file || file.length < 1) { - return false; - } - try { - fs.accessSync(path.normalize(file), fs.R_OK); //will error if doesnt exist - //todo async? - return true; - } catch (error) { - return false; - } -}; - module.exports = main; diff --git a/src/processConsole.js b/src/processConsole.js new file mode 100644 index 0000000..3c37f65 --- /dev/null +++ b/src/processConsole.js @@ -0,0 +1,11 @@ +'use strict' + +module.exports = function processConsole(childProcess) { + if(childProcess && childProcess.stdout){ + childProcess.stdout.on('data', (data)=>console.log(data.toString())); + } + if(childProcess && childProcess.stderr){ + childProcess.stderr.on('data', (data)=>console.log(data.toString())); + } + +}; diff --git a/test/integration/Product.wsx b/test/integration/Product.wsx new file mode 100644 index 0000000..a761c2b --- /dev/null +++ b/test/integration/Product.wsx @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/integration/candle.rsp b/test/integration/candle.rsp new file mode 100644 index 0000000..c859a5e --- /dev/null +++ b/test/integration/candle.rsp @@ -0,0 +1,5 @@ +-dSourceDir=test +-nologo +-out output\test\installers\ +test\integration\Product.wsx +output\test\installers\testfiles.gen.wxs diff --git a/test/integration/heat.rsp b/test/integration/heat.rsp new file mode 100644 index 0000000..caa141b --- /dev/null +++ b/test/integration/heat.rsp @@ -0,0 +1,7 @@ +dir test +-nologo +-cg files +-gg -scom -sreg -sfrag -srd +-dr INSTALLFOLDER +-out output\test\installers\testfiles.gen.wxs +-var var.SourceDir diff --git a/test/integration/hydroexec.js b/test/integration/hydroexec.js new file mode 100644 index 0000000..96246ed --- /dev/null +++ b/test/integration/hydroexec.js @@ -0,0 +1,14 @@ +var assert = require('chai').assert; +var expect = require('chai').expect; +var hydroexec = require('../../test-tmp/exec'); +describe('wix', function(){ + + it('creates an msi', function(cb){ + this.timeout(1000000); + return hydroexec({ + heatFiles: ['test/integration/heat.rsp'], + candleFiles: ['test/integration/candle.rsp'], + lightFiles: ['test/integration/light.rsp'] + }, cb); + }) +}); diff --git a/test/integration/light.rsp b/test/integration/light.rsp new file mode 100644 index 0000000..272b779 --- /dev/null +++ b/test/integration/light.rsp @@ -0,0 +1,8 @@ +-dSourceDir=test +output\test\installers\testfiles.gen.wixobj +output\test\installers\Product.wixobj +-out output\test\installers\Web.msi +-nologo +-sw1076 +-sice:ICE80 +-sice:ICE18 diff --git a/test/mocha.opts b/test/mocha.opts index 916505e..5d4ff46 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,3 +1,3 @@ -./test/unit/**/*.test.js +./test/unit/*.test.js --reporter spec --recursive diff --git a/test/unit/CommandBuilder.test.js b/test/unit/CommandBuilder.test.js new file mode 100644 index 0000000..f5f5b4c --- /dev/null +++ b/test/unit/CommandBuilder.test.js @@ -0,0 +1,119 @@ +var assert = require('chai').assert; +var expect = require('chai').expect; + + + +describe('CommandBuilderWorks', function(){ + var commandBuilder = require('../../test-tmp/CommandBuilder'); + it('Should not throw when files are passed in', function(){ + var testObject = { + heatFiles: ['tst'], + candleFiles: ['awesome'], + lightFiles: ['filesss'] + }; + commandBuilder(testObject) + + }); + it('Should not throw when commands are passed in', function(){ + var testObject = { + heatCommands: ['tst'], + candleCommands: ['awesome'], + lightCommands: ['filesss'] + }; + commandBuilder(testObject) + + }); + + it('Should Throw if missing light files', function(){ + var testObject = { + heatFiles: ['tst'], + candleFiles: ['awesome'] + }; + assert.throws(()=>commandBuilder(testObject)); + + }); + it('Should Throw if missing candle files', function(){ + var testObject = { + heatFiles: ['tst'], + lightFiles: ['awesome'] + }; + assert.throws(()=>commandBuilder(testObject)); + + }); + it('Should not Throw if missing heat files or commands', function(){ + var testObject = { + lightFiles: ['awesome'], + candleFiles: ['awesome'] + }; + assert.doesNotThrow(()=>commandBuilder(testObject)); + + }); + + it('should run as expected with files', function(){ + var testObject = { + lightFiles: ['lightfile'], + candleFiles: ['candlefile'], + heatFiles: ['heatfile'] + }; + var result = commandBuilder(testObject); + expect(result.heatCommands).to.eql(['@heatfile']); + expect(result.lightCommands).to.eql(['@lightfile']); + expect(result.candleCommands).to.eql(['@candlefile']); + }); + + it('should run as expected with commands', function(){ + var testObject = { + lightCommands: ['lightfile'], + candleCommands: ['candlefile'], + heatCommands: ['heatfile'] + }; + var result = commandBuilder(testObject); + expect(result.heatCommands).to.eql(['heatfile']); + expect(result.lightCommands).to.eql(['lightfile']); + expect(result.candleCommands).to.eql(['candlefile']); + }); + + it('should use alternate heat location', function(){ + var testObject = { + lightCommands: ['lightfile'], + candleCommands: ['candlefile'], + heatCommands: ['heatfile'], + heatPath: "../awesome" + }; + var result = commandBuilder(testObject); + expect(result.heatPath).to.eql("../awesome"); + }); + + it('should use alternate candle location', function(){ + var testObject = { + lightCommands: ['lightfile'], + candleCommands: ['candlefile'], + heatCommands: ['heatfile'], + candlePath: "../awesome" + }; + var result = commandBuilder(testObject); + expect(result.candlePath).to.eql("../awesome"); + }); + + it('should use alternate light location', function(){ + var testObject = { + lightCommands: ['lightfile'], + candleCommands: ['candlefile'], + heatCommands: ['heatfile'], + lightPath: "../awesome" + }; + var result = commandBuilder(testObject); + expect(result.lightPath).to.eql("../awesome"); + }); + it('should suppress validations', function(){ + var testObject = { + lightCommands: ['lightfile'], + candleCommands: ['candlefile'], + heatCommands: ['heatfile'], + suppressValidation: true + }; + var result = commandBuilder(testObject); + expect(result.lightCommands).to.eql(['-sval', 'lightfile']); + }); + +}); diff --git a/test/unit/index.test.js b/test/unit/index.test.js deleted file mode 100644 index e69fded..0000000 --- a/test/unit/index.test.js +++ /dev/null @@ -1 +0,0 @@ -import { assert } from 'chai';