'use strict'; var fs = require('fs'), path = require('path'); /* jshint node:true */ /* jshint browser:false */ var StringReplacePlugin = require('string-replace-webpack-plugin'); module.exports = function(grunt) { require('time-grunt')(grunt); require('load-grunt-tasks')(grunt); grunt.loadTasks('grunt/tasks'); var webpack = require('webpack'); var pkg = require('./package.json'); var dt = new Date().toISOString().replace(/T.*/, ''); var minElectronVersionForUpdate = '1.0.1'; var zipCommentPlaceholder = 'zip_comment_placeholder_that_will_be_replaced_with_hash'; var electronVersion = pkg.devDependencies['electron-prebuilt'].replace(/^\D/, ''); while (zipCommentPlaceholder.length < 512) { zipCommentPlaceholder += '.'; } function replaceFont(css) { css.walkAtRules('font-face', function (rule) { var fontFamily = rule.nodes.filter(function(n) { return n.prop === 'font-family'; })[0]; if (!fontFamily) { throw 'Bad font rule: ' + rule.toString(); } var value = fontFamily.value.replace(/["']/g, ''); var fontFiles = { FontAwesome: 'fontawesome-webfont.woff' }; var fontFile = fontFiles[value]; if (!fontFile) { throw 'Unsupported font ' + value + ': ' + rule.toString(); } var data = fs.readFileSync('tmp/fonts/' + fontFile, 'base64'); var src = 'url(data:application/font-woff;charset=utf-8;base64,{data}) format(\'woff\')' .replace('{data}', data); //var src = 'url(\'../fonts/fontawesome-webfont.woff\') format(\'woff\')'; rule.nodes = rule.nodes.filter(function(n) { return n.prop !== 'src'; }); rule.append({ prop: 'src', value: src }); }); } var webpackConfig = { entry: { app: 'app', vendor: ['jquery', 'underscore', 'backbone', 'kdbxweb', 'baron', 'dropbox', 'pikaday', 'filesaver', 'qrcode'] }, output: { path: 'tmp/js', filename: 'app.js' }, stats: { colors: false, modules: true, reasons: true }, progress: false, failOnError: true, resolve: { root: [path.join(__dirname, 'app/scripts'), path.join(__dirname, 'bower_components')], alias: { backbone: 'backbone/backbone-min.js', underscore: 'underscore/underscore-min.js', _: 'underscore/underscore-min.js', jquery: 'jquery/dist/jquery.min.js', hbs: 'handlebars/runtime.js', kdbxweb: 'kdbxweb/dist/kdbxweb.js', dropbox: 'dropbox/lib/dropbox.min.js', baron: 'baron/baron.min.js', pikaday: 'pikaday/pikaday.js', filesaver: 'FileSaver.js/FileSaver.min.js', qrcode: 'jsqrcode/dist/qrcode.min.js', templates: path.join(__dirname, 'app/templates') } }, module: { loaders: [ { test: /\.hbs$/, loader: StringReplacePlugin.replace('handlebars-loader', { replacements: [{ pattern: /\r?\n\s*/g, replacement: function() { return '\n'; } }]})}, { test: /runtime\-info\.js$/, loader: StringReplacePlugin.replace({ replacements: [ { pattern: /@@VERSION/g, replacement: function() { return pkg.version; } }, { pattern: /@@DATE/g, replacement: function() { return dt; } }, { pattern: /@@COMMIT/g, replacement: function() { return grunt.config.get('gitinfo.local.branch.current.shortSHA'); } } ]})}, { test: /baron(\.min)?\.js$/, loader: 'exports?baron; delete window.baron;' }, { test: /pikadat\.js$/, loader: 'uglify' }, { test: /handlebars/, loader: 'strip-sourcemap-loader' }, { test: /\.js$/, exclude: /(node_modules|bower_components)/, loader: 'babel', query: { presets: ['es2015'], cacheDirectory: true } } ] }, plugins: [ new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), new webpack.BannerPlugin('keeweb v' + pkg.version + ', (c) 2015 ' + pkg.author.name + ', opensource.org/licenses/' + pkg.license), new webpack.optimize.OccurenceOrderPlugin(), new webpack.ProvidePlugin({ _: 'underscore', $: 'jquery' }), new webpack.IgnorePlugin(/^(moment)$/), new StringReplacePlugin() ], node: { console: false, process: false, Buffer: false, __filename: false, __dirname: false }, externals: { xmldom: 'null' } }; grunt.initConfig({ gitinfo: { branch: { current: { SHA: 'Current HEAD SHA', shortSHA: 'Current HEAD short SHA', name: 'Current branch name', lastCommitTime: 'Last commit time' } } }, 'bower-install-simple': { install: { } }, clean: { dist: ['dist', 'tmp'], desktop: ['tmp/desktop', 'dist/desktop'] }, copy: { html: { src: 'app/index.html', dest: 'tmp/index.html', nonull: true }, favicon: { src: 'app/favicon.png', dest: 'tmp/favicon.png', nonull: true }, touchicon: { src: 'app/touchicon.png', dest: 'tmp/touchicon.png', nonull: true }, fonts: { src: 'bower_components/font-awesome/fonts/fontawesome-webfont.*', dest: 'tmp/fonts/', nonull: true, expand: true, flatten: true }, 'desktop-app-content': { cwd: 'electron/', src: '**', dest: 'tmp/desktop/app/', expand: true, nonull: true }, 'desktop-windows-helper-ia32': { src: 'helper/win32/KeeWebHelper.exe', dest: 'tmp/desktop/KeeWeb-win32-ia32/', nonull: true }, 'desktop-windows-helper-x64': { src: 'helper/win32/KeeWebHelper.exe', dest: 'tmp/desktop/KeeWeb-win32-x64/', nonull: true } }, jshint: { options: { jshintrc: true }, all: ['app/scripts/**/*.js'] }, sass: { options: { sourceMap: false, includePaths: ['./bower_components'] }, dist: { files: { 'tmp/css/main.css': 'app/styles/main.scss' } } }, postcss: { options: { processors: [ replaceFont, require('cssnano')({discardComments: {removeAll: true}}) ] }, dist: { src: 'tmp/css/main.css', dest: 'tmp/css/main.css' } }, inline: { app: { src: 'tmp/index.html', dest: 'tmp/app.html' } }, htmlmin: { options: { removeComments: true, collapseWhitespace: true }, app: { files: { 'dist/index.html': 'tmp/app.html' } } }, 'string-replace': { manifest: { options: { replacements: [ { pattern: '# YYYY-MM-DD:v0.0.0', replacement: '# ' + dt + ':v' + pkg.version }, { pattern: '# updmin:v0.0.0', replacement: '# updmin:v' + minElectronVersionForUpdate } ] }, files: { 'dist/manifest.appcache': 'app/manifest.appcache' } }, 'manifest-html': { options: { replacements: [{ pattern: '', }, linux: { options: { platform: 'linux', arch: ['x64', 'ia32'], icon: 'graphics/icon.ico' } }, darwin: { options: { platform: 'darwin', arch: ['x64'], icon: 'graphics/icon.icns', 'app-bundle-id': 'net.antelle.keeweb', 'app-category-type': 'public.app-category.productivity', 'extend-info': 'package/osx/extend.plist' } }, win32: { options: { platform: 'win32', arch: ['ia32', 'x64'], icon: 'graphics/icon.ico', 'build-version': pkg.version, 'version-string': { 'CompanyName': 'KeeWeb', 'FileDescription': pkg.description, 'OriginalFilename': 'KeeWeb.exe', 'ProductName': 'KeeWeb', 'InternalName': 'KeeWeb' } } } }, compress: { options: { level: 6 }, 'desktop-update': { options: { archive: 'dist/desktop/UpdateDesktop.zip', comment: zipCommentPlaceholder }, files: [{ cwd: 'tmp/desktop/app', src: '**', expand: true }] }, 'win32-x64': { options: { archive: 'dist/desktop/KeeWeb.win32.x64.zip' }, files: [{ cwd: 'tmp/desktop/KeeWeb-win32-x64', src: '**', expand: true }] }, 'win32-ia32': { options: { archive: 'dist/desktop/KeeWeb.win32.ia32.zip' }, files: [{ cwd: 'tmp/desktop/KeeWeb-win32-ia32', src: '**', expand: true }] }, 'linux-x64': { options: { archive: 'dist/desktop/KeeWeb.linux.x64.zip' }, files: [{ cwd: 'tmp/desktop/KeeWeb-linux-x64', src: '**', expand: true }] }, 'linux-ia32': { options: { archive: 'dist/desktop/KeeWeb.linux.ia32.zip' }, files: [{ cwd: 'tmp/desktop/KeeWeb-linux-ia32', src: '**', expand: true }] } }, appdmg: { options: { title: 'KeeWeb', icon: 'graphics/icon.icns', background: 'graphics/background.png', 'background-color': '#E0E6F9', 'icon-size': 80, window: { size: { width: 658, height: 498 } }, contents: [ { x: 438, y: 344, type: 'link', path: '/Applications' }, { x: 192, y: 344, type: 'file', path: 'tmp/desktop/KeeWeb-darwin-x64/KeeWeb.app' } ] }, app: { dest: 'dist/desktop/KeeWeb.mac.dmg' } }, deb: { 'linux-x64': { options: { tmpPath: 'tmp/desktop/', package: { name: 'keeweb-desktop', version: pkg.version, description: pkg.description, author: pkg.author, homepage: pkg.homepage, rev: function() { return grunt.config.get('gitinfo.local.branch.current.shortSHA'); } }, info: { arch: 'amd64', targetDir: 'dist/desktop', pkgName: 'KeeWeb.linux.x64.deb', appName: 'KeeWeb', scripts: { postinst: 'package/deb/scripts/postinst' } } }, files: [ { cwd: 'package/deb/usr', src: '**', dest: '/usr', expand: true, nonull: true }, { cwd: 'tmp/desktop/KeeWeb-linux-x64/', src: '**', dest: '/opt/keeweb-desktop', expand: true, nonull: true }, { src: 'graphics/128x128.png', dest: '/usr/share/icons/hicolor/128x128/apps/keeweb.png', nonull: true }] } }, 'sign-archive': { 'desktop-update': { options: { file: 'dist/desktop/UpdateDesktop.zip', signature: zipCommentPlaceholder, privateKey: 'keys/private-key.pem' } } }, 'validate-desktop-update': { desktop: { options: { file: 'dist/desktop/UpdateDesktop.zip', expected: ['main.js', 'app.js', 'index.html', 'package.json', 'node_modules/node-stream-zip/node_stream_zip.js'], publicKey: 'app/resources/public-key.pem' } } }, 'sign-html': { 'app': { options: { file: 'dist/index.html', privateKey: 'keys/private-key.pem' } } }, 'concurrent': { options: { logConcurrentOutput: true, }, 'dev-server': [ 'watch', 'webpack-dev-server' ] } }); // compound builder tasks grunt.registerTask('build-web-app', [ 'gitinfo', 'bower-install-simple', 'clean', 'jshint', 'copy:html', 'copy:favicon', 'copy:touchicon', 'copy:fonts', 'webpack', 'uglify', 'sass', 'postcss', 'inline', 'htmlmin', 'string-replace:manifest-html', 'string-replace:manifest', 'sign-html' ]); grunt.registerTask('build-desktop-app-content', [ 'copy:desktop-app-content', 'string-replace:desktop-html', ]); grunt.registerTask('build-desktop-update', [ 'compress:desktop-update', 'sign-archive:desktop-update', 'validate-desktop-update', ]); grunt.registerTask('build-desktop-executables', [ 'electron', 'copy:desktop-windows-helper-ia32', 'copy:desktop-windows-helper-x64', ]); grunt.registerTask('build-desktop-archives', [ 'compress:win32-x64', 'compress:win32-ia32', 'compress:linux-x64', 'compress:linux-ia32', ]); grunt.registerTask('build-desktop-dist-darwin', [ 'appdmg' ]); grunt.registerTask('build-desktop-dist-win32', [ // TODO: windows installer ]); grunt.registerTask('build-desktop-dist-linux', [ 'deb:linux-x64' ]); grunt.registerTask('build-desktop-dist', [ 'build-desktop-dist-darwin', 'build-desktop-dist-win32', 'build-desktop-dist-linux' ]); grunt.registerTask('build-desktop', [ 'gitinfo', 'clean:desktop', 'build-desktop-app-content', 'build-desktop-update', 'build-desktop-executables', 'build-desktop-archives', 'build-desktop-dist' ]); // entry point tasks grunt.registerTask('default', 'Default: build web app', [ 'build-web-app' ]); grunt.registerTask('dev', 'Start web server and watcher', [ 'concurrent:dev-server' ]); grunt.registerTask('desktop', 'Build web and desktop apps for all platforms', [ 'default', 'build-desktop' ]); };