4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,5 @@
|
||||
node_modules
|
||||
coverage
|
||||
wixToolset.zip
|
||||
output/
|
||||
test-tmp/
|
||||
|
||||
45
Gulpfile.js
45
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())
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
35
package.json
35
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": "*"
|
||||
}
|
||||
}
|
||||
|
||||
50
src/CommandBuilder.js
Normal file
50
src/CommandBuilder.js
Normal file
@@ -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;
|
||||
145
src/exec.js
145
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;
|
||||
|
||||
11
src/processConsole.js
Normal file
11
src/processConsole.js
Normal file
@@ -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()));
|
||||
}
|
||||
|
||||
};
|
||||
54
test/integration/Product.wsx
Normal file
54
test/integration/Product.wsx
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension">
|
||||
<Product Id="*"
|
||||
Name="Tommy"
|
||||
Language="1033"
|
||||
Version="1.0.0"
|
||||
Manufacturer="Tommy"
|
||||
UpgradeCode="{aba1c34e-39c6-47cf-b50a-cae4e77f8204}">
|
||||
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
|
||||
|
||||
<Media Id="1" Cabinet="product.cab" EmbedCab="yes" />
|
||||
|
||||
<!-- Allows our MSI to automatically uninstall any previously installed versions (makes it play nicer with puppet) -->
|
||||
<Property Id="PREVIOUSVERSIONSINSTALLED" Secure="yes" />
|
||||
<!-- Upgrade id has to match our upgrade code -->
|
||||
<Upgrade Id="{aba1c34e-39c6-47cf-b50a-cae4e77f8204}">
|
||||
<UpgradeVersion
|
||||
Minimum="0.0.0.1" Maximum="99.0.0.0"
|
||||
Property="PREVIOUSVERSIONINSTALLED"
|
||||
IncludeMinimum="yes" IncludeMaximum="no" />
|
||||
</Upgrade>
|
||||
<!--
|
||||
We need to be able to uninstall a newer version from an older version.
|
||||
The default reinstallmode is "omus", of which the 'o' means "reinstall if missing or older"
|
||||
The 'd' means "reinstall if different". This ensures that, at the individual component level, rollbacks work correctly.
|
||||
See http://msdn.microsoft.com/en-us/library/windows/desktop/aa371182(v=vs.85).aspx
|
||||
-->
|
||||
<Property Id="REINSTALLMODE" Value="dmus" />
|
||||
|
||||
<Feature Id="TommysFiles" Title="TommysFiles">
|
||||
<ComponentGroupRef Id="files" />
|
||||
<ComponentRef Id='main' />
|
||||
</Feature>
|
||||
</Product>
|
||||
|
||||
<Fragment>
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFilesFolder">
|
||||
<Directory Id="INSTALLFOLDER" Name="Test" />
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<Component Directory="INSTALLFOLDER" Id="main" Guid="1330738a-9a86-41ed-a0de-68c55318612a"></Component>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<InstallExecuteSequence>
|
||||
<RemoveExistingProducts After="InstallInitialize" />
|
||||
</InstallExecuteSequence>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
5
test/integration/candle.rsp
Normal file
5
test/integration/candle.rsp
Normal file
@@ -0,0 +1,5 @@
|
||||
-dSourceDir=test
|
||||
-nologo
|
||||
-out output\test\installers\
|
||||
test\integration\Product.wsx
|
||||
output\test\installers\testfiles.gen.wxs
|
||||
7
test/integration/heat.rsp
Normal file
7
test/integration/heat.rsp
Normal file
@@ -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
|
||||
14
test/integration/hydroexec.js
Normal file
14
test/integration/hydroexec.js
Normal file
@@ -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);
|
||||
})
|
||||
});
|
||||
8
test/integration/light.rsp
Normal file
8
test/integration/light.rsp
Normal file
@@ -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
|
||||
@@ -1,3 +1,3 @@
|
||||
./test/unit/**/*.test.js
|
||||
./test/unit/*.test.js
|
||||
--reporter spec
|
||||
--recursive
|
||||
|
||||
119
test/unit/CommandBuilder.test.js
Normal file
119
test/unit/CommandBuilder.test.js
Normal file
@@ -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']);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
import { assert } from 'chai';
|
||||
Reference in New Issue
Block a user