diff --git a/.jshintignore b/.jshintignore index 3c3629e..33493e1 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1 +1,4 @@ node_modules +example/node_modules +example/www_static +example/www_views \ No newline at end of file diff --git a/README.md b/README.md index 806cb42..dbed0b5 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,6 @@ A Node.js library providing automatic site assets aggregation. assetAssemblerA.on('begin', function() { }); assetAssemblerA.on('end', function() { }); assetAssemblerA.on('log', function(logLevel, logMessage) { }); - assetAssemblerA.on('error', function(err) { }); assetAssemblerA.on('result', function(resultValue) { }); assetAssemblerA.on('file-download-pre', function(remoteUrl) { }); assetAssemblerA.on('file-download-post', function(localFile) { }); @@ -426,6 +425,14 @@ Add unit tests for any new or changed functionality. Lint and test your code. ## Release History +### 0.2.2 +* Fixed error to callback +* Fixed 404 error +* Removed error event +* Added log event to builder +* Added builder tests +* Made example jslint pass + ### 0.2.1 * Added example application * Fixed buid callback diff --git a/example/example.js b/example/example.js index 797a478..a135264 100644 --- a/example/example.js +++ b/example/example.js @@ -1,3 +1,4 @@ +'use strict'; var express = require('express'); var async = require('async'); @@ -25,14 +26,14 @@ var assetsConfig = { '/static/js/lib/angularjs-1.4.0-b4/angular-resource.js@https://code.angularjs.org/1.4.0-beta.4/angular-resource.js', '/static/js/lib/angularjs-1.4.0-b4/angular-touch.js@https://code.angularjs.org/1.4.0-beta.4/angular-touch.js', ], -} +}; //callback helper to return an assembler config per asset type function createBuildConfig(type) { return function(callback) { - var singleResult = 'false' != process.env.DEV_ASSETS_SINGLE_RESULT; + var singleResult = 'false' !== process.env.DEV_ASSETS_SINGLE_RESULT; console.info('Asset single page result: '+singleResult+' for: '+type); - if (type == 'css') { + if (type === 'css') { callback(null,{ linkMapping: assetsConfig.linkMapping, linkTargetSingleResult: singleResult, @@ -42,7 +43,6 @@ function createBuildConfig(type) { }); } else { callback(null, { - downloadStartDelay: 200, linkTargetSingleResult: singleResult, linkMapping: assetsConfig.linkMapping, linkTarget: '/static/js/lib/assets.js', @@ -50,7 +50,7 @@ function createBuildConfig(type) { assetType: type, }); } - } + }; } function buildAssets(server,callbackDone) { @@ -87,29 +87,29 @@ function buildAssets(server,callbackDone) { },callbackDone); } -function renderTemplate(server) { - return function (req, res, next) { +function renderTemplate() { + return function (req, res) { if (req.params.sub) { res.render('include/' + req.params.page + '/' + req.params.sub); } else { res.render('include/' + req.params.page); } - } + }; } function renderPage(server) { - return function (req, res, next) { + return function (req, res) { res.render('index', { includeCssFiles: server.get('ff_assets_css'), includeJsFiles: server.get('ff_assets_js'), }); - } + }; } function sendRedirect() { - return function (req, res, next) { + return function (req, res) { res.redirect('/example-ui'); - } + }; } diff --git a/lib/asset-assembler-config.js b/lib/asset-assembler-config.js index bdc225e..159010b 100644 --- a/lib/asset-assembler-config.js +++ b/lib/asset-assembler-config.js @@ -53,6 +53,12 @@ function fillDefaults(config,callback) { if (config.assetSeperator === undefined) { config.assetSeperator = template.assetSeperator; } + if (config.linkTargetSingleResult === undefined) { + config.linkTargetSingleResult = template.linkTargetSingleResult; + } + if (config.downloadForce === undefined) { + config.downloadForce = template.downloadForce; + } callback(null,config); } diff --git a/lib/asset-assembler.js b/lib/asset-assembler.js index 1d11b98..1f1f9b3 100644 --- a/lib/asset-assembler.js +++ b/lib/asset-assembler.js @@ -7,45 +7,61 @@ var factory = require('./factory'); var config = require('./asset-assembler-config'); -function buildEnd(builder, targetFile, resultUriList, callback) { - fs.appendFile(targetFile, builder.config.assetFooter, function (err) { +function buildEnd(assembler, targetFile, resultUriList, callback) { + fs.appendFile(targetFile, assembler.config.assetFooter, function (err) { if (err) { callback(err); } else { - var buildTime = new Date().getTime() - builder.startTime, + var buildTime = new Date().getTime() - assembler.startTime, buildResultSize = resultUriList.length, targetStats = fs.statSync(targetFile), targetSize = targetStats.size; - if (builder.config.linkTargetSingleResult) { - resultUriList = [builder.config.linkTarget]; + if (assembler.config.linkTargetSingleResult) { + resultUriList = [assembler.config.linkTarget]; } - builder.emit('file-write-post',targetFile); - builder.emit('log','debug','target size: '+targetSize); - builder.emit('log','info','build result size: '+resultUriList.length+' from: '+buildResultSize); - builder.emit('log','info','build done in: '+buildTime+' ms.'); - builder.emit('result',resultUriList); - builder.emit('end'); + assembler.emit('file-write-post',targetFile); + assembler.emit('log','debug','target file: '+targetFile); + assembler.emit('log','debug','target size: '+targetSize); + assembler.emit('log','info','build result size: '+resultUriList.length+' from: '+buildResultSize); + assembler.emit('log','info','build done in: '+buildTime+' ms.'); + assembler.emit('result',resultUriList); + assembler.emit('end'); callback(); } }); } -function downloadFile(builder, remoteUrl, localFile, callback) { - builder.emit('file-download-pre',remoteUrl); - builder.emit('log','debug','downloadFile: '+remoteUrl); +function downloadFile(assembler, remoteUrl, localFile, callback) { + assembler.emit('file-download-pre',remoteUrl); + assembler.emit('log','debug','downloadFile: '+remoteUrl); fs.ensureFile(localFile, function(err) { if (err) { callback(err); } else { var stream = new fetch.FetchStream(remoteUrl); + var streamCallbackErrorOnEnd = null; + var streamCallback = function(err) { + if (err) { + streamCallbackErrorOnEnd = err; + return; + } else { + callback(streamCallbackErrorOnEnd); + } + }; stream.on('error', function(err) { - callback(err); + callback(err); // check !! + //streamCallback(err); + }); + stream.on('meta', function(meta) { + if (meta.status !== 200) { + streamCallback(new Error('Got status: '+meta.status)); + } }); stream.on('end', function() { - builder.emit('file-download-post',remoteUrl); - callback(); + assembler.emit('file-download-post',remoteUrl); + streamCallback(); }); stream.pipe(fs.createWriteStream(localFile)); } @@ -53,42 +69,42 @@ function downloadFile(builder, remoteUrl, localFile, callback) { } -function downloadFileList(builder, downloadList, callback) { +function downloadFileList(assembler, downloadList, callback) { if (downloadList.length === 0) { callback(); return; } var download = downloadList.pop(); - downloadFile(builder, download.remoteUrl, download.localFile, function(err) { + downloadFile(assembler, download.remoteUrl, download.localFile, function(err) { if (err) { callback(err); } else { - downloadFileList(builder, downloadList, callback); + downloadFileList(assembler, downloadList, callback); } }); } -function aggregateFileList(builder, targetFile, aggregateList, readFile, callback) { +function aggregateFileList(assembler, targetFile, aggregateList, readFile, callback) { if (aggregateList.length === 0) { callback(); return; } var aggregateFile = aggregateList.pop(); - builder.emit('file-read-pre',aggregateFile); + assembler.emit('file-read-pre',aggregateFile); readFile(aggregateFile, function(err, data) { if (err) { callback(err); } else { - builder.emit('file-read-post',aggregateFile); - builder.emit('log','debug','readFile: '+aggregateFile+' size: '+data.length); + assembler.emit('file-read-post',aggregateFile); + assembler.emit('log','debug','readFile: '+aggregateFile+' size: '+data.length); if (aggregateList.length > 0) { - data = data + builder.config.assetSeperator; + data = data + assembler.config.assetSeperator; } fs.appendFile(targetFile, data, function (err) { if (err) { callback(err); } else { - aggregateFileList(builder, targetFile, aggregateList, readFile, callback); + aggregateFileList(assembler, targetFile, aggregateList, readFile, callback); } }); } @@ -114,9 +130,9 @@ function mapLocalFileSync(assembler, linkSource) { return linkSource; } -function buildAsset(builder, targetFile, readFile, callback) { - var uriList = builder.config.linkSources; - var localFileList = []; +function buildAsset(assembler, targetFile, readFile, callback) { + var uriList = assembler.config.linkSources; + var readFileList = []; var resultUriList = []; var downloadList = []; @@ -134,11 +150,11 @@ function buildAsset(builder, targetFile, readFile, callback) { } assetItem = assetItem.substring(0,remoteIndex); } - var localFile = mapLocalFileSync(builder, assetItem); + var localFile = mapLocalFileSync(assembler, assetItem); // override force on all files - if (remoteForce===undefined && builder.config.downloadForce !== undefined) { - remoteForce = builder.config.downloadForce; + if (remoteForce===undefined && assembler.config.downloadForce !== undefined) { + remoteForce = assembler.config.downloadForce; } if (remoteUrl && (remoteForce || !fs.existsSync(localFile))) { @@ -147,13 +163,16 @@ function buildAsset(builder, targetFile, readFile, callback) { localFile: localFile }); } - localFileList.push(localFile); + readFileList.push(localFile); resultUriList.push(assetItem); } downloadList.reverse(); - localFileList.reverse(); + readFileList.reverse(); - downloadFileList(builder, downloadList, function(err) { + assembler.emit('log','debug','downloadList size: '+downloadList.length); + assembler.emit('log','debug','readFileList size: '+readFileList.length); + + downloadFileList(assembler, downloadList, function(err) { if (err) { callback(err); } else { @@ -162,11 +181,11 @@ function buildAsset(builder, targetFile, readFile, callback) { // Log.warn("illegal entry: "+localFile); // continue; //} - aggregateFileList(builder, targetFile, localFileList, readFile, function(err) { + aggregateFileList(assembler, targetFile, readFileList, readFile, function(err) { if (err) { callback(err); } else { - buildEnd(builder, targetFile, resultUriList, callback); + buildEnd(assembler, targetFile, resultUriList, callback); } }); } @@ -207,20 +226,10 @@ function runAssembler(assembler, readFile, callback) { } } -function runAssemblerSafe(assembler, readFile, callback) { - runAssembler(assembler, readFile, function(err) { - // send error - if (err) { - assembler.emit('error',err); - } - // finish callback - if (callback !== undefined) { - callback(err); - } - }); -} - function AssetAssembler(config, readFile) { + if (config === undefined) { + throw new Error('no config'); + } this.config = config; this.startTime = 0; this.readFile = readFile || factory.assembler.constructor.readFile(); @@ -242,7 +251,7 @@ AssetAssembler.prototype.run = function(callback) { if (err) { callback(err); } else { - runAssemblerSafe(self, self.readFile, callback); + runAssembler(self, self.readFile, callback); } }); } diff --git a/lib/assets-builder.js b/lib/assets-builder.js index f8b4217..392fa3f 100644 --- a/lib/assets-builder.js +++ b/lib/assets-builder.js @@ -5,11 +5,13 @@ var async = require('async'); var config = require('./assets-builder-config'); function buildStepAssemblerRun(builder, buildConfig, assembler, callback) { + builder.emit('log','debug','runAssembler '+assembler.config.assetType); assembler.run(callback); } function buildStepAssemblerFill(builder, buildConfig, assembler, callback) { var assemblerFill = buildConfig.assemblerFill || builder.config.assemblerFill; + builder.emit('log','debug','assemblerFill: '+Object.keys(assemblerFill)); assemblerFill(assembler, function(err) { if (err) { callback(err); @@ -21,6 +23,7 @@ function buildStepAssemblerFill(builder, buildConfig, assembler, callback) { function buildStepAssemblerCreate(builder, buildConfig, assemblerConfig, callback) { var assemblerCreate = buildConfig.assemblerCreate || builder.config.assemblerCreate; + builder.emit('log','debug','assemblerCreate: '+Object.keys(assemblerCreate)); assemblerCreate(assemblerConfig, function(err, assembler) { if (err) { callback(err); @@ -36,7 +39,7 @@ function buildStepConfigFill(builder, buildConfig, assemblerConfig, callback) { if (assemblerConfig.assetType === undefined) { assemblerConfig.assetType = builder.currentAssetType; } - + builder.emit('log','debug','configFill: '+Object.keys(configFill)); configFill(assemblerConfig, function(err) { if (err) { callback(err); @@ -48,6 +51,7 @@ function buildStepConfigFill(builder, buildConfig, assemblerConfig, callback) { function buildStepConfigCreate(builder, buildConfig, callback) { var configCreate = buildConfig.configCreate || builder.config.configCreate; + builder.emit('log','debug','configCreate: '+Object.keys(configCreate)); configCreate(function(err, assemblerConfig) { if (err) { callback(err); @@ -71,6 +75,7 @@ AssetsBuilder.prototype.__proto__ = events.EventEmitter.prototype; AssetsBuilder.prototype.runAll = function(callback) { var self = this; + self.emit('log','debug','runAll started.'); config.checkConfig(self.config,function(err) { if (err) { callback(err); @@ -93,10 +98,11 @@ AssetsBuilder.prototype.runAll = function(callback) { AssetsBuilder.prototype.runAsset = function(name) { var self = this; + if (name === undefined) { + throw new Error('no name'); + } + self.emit('log','debug','runAsset started for: '+name); return function(callback) { - if (name === undefined) { - throw new Error('no name'); - } if (callback === undefined) { throw new Error('no callback'); } @@ -106,7 +112,7 @@ AssetsBuilder.prototype.runAsset = function(name) { } else { var buildConfig = self.config.assets[name]; if (buildConfig === undefined) { - throw new Error('no asset config found'); + callback(new Error('no asset config found')); } self.currentAssetType = name; buildStepConfigCreate(self,buildConfig,callback); diff --git a/lib/factory.js b/lib/factory.js index a74febd..9b805e7 100644 --- a/lib/factory.js +++ b/lib/factory.js @@ -118,7 +118,11 @@ var builderAssemblerCreate = { }; var builderEmptyCallback = function () { - return function () {}; + return function (object, callback) { + if (callback !== undefined) { + callback(); + } + }; }; module.exports = { diff --git a/package.json b/package.json index 99a2e98..54efb79 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { "name": "node-ff-assets", - "version": "0.2.1", + "version": "0.2.2", "description": "Site assets local/remote aggregation with minify.", "main": "lib/node-ff-assets.js", "scripts": { - "test": "node_modules/mocha/bin/mocha" + "test": "npm run-script test-clean;npm run-script test-mocha", + "test-clean": "rm test/data/* -rf", + "test-mocha": "export JUNIT_REPORT_PATH=test/data/report.xml;export JUNIT_REPORT_STACK=1;node_modules/mocha/bin/mocha --reporter mocha-jenkins-reporter" + }, "author": "Willem (http://forwardfire.net/)", "license": "BSD-2-Clause", @@ -20,6 +23,7 @@ }, "devDependencies": { "mocha": "^2.1.0", + "mocha-jenkins-reporter": "^0.1.4", "mocha-jshint": "0.0.9" } } diff --git a/test/jshint.spec.js b/test/jshint.spec.js index 67cb299..a14e254 100644 --- a/test/jshint.spec.js +++ b/test/jshint.spec.js @@ -1,3 +1,3 @@ 'use strict'; -require('mocha-jshint')(['lib']); +require('mocha-jshint')(['lib','example']); diff --git a/test/test-asset-assembler.js b/test/test-asset-assembler.js index c0d9c38..9db29a5 100644 --- a/test/test-asset-assembler.js +++ b/test/test-asset-assembler.js @@ -5,37 +5,50 @@ var fs = require('fs-extra'); var assert = require("assert"); var assets = require("../lib/node-ff-assets"); -var testDataCss = function (testName) { +function createTestAssembler(suite) { + var assetAssembler = new assets.AssetAssembler({ + linkMapping: {'/static-test/': 'test/data/'+suite.title+'/'}, + linkTarget: '/static-test/output.css', + linkSources: [ + '/static-test/input0.css', + '/static-test/input1.css', + ], + }); + assetAssembler.on ('log',function (logLevel, logMessage) { + var level = (logLevel+' ').substring(0,6); + console.log(' '+ level + ' ' + suite.title + ' ' +logMessage); + }); + return assetAssembler; +} + +function createTestData(suite) { + var testName = suite.title; var outputFile = 'test/data/'+testName+'/output.css'; var dataCss0 = 'test/data/'+testName+'/input0.css'; var dataCss1 = 'test/data/'+testName+'/input1.css'; fs.ensureFileSync(dataCss0); - fs.writeFileSync(dataCss0,''); fs.appendFile(dataCss0,'input__0'); fs.ensureFileSync(dataCss1); - fs.writeFileSync(dataCss1,''); fs.appendFile(dataCss1,'input__1'); - if (fs.existsSync(outputFile)) { - fs.writeFileSync(outputFile,''); - } return outputFile; } -var testName = ''; describe('lib/asset-assembler.js', function() { - testName = 'test-aggregate-two'; - describe(testName, function() { + describe('config-null', function() { this.timeout(10000); - var outputFile = testDataCss(testName); - var assetAssembler = new assets.AssetAssembler({ - linkMapping: {'/static-test/': 'test/data/'+testName+'/'}, - linkTarget: '/static-test/output.css', - linkSources: [ - '/static-test/input0.css', - '/static-test/input1.css', - ], - }); + try { + new assets.AssetAssembler(); + assert.ok(false); + } catch (err) { + assert.ok(err !== undefined); + } + }); + + describe('aggregate-two', function() { + this.timeout(10000); + var outputFile = createTestData(this); + var assetAssembler = createTestAssembler(this); it('run builder and check output', function (done) { assetAssembler.run(function (err) { try { @@ -54,25 +67,19 @@ describe('lib/asset-assembler.js', function() { }); }); - testName = 'test-aggregate-two-and-remote'; - describe(testName, function() { + describe('aggregate-two-and-remote', function() { this.timeout(10000); - var outputFile = testDataCss(testName); - var assetAssembler = new assets.AssetAssembler({ - linkMapping: {'/static-test/': 'test/data/'+testName+'/'}, - linkTarget: '/static-test/output.css', - linkSources: [ - '/static-test/input0.css', - '/static-test/input1.css', - '/static-test/fonts-sans-pro.css@http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700', - ], - }); + var remoteFile = 'test/data/'+this.title+'/fonts-sans-pro.css' + var outputFile = createTestData(this); + var assetAssembler = createTestAssembler(this); + assetAssembler.config.linkSources.push('/static-test/fonts-sans-pro.css@http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700'); + it('run builder and check output', function (done) { assetAssembler.run(function (err) { try { assert.ok(true); assert.equal(true, fs.existsSync(outputFile)); - assert.equal(true, fs.existsSync('test/data/'+testName+'/fonts-sans-pro.css')); + assert.equal(true, fs.existsSync(remoteFile)); var outputData = ''+fs.readFileSync(outputFile); assert.equal(true, outputData.indexOf('input__0') > 0); @@ -81,10 +88,75 @@ describe('lib/asset-assembler.js', function() { done(); } catch (err) { + console.log('sdf: '+Object.keys(err)); done(err); } }); }); }); + + describe('remote-404', function() { + this.timeout(10000); + var remoteFile = 'test/data/'+this.title+'/font-404.css'; + var outputFile = createTestData(this); + var assetAssembler = createTestAssembler(this); + assetAssembler.config.linkSources.push('/static-test/font-404.css@http://fonts.googleapis.com/404'); + + it('run builder and check output', function (done) { + assetAssembler.run(function (err) { + try { + assert.ok(err !== undefined); + assert.equal(true, err.message.indexOf('404') >= 0); + assert.equal(true, fs.existsSync(outputFile)); + assert.equal(true, fs.existsSync(remoteFile)); + + var outputData = ''+fs.readFileSync(outputFile); + assert.equal(true, outputData.length !== 0); + assert.equal(true, outputData.indexOf('input__0') === -1); + assert.equal(true, outputData.indexOf('input__1') === -1); + assert.equal(true, outputData.indexOf('Source Sans Pro') === -1); + + var remoteData = ''+fs.readFileSync(remoteFile); + assert.equal(true, remoteData.indexOf('404') > 0); + + done(); + } catch (err) { + done(err); + } + }); + }); + }); + + describe('remote-closed', function() { + this.timeout(10000); + var remoteFile = 'test/data/'+this.title+'/font-closed.css' + var outputFile = createTestData(this); + var assetAssembler = createTestAssembler(this); + assetAssembler.config.linkSources.push('/static-test/font-closed.css@http://localhost:12345/closed'); + + it('run builder and check output', function (done) { + assetAssembler.run(function (err) { + try { + assert.ok(err !== undefined); + assert.equal(true, err.message.indexOf('REFUSED') >= 0); + + assert.equal(true, fs.existsSync(outputFile)); + assert.equal(true, fs.existsSync(remoteFile)); + + var outputData = ''+fs.readFileSync(outputFile); + assert.equal(true, outputData.indexOf('input__0') === -1); + assert.equal(true, outputData.indexOf('input__1') === -1); + + var remoteData = ''+fs.readFileSync(remoteFile); + assert.equal(true, remoteData.length === 0); + + done(); + } catch (err) { + done(err); + } + }); + }); + }); + }); diff --git a/test/test-assets-builder.js b/test/test-assets-builder.js new file mode 100644 index 0000000..aa4d081 --- /dev/null +++ b/test/test-assets-builder.js @@ -0,0 +1,94 @@ +'use strict'; + +var u = require('underscore'); +var fs = require('fs-extra'); +var assert = require("assert"); +var assets = require("../lib/node-ff-assets"); + +function createTestConfig(suite,type) { + + var inputFile0 = 'test/data/'+suite.title+'/'+type+'/input0.'+type; + fs.ensureFileSync(inputFile0); + fs.appendFile(inputFile0,'\n// input0__'+suite.title+'__'+type); + var inputFile1 = 'test/data/'+suite.title+'/'+type+'/input1.'+type; + fs.ensureFileSync(inputFile1); + fs.appendFile(inputFile1,'\n// input0__'+suite.title+'__'+type); + + return function (callback) { + var linkMapping = {}; + linkMapping['/static-test/'+type+'/'] = 'test/data/'+suite.title+'/'+type+'/'; + callback(null, { + linkMapping: linkMapping, + linkTarget: '/static-test/'+type+'/output.'+type, + linkSources: [ + '/static-test/'+type+'/input0.'+type, + '/static-test/'+type+'/input1.'+type, + ], + assetType: type, + }); + } +} + +function createTestBuilder(suite) { + var testConfig = { + assemblerFill: function (assembler, callback) { + var serverResultKey = 'ff_assets_'+assembler.config.assetType; + assembler.on ('log',function (logLevel, logMessage) { + var level = (logLevel+' ').substring(0,6); + console.log(' '+ level + ' ' + suite.title + ' assembler ' +logMessage); // == -2 space from asm + }); + callback(); + }, + assets: { + js: { + configCreate: createTestConfig(suite,'js'), + }, + css: { + configCreate: createTestConfig(suite,'css'), + } + } + }; + var assetsBuilder = new assets.AssetsBuilder(testConfig); + assetsBuilder.on ('log',function (logLevel, logMessage) { + var level = (logLevel+' ').substring(0,6); + console.log(' '+ level + ' ' + suite.title + ' builder ' +logMessage); // == -1 space from asm + }); + return assetsBuilder; +} + +describe('lib/assets-builder.js', function() { + + describe('config-null', function() { + this.timeout(10000); + try { + new assets.AssetsBuilder(); + assert.ok(false); + } catch (err) { + assert.ok(err !== undefined); + } + }); + + describe('run-all', function() { + this.timeout(10000); + var assetsBuilder = createTestBuilder(this); + it('run builder and check output', function (done) { + assetsBuilder.runAll(function (err) { + if (err) { + done(err); + return; + } + var outputFileJs = 'test/data/run-all/js/output.js'; + var outputFileCss = 'test/data/run-all/css/output.css'; + try { + assert.ok(true); + assert.equal(true, fs.existsSync(outputFileJs)); + assert.equal(true, fs.existsSync(outputFileCss)); + done(); + } catch (err) { + done(err); + } + }); + }); + }); +}); +