From c4e1fc1d683701400491a6ccfd8ccb0514dbd74b Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Wed, 16 Dec 2015 16:54:56 +0100 Subject: [PATCH] Initial Version. Support both AfbDaemon & Backend Mock server. --- afb-client/.gitignore | 7 + afb-client/LICENSE | 17 ++ afb-client/README.md | 97 +++++++++- afb-client/bower.json | 18 ++ afb-client/gulpfile.js | 456 ++++++++++++++++++++++++++++++++++++++++++++++++ afb-client/index.html | 41 +++++ afb-client/package.json | 61 +++++++ 7 files changed, 695 insertions(+), 2 deletions(-) create mode 100644 afb-client/.gitignore create mode 100644 afb-client/LICENSE create mode 100644 afb-client/bower.json create mode 100644 afb-client/gulpfile.js create mode 100644 afb-client/index.html create mode 100644 afb-client/package.json diff --git a/afb-client/.gitignore b/afb-client/.gitignore new file mode 100644 index 0000000..bb6f2ab --- /dev/null +++ b/afb-client/.gitignore @@ -0,0 +1,7 @@ +bower_components/ +node_modules/ +dist.dev/ +dist.prod/ +*.DS_Store +nbproject/private/ +.noderc* diff --git a/afb-client/LICENSE b/afb-client/LICENSE new file mode 100644 index 0000000..89f9ced --- /dev/null +++ b/afb-client/LICENSE @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ diff --git a/afb-client/README.md b/afb-client/README.md index 11a8f45..e31390e 100644 --- a/afb-client/README.md +++ b/afb-client/README.md @@ -1,2 +1,95 @@ -# afb-client-sample -Simple HTML5 Application Framework Client base on Angular & Zurb Foundation + Gulp + +## Installation + +Install HTML5 development toolchain on your host + +1. Check out this repository + git clone https://github.com/iotbzh/afb-client-sample.git + +2) Install NodeJs [not used on target] + zypper install nodejs + yum install nodejs + +3) Install building tools [bower, gulp, ....] + npm install # this install all development tool chain dependencies + sudo npm install --global gulp # this is not mandatory but it will make your live simpler + +4. For livereload functionality [automatic refresh of HTML/CSS] + install [livereload Chrome extension](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei) + + +### Overload ./etc/Defaults.js with '.noderc.js' + var config= { + APPNAME : 'AFBclient', // AppName is use as main Angular Module name + FRONTEND: "Frontend", // HTML5 frontend [no leading ./] + BACKEND : "Backend", // NodeJS Rest API [no leading ./] + URLBASE : '/opa/', // HTML basedir when running in production [should end with a /] + APIBASE : '/api/', // Api url base dir [should end with a /] + DEBUG : 4001, // Node Debug Port [for mock API debug only] + DBG_LVL : 5, // Debug Trace Level 0=no trace. + }; + module.exports = config; + + WARNING: in current version Frontend/services/ConfigApp.js is not updated automatically + you should make sure than your backend config fit with your frontend config. + Note: FCS version should have ConfigApp.js configurated automatically from GULP, but this is for "tomorrow" + +### Build project + gulp help + gulp build-app-dev + gulp watch-dev + http://localhost:3001/opa /* debug mock api base on Backend/RestApi */ + +### Test with Application server binder [you may safely run gulp 'watch-dev' + 'afb-daemon' simultaneously] + export MYWORKSPACE=$HOME/Workspace + $MYWORKSPACE/afb-daemon/build/afb-daemon --port=1234 --verbose --token=123456789 --rootdir=$MYWORKSPACE/afb-client/dist.dev + http://localhost:1234/opa + + Note: + - do not forget "/opa" that should match with your config.URLBASE + - if you change --token=xxxx do not forget to update ./Frontend/pages/HomeModules.js + - Force HTML/OPA reload with F5 after each HTML5/OPA update or new pages may not be loaded. + - When reloading HTML/OPA with F5 do not forget that your initial token wont be accepted anymore. You should either restart to clean existing session or cleanup AJB_session cookie. + +### Move to Target + cd $MYWORKSPACE/afb-client + gulp build-app-prod + scp -r ./dist.pro/* user@mytarget:/rootdir/apfDaemon + + /AppClient + | + |---- package.json + |---- bower.json + |---- gulpfile.js + |---- .noderc.js [Warning: contains private keys should not uploaded in Github] + | + |---- /Frontend + | | + | |---- index.html + | |---- app.js + | | + | |---- /styles + | | | + | | |---- _settings.scss + | | |---- app.scss + | | + | |---- /Widgets + | | | + | | |--- Widget-1 + | | |... + | | + | |-----/Pages + | |--- Home-Page + | |... + | + | + |---- /Backend + | |-- server.js // launcher + | |----/ models // mogoose database schemas + | |----/ providers // authentication services + | |----/ restapis // application APIs + | + |---- (/dist.dev) + |---- (/dist.prod) + + \ No newline at end of file diff --git a/afb-client/bower.json b/afb-client/bower.json new file mode 100644 index 0000000..84dbe1e --- /dev/null +++ b/afb-client/bower.json @@ -0,0 +1,18 @@ +{ + "name": "healthy-gulp-angular", + "version": "0.0.0", + "authors": "", + "private": true, + "dependencies": { + "angular": "~1.3.4", + "angular-cookies": "~1.3.4", + "foundation-apps": "~1.1.0", + "angular-ui-notification": "~0.0.14", + "foundation-icon-fonts": "*" + }, + "overrides": { + "foundation": { + "main": ["dist/js/foundation-apps-templates.js", "dist/js/foundation-apps.js"] + } + } +} diff --git a/afb-client/gulpfile.js b/afb-client/gulpfile.js new file mode 100644 index 0000000..f483431 --- /dev/null +++ b/afb-client/gulpfile.js @@ -0,0 +1,456 @@ +// BUG Symlink not working + +var gulp = require('gulp'); +var debug = require('gulp-debug'); +var plugins = require('gulp-load-plugins')(); +var del = require('del'); +var es = require('event-stream'); +var bowerFiles = require('main-bower-files'); +var print = require('gulp-print'); +var Q = require('q'); +var imagemin = require('gulp-imagemin'), pngquant = require('imagemin-pngquant'); +var taskListing = require('gulp-task-listing'); +var symlink = require('gulp-sym'); +var rename = require("gulp-rename"); + +// addon for Foundation6 +var router = require('front-router'); + +// == PATH STRINGS ======== +var appdir = "./app/"; // Warning to not forget trailling '/' +config=require (appdir + "etc/_Config"); // upload user local preferences if any + +// Run node in debug mode in developpement mode ? +var nodeopts = config.DEBUG !== undefined ? '--debug='+config.DEBUG : ''; +var frontend= appdir + config.FRONTEND; +var backend = appdir + config.BACKEND; + +var paths = { + application : frontend, + scripts : frontend+'/**/*.js', + appStyles : [frontend+'/**/*.scss', '!'+frontend+'/styles/*/*-conf.scss'], + globalStyles: [frontend+'/styles/**/*.scss'], + images : [frontend+'/**/*.png',frontend+'/**/*.jpg',frontend+'/**/*.jpeg',frontend+'/**/*.svg',frontend+'/**/*.ttf'], + index : frontend+'/index.html', + partials : [frontend + '/**/*.html', '!' + frontend +'/index.html'], + distDev : './dist.dev', + distProd : './dist.prod', + scriptsDevServer: backend + '/**/*.js', + sass: [frontend+'/styles', 'bower_components/foundation-apps/scss','bower_components/foundation-icon-fonts'], + fonts: ['bower_components/**/*.woff'], + favicon: frontend+'/favicon.ico' +}; + +paths['distAppDev'] = paths.distDev + config.URLBASE; +paths['distAppProd'] = paths.distProd + config.URLBASE; + +// Run node in debug mode in developpement mode ? +var nodeopts = config.DEBUG !== undefined ? '--debug='+config.DEBUG : ''; + +// == PIPE SEGMENTS ======== +var pipes = {}; + +pipes.orderedVendorScripts = function() { + return plugins.order(['jquery.js', 'angular.js']); +}; + +pipes.orderedAppScripts = function() { + return plugins.angularFilesort(); +}; + +pipes.minifiedFileName = function() { + return plugins.rename(function (path) { + path.extname = '.min' + path.extname; + }); +}; + +pipes.validatedAppScripts = function() { + return gulp.src(paths.scripts) + .pipe(plugins.replace('@@APPNAME@@', config.APPNAME)) + .pipe(plugins.jshint()) + .pipe(plugins.jshint.reporter('jshint-stylish')); +}; + +pipes.builtAppScriptsDev = function() { + return pipes.validatedAppScripts() + .pipe(gulp.dest(paths.distAppDev)); +}; + +pipes.builtAppScriptsProd = function() { + var scriptedPartials = pipes.scriptedPartials(); + var validatedAppScripts = pipes.validatedAppScripts(); + + return es.merge(scriptedPartials, validatedAppScripts) + .pipe(plugins.ngAnnotate()) + .pipe(pipes.orderedAppScripts()) + .pipe(plugins.sourcemaps.init()) + .pipe(plugins.concat(config.APPNAME+'.min.js')) + .pipe(plugins.uglify({compress: {drop_console: true}})) + .pipe(plugins.sourcemaps.write()) + .pipe(gulp.dest(paths.distAppProd)); +}; + +pipes.builtVendorScriptsDev = function() { + return gulp.src(bowerFiles()) + .pipe(gulp.dest( paths.distDev +'/bower_components')); +}; + +pipes.builtVendorScriptsProd = function() { + return gulp.src(bowerFiles('**/*.js')) + .pipe(pipes.orderedVendorScripts()) + .pipe(plugins.concat('vendor.min.js')) + .pipe(plugins.uglify()) + .pipe(gulp.dest(paths.distProd+ '/bower_components')); +}; + +pipes.validatedDevServerScripts = function() { + return gulp.src(paths.scriptsDevServer) + .pipe(plugins.jshint()) + .pipe(plugins.jshint.reporter('jshint-stylish')); +}; + +pipes.validatedPartials = function() { + return gulp.src(paths.partials) + .pipe(plugins.htmlhint({'doctype-first': false})) + .pipe(router({path: paths.application+'/tmp/routes.js', root: paths.application})) + .pipe(plugins.htmlhint.reporter()); +}; + +pipes.builtPartialsDev = function() { + return pipes.validatedPartials() + .pipe(gulp.dest(paths.distAppDev)); +}; + +pipes.scriptedPartials = function() { + return pipes.validatedPartials() + .pipe(plugins.htmlhint.failReporter()) + .pipe(plugins.htmlmin({collapseWhitespace: true, removeComments: true})) + .pipe(plugins.ngHtml2js({ + moduleName: config.APPNAME, + template: "(function() {" + + "angular.module('<%= moduleName %>').run(['$templateCache', function($templateCache) {" + + "$templateCache.put('<%= template.url %>',\n '<%= template.escapedContent %>');" + + "}]);\n" + + "})();\n" + })); +}; + +pipes.builtAppStylesDev = function() { + return gulp.src(paths.appStyles) + .pipe(plugins.sass({includePaths: paths.sass})) + .pipe(gulp.dest(paths.distAppDev + '/styles')); +}; + +pipes.builtglobalStylesDev = function() { + return gulp.src(paths.globalStyles) + .pipe(plugins.sass({includePaths: paths.sass})) + .pipe(gulp.dest(paths.distDev + '/global_styles')); +}; + +pipes.builtAppStylesProd = function() { + return gulp.src(paths.appStyles) + .pipe(plugins.sourcemaps.init()) + .pipe(plugins.sass({includePaths: frontend + '/styles'})) + // .pipe(debug({title: '***** appStyle:'})) + .pipe(plugins.minifyCss()) + .pipe(plugins.concat(config.APPNAME+'.css')) + .pipe(plugins.sourcemaps.write()) + .pipe(pipes.minifiedFileName()) + .pipe(gulp.dest(paths.distAppProd)); +}; + +pipes.builtglobalStylesProd = function() { + return gulp.src(paths.globalStyles) + .pipe(plugins.sourcemaps.init()) + .pipe(plugins.sass({includePaths: paths.sass})) + .pipe(plugins.minifyCss()) + .pipe(plugins.sourcemaps.write()) + .pipe(pipes.minifiedFileName()) + .pipe(rename(function (path) {path.dirname="";return path;})) + .pipe(gulp.dest(paths.distProd + '/global_styles')); +}; + +pipes.processedFontsDev = function() { + return gulp.src(paths.fonts) + .pipe(rename(function (path) {path.dirname="";return path;})) + .pipe(gulp.dest(paths.distDev+'/bower_components')); +}; + +pipes.processedFontsProd = function() { + return gulp.src(paths.fonts) + .pipe(rename(function (path) {path.dirname="";return path;})) + .pipe(gulp.dest(paths.distProd+'/bower_components')); +}; + + +pipes.processedImagesDev = function() { + return gulp.src(paths.images) + .pipe(gulp.dest(paths.distAppDev)); +}; + +pipes.processedFaviconDev = function() { + return gulp.src(paths.favicon) + .pipe(gulp.dest(paths.distDev)); +}; + +pipes.processedImagesProd = function() { + return gulp.src(paths.images) + .pipe(imagemin({ + progressive: true, + svgoPlugins: [{removeViewBox: false}], + use: [pngquant()] + })) + .pipe(gulp.dest(paths.distAppProd)); +}; + +pipes.processedFaviconProd = function() { + return gulp.src(paths.favicon) + .pipe(gulp.dest(paths.distProd)); +}; + +// Create an Symlink when config.URLBASE exist +pipes.createDevSymLink = function() { + return gulp.src(paths.distDev).pipe(symlink(paths.distDev+config.URLBASE, {force: true})); +}; + +pipes.createProdSymLink = function() { + return gulp.src(paths.distProd).pipe(symlink(paths.distDev+config.URLBASE,{force: true})); +}; + +pipes.validatedIndex = function() { + return gulp.src(paths.index) + .pipe(plugins.replace('@@APPNAME@@', config.APPNAME)) + .pipe(plugins.replace('@@URLBASE@@', config.URLBASE)) + .pipe(plugins.htmlhint()) + .pipe(plugins.htmlhint.reporter()); +}; + +pipes.builtIndexDev = function() { + + var orderedVendorScripts = pipes.builtVendorScriptsDev() + .pipe(pipes.orderedVendorScripts()); + + var orderedAppScripts = pipes.builtAppScriptsDev() + .pipe(pipes.orderedAppScripts()); + + var appStyles = pipes.builtAppStylesDev(); + var globalStyles = pipes.builtglobalStylesDev(); + + return pipes.validatedIndex() + // Vendor and Global should have absolute path to rootdir application one are relative to BaseURL + .pipe(plugins.inject(orderedVendorScripts, {relative: false, ignorePath: "/dist.dev", name: 'bower'})) + .pipe(plugins.inject(globalStyles, {relative: false, ignorePath: "/dist.dev", name:'vendor'})) + .pipe(gulp.dest(paths.distAppDev)) // write first to get relative path for inject + .pipe(plugins.inject(orderedAppScripts, {relative: true})) + .pipe(plugins.inject(appStyles, {relative: true, name: 'appli'})) + .pipe(gulp.dest(paths.distAppDev)); +}; + +pipes.builtIndexProd = function() { + + var vendorScripts= pipes.builtVendorScriptsProd(); + var appScripts = pipes.builtAppScriptsProd(); + var appStyles = pipes.builtAppStylesProd(); + var globalStyles = pipes.builtglobalStylesProd(); + + return pipes.validatedIndex() + // Vendor and Global should have absolute path to rootdir application one are relative to BaseURL + .pipe(plugins.inject(vendorScripts, {relative: false, ignorePath: "/dist.prod", name: 'bower'})) + .pipe(plugins.inject(globalStyles, {relative: false, ignorePath: "/dist.prod", name:'vendor'})) + .pipe(gulp.dest(paths.distAppProd)) // write first to get relative path for inject + .pipe(plugins.inject(appScripts, {relative: true})) + .pipe(plugins.inject(appStyles, {relative: true, name:'appli'})) + .pipe(plugins.htmlmin({collapseWhitespace: true, removeComments: true})) + .pipe(gulp.dest(paths.distAppProd)); +}; + +pipes.builtAppDev = function() { + return es.merge(pipes.builtIndexDev(), pipes.builtPartialsDev(), pipes.processedFaviconDev(), pipes.processedImagesDev(), pipes.processedFontsDev() ); +}; + +pipes.builtAppProd = function() { + return es.merge(pipes.builtIndexProd(), pipes.processedFaviconProd(), pipes.processedImagesProd(), pipes.processedFontsProd()); +}; + + +// == TASKS ======== + +// Add a task to render the output +gulp.task('help', taskListing.withFilters(/-/)); + +// clean, build of production environement +gulp.task('build', ['clean-build-app-prod', 'validate-devserver-scripts']); + +gulp.task('run', function() { + // start nodemon to auto-reload the dev server + plugins.nodemon({ script: 'server.js', ext: 'js', watch: ['devServer/']}) + .on('change', ['validate-devserver-scripts']) + .on('restart', function () { + console.log('[nodemon] restarted dev server'); + }); +}); + +// removes all compiled dev files +gulp.task('clean-dev', function() { + var deferred = Q.defer(); + del(paths.distDev, function() { + deferred.resolve(); + }); + return deferred.promise; +}); + +// removes all compiled production files +gulp.task('clean-prod', function() { + var deferred = Q.defer(); + del(paths.distProd, function() { + deferred.resolve(); + }); + return deferred.promise; +}); + +// checks html source files for syntax errors +gulp.task('validate-partials', pipes.validatedPartials); + +// checks index.html for syntax errors +gulp.task('validate-index', pipes.validatedIndex); + +// moves html source files into the dev environment +gulp.task('build-partials-dev', pipes.builtPartialsDev); + +// converts partials to javascript using html2js +gulp.task('convert-partials-to-js', pipes.scriptedPartials); + +// runs jshint on the dev server scripts +gulp.task('validate-devserver-scripts', pipes.validatedDevServerScripts); + +// runs jshint on the app scripts +gulp.task('validate-app-scripts', pipes.validatedAppScripts); + +// moves app scripts into the dev environment +gulp.task('build-app-scripts-dev', pipes.builtAppScriptsDev); + +// concatenates, uglifies, and moves app scripts and partials into the prod environment +gulp.task('build-app-scripts-prod', pipes.builtAppScriptsProd); + +// compiles app sass and moves to the dev environment +gulp.task('build-app-styles-dev', pipes.builtAppStylesDev); + +// compiles and minifies app sass to css and moves to the prod environment +gulp.task('build-app-styles-prod', pipes.builtAppStylesProd); + +// moves vendor scripts into the dev environment +gulp.task('build-vendor-scripts-dev', pipes.builtVendorScriptsDev); + +// concatenates, uglifies, and moves vendor scripts into the prod environment +gulp.task('build-vendor-scripts-prod', pipes.builtVendorScriptsProd); + +// validates and injects sources into index.html and moves it to the dev environment +gulp.task('build-index-dev', pipes.builtIndexDev); + +// validates and injects sources into index.html, minifies and moves it to the dev environment +gulp.task('build-index-prod', pipes.builtIndexProd); + +// builds a complete dev environment +gulp.task('build-app-dev', pipes.builtAppDev); + +// builds a complete prod environment +gulp.task('build-app-prod', pipes.builtAppProd); + +// cleans and builds a complete dev environment +gulp.task('clean-build-app-dev', ['clean-dev'], pipes.builtAppDev); + +// cleans and builds a complete prod environment +gulp.task('clean-build-app-prod', ['clean-prod'], pipes.builtAppProd); + +// clean, build, and watch live changes to the dev environment +gulp.task('watch-dev', ['clean-build-app-dev', 'validate-devserver-scripts'], function() { + + // start nodemon to auto-reload the dev server + plugins.nodemon({ exec: 'node ' + nodeopts, script: backend+'/server.js', ext: 'js', watch: [backend], env: {NODE_ENV : 'dev'} }) + .on('change', ['validate-devserver-scripts']) + .on('restart', function () { + console.log('[nodemon] restarted dev server'); + }); + + // start live-reload server + plugins.livereload.listen({ start: true }); + + // watch index + gulp.watch(paths.index, function() { + return pipes.builtIndexDev() + .pipe(plugins.livereload()); + }); + + // watch app scripts + gulp.watch(paths.scripts, function() { + return pipes.builtAppScriptsDev() + .pipe(plugins.livereload()); + }); + + // watch html partials + gulp.watch(paths.partials, function() { + return pipes.builtPartialsDev() + .pipe(plugins.livereload()); + }); + + // watch Images + gulp.watch(paths.images, function() { + return pipes.processedImagesDev() + .pipe(plugins.livereload()); + }); + + // watch styles + gulp.watch(paths.appStyles, function() { + return pipes.builtAppStylesDev() + .pipe(plugins.livereload()); + }); + +}); + +// clean, build, and watch live changes to the prod environment +gulp.task('watch-prod', ['clean-build-app-prod', 'validate-devserver-scripts'], function() { + + // start nodemon to auto-reload the dev server + plugins.nodemon({ script: backend +'/server.js', ext: 'js', watch: [backend], env: {MODE : 'prod'} }) + .on('change', ['validate-devserver-scripts']) + .on('restart', function () { + console.log('[nodemon] restarted dev server'); + }); + + // start live-reload server + plugins.livereload.listen({start: true}); + + // watch index + gulp.watch(paths.index, function() { + return pipes.builtIndexProd() + .pipe(plugins.livereload()); + }); + + // watch app scripts + gulp.watch(paths.scripts, function() { + return pipes.builtAppScriptsProd() + .pipe(plugins.livereload()); + }); + + // watch hhtml partials + gulp.watch(paths.partials, function() { + return pipes.builtAppScriptsProd() + .pipe(plugins.livereload()); + }); + + // watch Images + gulp.watch(paths.images, function() { + return pipes.processedImagesProd() + .pipe(plugins.livereload()); + }); + + // watch styles + gulp.watch(paths.appStyles, function() { + return pipes.builtAppStylesProd() + .pipe(plugins.livereload()); + }); + +}); + +// default task builds for prod +gulp.task('default', ['clean-build-app-prod']); diff --git a/afb-client/index.html b/afb-client/index.html new file mode 100644 index 0000000..32e0efe --- /dev/null +++ b/afb-client/index.html @@ -0,0 +1,41 @@ + + + + + + + + + Simple Sample Application + + + + + + + + + + + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/afb-client/package.json b/afb-client/package.json new file mode 100644 index 0000000..3b11ee1 --- /dev/null +++ b/afb-client/package.json @@ -0,0 +1,61 @@ +{ + "name": "AudioClient", + "private": true, + "version": "0.0.0", + "repository": { + "type": "git", + "url": "https://github.com/iotbzh/healthy-gulp-angular-foundation6" + }, + "scripts": { + "postinstall": "bower install", + "build": "./node_modules/.bin/gulp clean-build-app-dev", + "start": "node node_modules/.bin/gulp watch-dev" + }, + "devDependencies": { + "body-parser": "^1.5.2", + "bower": "^1.3.1", + "cookie-parser": "^1.4.0", + "del": "^1.1.1", + "event-stream": "^3.2.2", + "express": "^4.7.2", + "express-session": "^1.12.1", + "front-router": "^1.0.0", + "gulp": "^3.9.0", + "gulp-angular-filesort": "^1.0.4", + "gulp-debug": "^2.1.2", + "gulp-concat": "^2.4.3", + "gulp-htmlhint": "0.0.9", + "gulp-htmlmin": "^1.0.0", + "gulp-imagemin": "*", + "gulp-inject": "^1.1.1", + "gulp-jshint": "^1.9.2", + "gulp-livereload": "^3.7.0", + "gulp-load-plugins": "^0.8.0", + "gulp-minify-css": "^0.4.5", + "gulp-ng-annotate": "^1.1.0", + "gulp-ng-html2js": "^0.2.0", + "gulp-nodemon": "^2.0.0", + "gulp-order": "^1.1.1", + "gulp-print": "^1.1.0", + "gulp-rename": "^1.2.0", + "gulp-replace": "^0.5.4", + "gulp-sass": "^2.0.3", + "gulp-sourcemaps": "^1.3.0", + "gulp-task-listing": "^1.0.1", + "gulp-uglify": "^1.2.0", + "imagemin-pngquant": "*", + "jshint-stylish": "^1.0.0", + "karma": "^0.10", + "karma-junit-reporter": "^0.2.2", + "main-bower-files": "^2.5.0", + "method-override": "^2.1.2", + "protractor": "^1.1.1", + "q": "^1.1.2", + "shelljs": "^0.2.6", + "traceback": "^0.3.1", + "gulp-sym": "0.0.14" + }, + "dependencies": { + "multer": "^1.1.0" + } +} -- 2.16.6