├── .npmignore ├── .gitattributes ├── .gitignore ├── app ├── templates │ ├── gitattributes │ ├── styles │ │ ├── main.css │ │ └── main.scss │ ├── bowerrc │ ├── coffees │ │ ├── popup.coffee │ │ ├── options.coffee │ │ ├── contentscript.coffee │ │ ├── background.coffee │ │ ├── background.browseraction.coffee │ │ ├── background.pageaction.coffee │ │ └── chromereload.coffee │ ├── scripts │ │ ├── options.js │ │ ├── popup.js │ │ ├── contentscript.js │ │ ├── background.js │ │ ├── background.browseraction.js │ │ ├── background.pageaction.js │ │ └── chromereload.js │ ├── images │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ ├── icon-19.png │ │ └── icon-38.png │ ├── gitignore │ ├── _bower.json │ ├── _locales │ │ └── en │ │ │ └── messages.json │ ├── manifest.json │ ├── editorconfig │ ├── jshintrc │ ├── options.html │ ├── popup.html │ ├── _package.json │ └── Gruntfile.js ├── USAGE └── index.js ├── .travis.yml ├── contributing.md ├── .editorconfig ├── .jshintrc ├── package.json ├── readme.md └── test └── test.js /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | temp/ 3 | -------------------------------------------------------------------------------- /app/templates/gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /app/templates/styles/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /app/templates/bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/templates/styles/main.scss: -------------------------------------------------------------------------------- 1 | $padding: 20px; 2 | body { 3 | padding: $padding; 4 | } 5 | -------------------------------------------------------------------------------- /app/templates/coffees/popup.coffee: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | console.log('\'Allo \'Allo! Popup') 4 | -------------------------------------------------------------------------------- /app/templates/scripts/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | console.log('\'Allo \'Allo! Option'); 4 | -------------------------------------------------------------------------------- /app/templates/scripts/popup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | console.log('\'Allo \'Allo! Popup'); 4 | -------------------------------------------------------------------------------- /app/templates/coffees/options.coffee: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | console.log('\'Allo \'Allo! Option') 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'iojs' 5 | - '0.12' 6 | - '0.10' 7 | -------------------------------------------------------------------------------- /app/templates/scripts/contentscript.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | console.log('\'Allo \'Allo! Content script'); 4 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | See the [contributing docs](https://github.com/yeoman/yeoman/blob/master/contributing.md) 2 | -------------------------------------------------------------------------------- /app/templates/coffees/contentscript.coffee: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | console.log('\'Allo \'Allo! Content script') 4 | -------------------------------------------------------------------------------- /app/templates/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanbuck/generator-chrome-extension/master/app/templates/images/icon-128.png -------------------------------------------------------------------------------- /app/templates/images/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanbuck/generator-chrome-extension/master/app/templates/images/icon-16.png -------------------------------------------------------------------------------- /app/templates/images/icon-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanbuck/generator-chrome-extension/master/app/templates/images/icon-19.png -------------------------------------------------------------------------------- /app/templates/images/icon-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanbuck/generator-chrome-extension/master/app/templates/images/icon-38.png -------------------------------------------------------------------------------- /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | temp 3 | .tmp 4 | dist 5 | .sass-cache 6 | app/bower_components 7 | test/bower_components 8 | package 9 | -------------------------------------------------------------------------------- /app/templates/_bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.slugify(appname) %>", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": {} 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /app/templates/coffees/background.coffee: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | chrome.runtime.onInstalled.addListener (details) -> 4 | console.log('previousVersion', details.previousVersion) 5 | 6 | console.log('\'Allo \'Allo! Event Page') 7 | -------------------------------------------------------------------------------- /app/templates/scripts/background.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | chrome.runtime.onInstalled.addListener(function (details) { 4 | console.log('previousVersion', details.previousVersion); 5 | }); 6 | 7 | console.log('\'Allo \'Allo! Event Page'); 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": false, 5 | "curly": false, 6 | "eqeqeq": true, 7 | "eqnull": true, 8 | "immed": true, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "undef": true, 13 | "strict": false 14 | } 15 | -------------------------------------------------------------------------------- /app/templates/coffees/background.browseraction.coffee: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | chrome.runtime.onInstalled.addListener (details) -> 4 | console.log('previousVersion', details.previousVersion) 5 | 6 | chrome.browserAction.setBadgeText({text: '\'Allo'}) 7 | 8 | console.log('\'Allo \'Allo! Event Page for Browser Action') 9 | -------------------------------------------------------------------------------- /app/templates/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "<%= manifest.name %>", 4 | "description": "The name of the application" 5 | }, 6 | "appDescription": { 7 | "message": "<%= manifest.description %>", 8 | "description": "The description of the application" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/templates/scripts/background.browseraction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | chrome.runtime.onInstalled.addListener(function (details) { 4 | console.log('previousVersion', details.previousVersion); 5 | }); 6 | 7 | chrome.browserAction.setBadgeText({text: '\'Allo'}); 8 | 9 | console.log('\'Allo \'Allo! Event Page for Browser Action'); 10 | -------------------------------------------------------------------------------- /app/templates/coffees/background.pageaction.coffee: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | chrome.runtime.onInstalled.addListener (details) -> 4 | console.log('previousVersion', details.previousVersion) 5 | 6 | chrome.tabs.onUpdated.addListener (tabId) -> 7 | chrome.pageAction.show(tabId) 8 | 9 | console.log('\'Allo \'Allo! Event Page for Page Action') 10 | -------------------------------------------------------------------------------- /app/templates/scripts/background.pageaction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | chrome.runtime.onInstalled.addListener(function (details) { 4 | console.log('previousVersion', details.previousVersion); 5 | }); 6 | 7 | chrome.tabs.onUpdated.addListener(function (tabId) { 8 | chrome.pageAction.show(tabId); 9 | }); 10 | 11 | console.log('\'Allo \'Allo! Event Page for Page Action'); 12 | -------------------------------------------------------------------------------- /app/templates/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_appName__", 3 | "version": "0.0.1", 4 | "manifest_version": 2, 5 | "description": "__MSG_appDescription__", 6 | "icons": { 7 | "16": "images/icon-16.png", 8 | "128": "images/icon-128.png" 9 | }, 10 | "default_locale": "en", 11 | "background": { 12 | "scripts": [ 13 | "scripts/chromereload.js", 14 | "scripts/background.js" 15 | ] 16 | }<%= manifest.items %> 17 | } 18 | -------------------------------------------------------------------------------- /app/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Create a chrome extension boilerplate generator that creates everything for you. have fun. 3 | 4 | Example: 5 | yeoman init chrome-extension $NAME[OPTIONAL] 6 | 7 | This will create: 8 | Gruntfile.js: Configuration for the task runner. 9 | bower.json: Front-end packages installed by bower. 10 | package.json: Development packages installed by npm. 11 | 12 | app/: Your application files. 13 | test/: Unit tests for your application. 14 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.json] 15 | indent_size = 2 16 | 17 | # We recommend you to keep these unchanged 18 | end_of_line = lf 19 | charset = utf-8 20 | trim_trailing_whitespace = true 21 | insert_final_newline = true 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /app/templates/jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true,<%if (coffee) { %> 9 | "eqnull": true,<% } %> 10 | "immed": true, 11 | "indent": 2, 12 | "latedef": true, 13 | "newcap": true, 14 | "noarg": true, 15 | "quotmark": <%if (coffee) { %>false<% } else { %>"single"<% } %>, 16 | "regexp": true, 17 | "undef": true, 18 | "unused": true, 19 | "strict": true, 20 | "trailing": true, 21 | "smarttabs": true, 22 | "globals" : { 23 | "chrome": true, 24 | "crypto": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/templates/coffees/chromereload.coffee: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | ### 4 | Reload client for Chrome Apps & Extensions. 5 | The reload client has a compatibility with livereload. 6 | WARNING: only supports reload command. 7 | ### 8 | 9 | LIVERELOAD_HOST = 'localhost:' 10 | LIVERELOAD_PORT = 35729 11 | connection = new WebSocket('ws://' + LIVERELOAD_HOST + LIVERELOAD_PORT + '/livereload') 12 | 13 | connection.onerror = (e) -> console.log('reload connection got error' + JSON.stringify(e)) 14 | 15 | connection.onmessage = (e) -> 16 | if e.data 17 | data = JSON.parse(e.data) 18 | if data and data.command == 'reload' then chrome.runtime.reload() 19 | -------------------------------------------------------------------------------- /app/templates/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

'Allo, 'Allo!

14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/templates/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

'Allo, 'Allo!

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/templates/scripts/chromereload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Reload client for Chrome Apps & Extensions. 4 | // The reload client has a compatibility with livereload. 5 | // WARNING: only supports reload command. 6 | 7 | var LIVERELOAD_HOST = 'localhost:'; 8 | var LIVERELOAD_PORT = 35729; 9 | var connection = new WebSocket('ws://' + LIVERELOAD_HOST + LIVERELOAD_PORT + '/livereload'); 10 | 11 | connection.onerror = function (error) { 12 | console.log('reload connection got error:', error); 13 | }; 14 | 15 | connection.onmessage = function (e) { 16 | if (e.data) { 17 | var data = JSON.parse(e.data); 18 | if (data && data.command === 'reload') { 19 | chrome.runtime.reload(); 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-chrome-extension", 3 | "version": "0.3.0", 4 | "description": "Yeoman generator for Chrome Extensions", 5 | "keywords": [ 6 | "yeoman-generator", 7 | "chrome", 8 | "extension", 9 | "addon" 10 | ], 11 | "author": "The Yeoman Team", 12 | "main": "app/index.js", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/yeoman/generator-chrome-extension.git" 16 | }, 17 | "scripts": { 18 | "test": "mocha --reporter spec" 19 | }, 20 | "dependencies": { 21 | "yeoman-generator": "^0.17.0" 22 | }, 23 | "peerDependencies": { 24 | "generator-mocha": ">=0.1.6", 25 | "yo": ">=1.0.0" 26 | }, 27 | "devDependencies": { 28 | "mocha": "*", 29 | "underscore": "^1.6.0" 30 | }, 31 | "engines": { 32 | "node": ">=0.10.0", 33 | "npm": ">=1.2.10" 34 | }, 35 | "licenses": [ 36 | { 37 | "type": "BSD" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= _.slugify(appname) %>", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-contrib-copy": "~0.5.0", 8 | "grunt-contrib-concat": "~0.3.0",<% if (coffee) { %> 9 | "grunt-contrib-coffee": "~0.10.1",<% } %> 10 | "grunt-contrib-uglify": "~0.4.0",<% if (compass) { %> 11 | "grunt-contrib-compass": "~0.7.0",<% } %> 12 | "grunt-contrib-jshint": "~0.9.2", 13 | "grunt-contrib-cssmin": "~0.9.0", 14 | "grunt-contrib-connect": "~0.7.1", 15 | "grunt-contrib-clean": "~0.5.0", 16 | "grunt-contrib-htmlmin": "~0.2.0", 17 | "grunt-bower-install": "~1.0.0", 18 | "grunt-contrib-imagemin": "~0.7.1", 19 | "grunt-contrib-watch": "~0.6.1",<% if (testFramework === 'jasmine') { %> 20 | "grunt-contrib-jasmine": "~0.6.2",<% } %> 21 | "grunt-usemin": "~2.1.0",<% if (testFramework === 'mocha') { %> 22 | "grunt-mocha": "~0.4.10",<% } %> 23 | "grunt-svgmin": "~0.4.0", 24 | "grunt-concurrent": "~0.5.0", 25 | "load-grunt-tasks": "~0.4.0", 26 | "time-grunt": "~0.3.1", 27 | "jshint-stylish": "~0.1.5", 28 | "grunt-chrome-manifest": "~0.2.0", 29 | "grunt-contrib-compress": "~0.9.1" 30 | }, 31 | "engines": { 32 | "node": ">=0.8.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Chrome Extension generator [![Build Status](https://secure.travis-ci.org/yeoman/generator-chrome-extension.svg?branch=master)](http://travis-ci.org/yeoman/generator-chrome-extension) 2 | 3 | Maintainer: [Jimmy Moon](https://github.com/ragingwind) 4 | 5 | > Chrome Extension generator that creates everything you need to get started with extension development. You can choose Browser UI(Browser,Page Action, Omnibox) type and select into permissions what you need. 6 | 7 | ## Getting Started 8 | 9 | - First make a new directory, and `cd` into it: mkdir my-new-chrome-extension && cd $_ 10 | - Install the generator: `npm install -g generator-chrome-extension` 11 | - Run: `yo chrome-extension`, optionally passing an extension name: yo chrome-extension [extension-name] 12 | 13 | Need more information about Chrome Extension? Please visit [Google Chrome Extension Develpment](http://developer.chrome.com/extensions/devguide.html) 14 | 15 | ![](http://recordit.co/H5y0XHYwgf.gif) 16 | 17 | ## Generators 18 | 19 | Available generators: 20 | 21 | * [chrome-extension](#app) (aka [chrome-extension:app](#app)) 22 | 23 | ### App 24 | 25 | Sets up a new Chrome Extension, generating all the boilerplate you need to get started. 26 | 27 | ```bash 28 | yo chrome-extension 29 | ``` 30 | 31 | ## Test Chrome Extension 32 | 33 | To test, go to: chrome://extensions, enable Developer mode and load app as an unpacked extension. 34 | 35 | ## Grunt tasks 36 | 37 | ### Debug 38 | 39 | Debug task helps reduce your effors during development extensions. If the task detects your changes of source files, Livereload([chromereload.js](https://github.com/yeoman/generator-chrome-extension/blob/master/app/templates/scripts/chromereload.js)) reloads your extension. If you would like to know more about Livereload and preview of Yeoman? Please see [Getting started with Yeoman and generator-webapp](http://youtu.be/zBt2g9ekiug?t=3m51s) for your understanding. 40 | 41 | ```bash 42 | grunt debug 43 | ``` 44 | 45 | ### Build 46 | 47 | By default, generators compress the file that was created by building a js/css/html/resource file. You can distribute the compressed file using the Chrome Developer Dashboard to publish to the Chrome Web Store. 48 | 49 | Run this command to build your Chrome Extension project. 50 | 51 | ```bash 52 | grunt build 53 | ``` 54 | 55 | ## Options 56 | 57 | * `--skip-install` 58 | 59 | Skips the automatic execution of `bower` and `npm` after 60 | scaffolding has finished. 61 | 62 | * `--test-framework=[framework]` 63 | 64 | Defaults to `mocha`. Can be switched for 65 | another supported testing framework like `jasmine`. 66 | 67 | * `--coffee` 68 | 69 | Add support for [CoffeeScript](http://coffeescript.org/). 70 | 71 | * `--compass` 72 | 73 | Add support for [Compass](http://compass-style.org/). 74 | 75 | > WARN, Compiled files that generated by coffee or compass will be remained if your Chrome App is running on Chrome App container (with `grunt debug`). You should remove or ignore that files if you don't want to commit to repo. 76 | 77 | ## Contribute 78 | 79 | See the [contributing docs](https://github.com/yeoman/yeoman/blob/master/contributing.md) 80 | 81 | ## License 82 | 83 | [BSD license](http://opensource.org/licenses/bsd-license.php) 84 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /*global describe, it */ 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | var helpers = require('yeoman-generator').test; 6 | var assert = require('assert'); 7 | var _ = require('underscore'); 8 | 9 | describe('Chrome Extension generator', function () { 10 | if ('the generator can be required without throwing', function () { 11 | this.app = require('../app'); 12 | }); 13 | 14 | var options = { 15 | 'skip-install': true 16 | }; 17 | 18 | var prompts = { 19 | uifeatures: [], 20 | permissions: [] 21 | }; 22 | 23 | var runGen; 24 | 25 | beforeEach(function (done) { 26 | helpers.testDirectory(path.join(__dirname, 'temp'), function (err) { 27 | if (err) { 28 | return done(err); 29 | } 30 | 31 | runGen = helpers 32 | .run(path.join(__dirname, '../app')) 33 | .withGenerators([ 34 | [helpers.createDummyGenerator(), 'chrome-extension:app'], 35 | [helpers.createDummyGenerator(), 'mocha:app'] 36 | ]); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('creates expected files in no UI Action', function (done) { 42 | var expected = [ 43 | 'app/bower_components', 44 | 'Gruntfile.js', 45 | 'app/manifest.json', 46 | 'app/_locales/en/messages.json', 47 | 'app/images/icon-128.png', 48 | 'app/images/icon-16.png' 49 | ]; 50 | 51 | runGen.withOptions(options).withPrompt( 52 | _.extend(prompts, { 53 | 'name': 'temp', 54 | 'description': 'description', 55 | 'action': 'No' 56 | }) 57 | ).on('end', function () { 58 | assert.file(expected); 59 | assert.fileContent([ 60 | ['bower.json', /"name": "temp"/], 61 | ['package.json', /"name": "temp"/], 62 | ['Gruntfile.js', /\/\/ No UI feature selected, cssmin task will be commented\n\s+\/\/ 'cssmin'/] 63 | ]); 64 | done(); 65 | }); 66 | }); 67 | 68 | it('creates expected files in Browser Action', function (done) { 69 | var expected = [ 70 | 'app/bower_components', 71 | 'Gruntfile.js', 72 | 'app/_locales/en/messages.json', 73 | 'app/images/icon-128.png', 74 | 'app/images/icon-16.png', 75 | 'app/images/icon-19.png', 76 | 'app/images/icon-38.png', 77 | 'app/scripts/background.js', 78 | 'app/scripts/popup.js', 79 | 'app/popup.html', 80 | 'app/styles/main.css' 81 | ]; 82 | 83 | runGen.withOptions(options).withPrompt( 84 | _.extend(prompts, { 85 | 'name': 'temp', 86 | 'description': 'description', 87 | 'action': 'Browser', 88 | }) 89 | ).on('end', function () { 90 | assert.file(expected); 91 | assert.fileContent([ 92 | ['app/manifest.json', /"browser_action": {\s+"default_icon": {\s+"19": "images\/icon-19.png",\s+"38": "images\/icon-38.png"\s+},\s+"default_title": "temp",\s+"default_popup": "popup.html"\s+}/] 93 | ]); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('creates expected files in Page Action', function (done) { 99 | var expected = [ 100 | 'app/bower_components', 101 | 'Gruntfile.js', 102 | 'app/_locales/en/messages.json', 103 | 'app/images/icon-128.png', 104 | 'app/images/icon-16.png', 105 | 'app/images/icon-19.png', 106 | 'app/images/icon-38.png', 107 | 'app/scripts/background.js', 108 | 'app/scripts/popup.js', 109 | 'app/popup.html', 110 | 'app/styles/main.css' 111 | ]; 112 | 113 | runGen.withOptions(options).withPrompt( 114 | _.extend(prompts, { 115 | 'name': 'temp', 116 | 'description': 'description', 117 | 'action': 'Page', 118 | }) 119 | ).on('end', function () { 120 | assert.file(expected); 121 | assert.fileContent([ 122 | ['app/manifest.json', /"page_action": {\s+"default_icon": {\s+"19": "images\/icon-19.png",\s+"38": "images\/icon-38.png"\s+},\s+"default_title": "temp",\s+"default_popup": "popup.html"\s+}/] 123 | ]); 124 | done(); 125 | }); 126 | }); 127 | 128 | it('creates expected files with Options Page', function (done) { 129 | var expected = [ 130 | 'app/bower_components', 131 | 'Gruntfile.js', 132 | 'app/_locales/en/messages.json', 133 | 'app/images/icon-128.png', 134 | 'app/images/icon-16.png', 135 | 'app/scripts/background.js', 136 | 'app/scripts/options.js', 137 | 'app/options.html', 138 | 'app/styles/main.css' 139 | ]; 140 | 141 | prompts['uifeatures'].push('options'); 142 | 143 | runGen.withOptions(options).withPrompt( 144 | _.extend(prompts, { 145 | 'name': 'temp', 146 | 'description': 'description' 147 | }) 148 | ).on('end', function () { 149 | assert.file(expected); 150 | assert.fileContent([ 151 | ['bower.json', /"name": "temp"/], 152 | ['package.json', /"name": "temp"/], 153 | ['app/manifest.json', /"options_page": "options.html"/] 154 | ]); 155 | done(); 156 | }); 157 | }); 158 | 159 | it('creates manifest.json with Omnibox option', function (done) { 160 | prompts['uifeatures'].push('omnibox'); 161 | 162 | runGen.withOptions(options).withPrompt( 163 | _.extend(prompts, { 164 | 'name': 'temp', 165 | 'description': 'description' 166 | }) 167 | ).on('end', function () { 168 | assert.fileContent([ 169 | ['app/manifest.json', /"omnibox": {\s+"keyword": "temp"\s+}/] 170 | ]); 171 | done(); 172 | }); 173 | }); 174 | 175 | it('creates expected files with Content-script option', function (done) { 176 | prompts['uifeatures'].push('contentscript'); 177 | 178 | runGen.withOptions(options).withPrompt( 179 | _.extend(prompts, { 180 | 'name': 'temp', 181 | 'description': 'description' 182 | }) 183 | ).on('end', function () { 184 | assert.fileContent([ 185 | ['app/manifest.json', /"content_scripts": \[\s+{\s+"matches": \[\s+"http:\/\/\*\/\*",\s+"https:\/\/\*\/\*"\s+\],\s+"js": \[\s+"scripts\/contentscript.js"\s+\],\s+"run_at": "document_end",\s+"all_frames": false/], 186 | ]); 187 | done(); 188 | }); 189 | }); 190 | 191 | it('creates expected manifest permission properties', function (done) { 192 | prompts['permissions'] = [ 193 | 'permission', 194 | 'tabs', 195 | 'bookmarks', 196 | 'cookies', 197 | 'history', 198 | 'http://*/*', 199 | 'https://*/*' 200 | ]; 201 | 202 | runGen.withOptions(options).withPrompt( 203 | _.extend(prompts, { 204 | 'name': 'temp', 205 | 'description': 'description' 206 | }) 207 | ).on('end', function () { 208 | assert.fileContent([ 209 | ['app/manifest.json', /"permissions"/], 210 | ['app/manifest.json', /"tabs"/], 211 | ['app/manifest.json', /"bookmarks"/], 212 | ['app/manifest.json', /"cookies"/], 213 | ['app/manifest.json', /"history"/], 214 | ['app/manifest.json', /\s+"http:\/\/\*\/\*",\s+"https:\/\/\*\/\*"/], 215 | ]); 216 | done(); 217 | }); 218 | }); 219 | }); 220 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var util = require('util'); 4 | var spawn = require('child_process').spawn; 5 | var yeoman = require('yeoman-generator'); 6 | 7 | module.exports = yeoman.generators.Base.extend({ 8 | constructor: function (args, options, config) { 9 | yeoman.generators.Base.apply(this, arguments); 10 | 11 | // setup the test-framework property, Gruntfile template will need this 12 | this.option('test-framework', { 13 | desc: 'Test framework to be invoked', 14 | type: String, 15 | defaults: 'mocha' 16 | }); 17 | this.testFramework = this.options['test-framework']; 18 | 19 | // setup the coffee property 20 | this.option('coffee', { 21 | desc: 'Use CoffeeScript', 22 | type: Boolean, 23 | defaults: false 24 | }); 25 | this.coffee = this.options.coffee; 26 | 27 | // setup the coffee property 28 | this.option('compass', { 29 | desc: 'Use Compass', 30 | type: Boolean, 31 | defaults: false 32 | }); 33 | this.compass = this.options.compass; 34 | 35 | // load package 36 | this.pkg = require('../package.json'); 37 | 38 | // set source root path to templates 39 | this.sourceRoot(path.join(__dirname, 'templates')); 40 | 41 | // init extension manifest data 42 | this.manifest = { 43 | permissions:{} 44 | }; 45 | 46 | // copy script with js or coffee extension 47 | this.copyjs = function copyjs(src, dest) { 48 | var ext = this.coffee ? '.coffee' : '.js'; 49 | 50 | src = src + ext; 51 | dest = dest ? dest + ext : src; 52 | this.copy((this.coffee ? 'coffees/' : 'scripts/') + src, 'app/scripts/' + dest); 53 | }; 54 | }, 55 | 56 | askFor: function (argument) { 57 | var cb = this.async(); 58 | 59 | var prompts = [ 60 | { 61 | name: 'name', 62 | message: 'What would you like to call this extension?', 63 | default: (this.appname) ? this.appname : 'myChromeApp' 64 | }, 65 | { 66 | name: 'description', 67 | message: 'How would you like to describe this extension?', 68 | default: 'My Chrome Extension' 69 | }, 70 | { 71 | type: 'list', 72 | name: 'action', 73 | message: 'Would you like to use UI Action?', 74 | choices:[ 75 | 'No', 76 | 'Browser', 77 | 'Page' 78 | ] 79 | }, 80 | { 81 | type: 'checkbox', 82 | name: 'uifeatures', 83 | message: 'Would you like more UI Features?', 84 | choices: [{ 85 | value: 'options', 86 | name: 'Options Page', 87 | checked: false 88 | }, { 89 | value: 'contentscript', 90 | name: 'Content Scripts', 91 | checked: false 92 | }, { 93 | value: 'omnibox', 94 | name: 'Omnibox', 95 | checked: false 96 | }] 97 | }, 98 | { 99 | type: 'checkbox', 100 | name: 'permissions', 101 | message: 'Would you like to use permissions?', 102 | choices: [{ 103 | value: 'tabs', 104 | name: 'Tabs', 105 | checked: false 106 | }, { 107 | value: 'bookmark', 108 | name: 'Bookmarks', 109 | checked: false 110 | }, { 111 | value: 'cookie', 112 | name: 'Cookies', 113 | checked: false 114 | }, { 115 | value: 'history', 116 | name: 'History', 117 | checked: false 118 | }, { 119 | value: 'management', 120 | name: 'Management', 121 | checked: false 122 | }] 123 | } 124 | ]; 125 | 126 | this.prompt( prompts , function(answers) { 127 | var isChecked = function (choices, value) { return choices.indexOf(value) > -1; }; 128 | 129 | this.appname = this.manifest.name = answers.name.replace(/\"/g, '\\"'); 130 | this.manifest.description = answers.description.replace(/\"/g, '\\"'); 131 | this.manifest.action = (answers.action === 'No') ? 0 : (answers.action === 'Browser') ? 1 : 2; 132 | this.manifest.options = isChecked(answers.uifeatures, 'options'); 133 | this.manifest.omnibox = isChecked(answers.uifeatures, 'omnibox'); 134 | this.manifest.contentscript = isChecked(answers.uifeatures, 'contentscript'); 135 | this.manifest.permissions.tabs = isChecked(answers.permissions, 'tabs'); 136 | this.manifest.permissions.bookmarks = isChecked(answers.permissions, 'bookmarks'); 137 | this.manifest.permissions.cookies = isChecked(answers.permissions, 'cookies'); 138 | this.manifest.permissions.history = isChecked(answers.permissions, 'history'); 139 | this.manifest.permissions.management = isChecked(answers.permissions, 'management'); 140 | 141 | cb(); 142 | }.bind(this)); 143 | }, 144 | 145 | app: function () { 146 | this.mkdir('app'); 147 | this.mkdir('app/bower_components'); 148 | }, 149 | 150 | gruntfile: function () { 151 | this.template('Gruntfile.js'); 152 | }, 153 | 154 | packageJSON: function () { 155 | this.template('_package.json', 'package.json'); 156 | }, 157 | 158 | git: function () { 159 | this.copy('gitignore', '.gitignore'); 160 | this.copy('gitattributes', '.gitattributes'); 161 | }, 162 | 163 | bower: function () { 164 | this.copy('bowerrc', '.bowerrc'); 165 | this.copy('_bower.json', 'bower.json'); 166 | }, 167 | 168 | jshint: function () { 169 | this.copy('jshintrc', '.jshintrc'); 170 | }, 171 | 172 | editorConfig: function () { 173 | this.copy('editorconfig', '.editorconfig'); 174 | }, 175 | 176 | manifest: function () { 177 | var manifest = {}; 178 | var permissions = []; 179 | var items = []; 180 | 181 | // add browser / page action field 182 | if (this.manifest.action > 0) { 183 | var action = { 184 | default_icon: { 19: 'images/icon-19.png', 38: 'images/icon-38.png' }, 185 | default_title: this.manifest.name, 186 | default_popup: 'popup.html' 187 | }; 188 | var title = (this.manifest.action === 1) ? 'browser_action' : 'page_action'; 189 | manifest[title] = JSON.stringify(action, null, 2).replace(/\n/g, '\n '); 190 | } 191 | 192 | // add options page field. 193 | if (this.manifest.options) { 194 | manifest.options_page = '"options.html"'; 195 | } 196 | 197 | // add omnibox keyword field. 198 | if (this.manifest.omnibox) { 199 | manifest.omnibox = JSON.stringify({ keyword: this.manifest.name }, null, 2).replace(/\n/g, '\n '); 200 | } 201 | 202 | // add contentscript field. 203 | if (this.manifest.contentscript) { 204 | var contentscript = [{ 205 | matches: ['http://*/*', 'https://*/*'], 206 | js: ['scripts/contentscript.js'], 207 | run_at: 'document_end', 208 | all_frames: false 209 | }]; 210 | 211 | manifest.content_scripts = JSON.stringify(contentscript, null, 2).replace(/\n/g, '\n '); 212 | } 213 | 214 | // add generate permission field. 215 | for (var p in this.manifest.permissions) { 216 | if (this.manifest.permissions[p]) { 217 | permissions.push(p); 218 | } 219 | } 220 | 221 | // add generic match pattern field. 222 | if (this.manifest.permissions.tabs) { 223 | permissions.push('http://*/*'); 224 | permissions.push('https://*/*'); 225 | } 226 | 227 | if (permissions.length > 0) { 228 | manifest.permissions = JSON.stringify(permissions, null, 2).replace(/\n/g, '\n '); 229 | } 230 | 231 | for (var i in manifest) { 232 | items.push([' "', i, '": ', manifest[i]].join('')); 233 | } 234 | 235 | this.manifest.items = (items.length > 0) ? ',\n' + items.join(',\n') : ''; 236 | 237 | this.template('manifest.json', 'app/manifest.json'); 238 | }, 239 | 240 | actions: function () { 241 | if (this.manifest.action === 0) { 242 | return; 243 | } 244 | 245 | this.copy('popup.html', 'app/popup.html'); 246 | this.copyjs('popup'); 247 | this.copy('images/icon-19.png', 'app/images/icon-19.png'); 248 | this.copy('images/icon-38.png', 'app/images/icon-38.png'); 249 | }, 250 | 251 | eventpage: function () { 252 | var backgroundjs = 'background'; 253 | 254 | if (this.manifest.action === 2) { 255 | backgroundjs = 'background.pageaction'; 256 | } else if (this.manifest.action === 1) { 257 | backgroundjs = 'background.browseraction'; 258 | } 259 | 260 | this.copyjs(backgroundjs, 'background'); 261 | this.copyjs('chromereload'); 262 | }, 263 | 264 | options: function () { 265 | if (!this.manifest.options) { 266 | return; 267 | } 268 | 269 | this.copy('options.html', 'app/options.html'); 270 | this.copyjs('options'); 271 | }, 272 | 273 | contentscript: function () { 274 | if (!this.manifest.contentscript) { 275 | return; 276 | } 277 | 278 | this.copyjs('contentscript'); 279 | }, 280 | 281 | mainStylesheet: function () { 282 | if (this.manifest.action === 0 && !this.manifest.options) { 283 | return; 284 | } 285 | 286 | var css = 'styles/main.' + (this.compass ? 's' : '') + 'css'; 287 | this.copy(css, 'app/' + css); 288 | }, 289 | 290 | assets: function () { 291 | this.template('_locales/en/messages.json', 'app/_locales/en/messages.json'); 292 | this.copy('images/icon-16.png', 'app/images/icon-16.png'); 293 | this.copy('images/icon-128.png', 'app/images/icon-128.png'); 294 | }, 295 | 296 | install: function () { 297 | this.on('end', function () { 298 | this.invoke(this.options['test-framework'], { 299 | options: { 300 | 'skip-message': this.options['skip-install-message'], 301 | 'skip-install': this.options['skip-install'], 302 | 'coffee': this.options.coffee 303 | } 304 | }); 305 | 306 | if (!this.options['skip-install']) { 307 | this.installDependencies({ 308 | skipMessage: this.options['skip-install-message'], 309 | skipInstall: this.options['skip-install'] 310 | }); 311 | } 312 | }); 313 | } 314 | }); 315 | -------------------------------------------------------------------------------- /app/templates/Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %> 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | // Configurable paths 19 | var config = { 20 | app: 'app', 21 | dist: 'dist' 22 | }; 23 | 24 | grunt.initConfig({ 25 | 26 | // Project settings 27 | config: config, 28 | 29 | // Watches files for changes and runs tasks based on the changed files 30 | watch: { 31 | bower: { 32 | files: ['bower.json'], 33 | tasks: ['bowerInstall'] 34 | },<%if (coffee) { %> 35 | coffee: { 36 | files: ['<%%= config.app %>/scripts/{,*/}*.{coffee,litcoffee,coffee.md}'], 37 | tasks: ['coffee:chrome'], 38 | options: { 39 | livereload: '<%%= connect.options.livereload %>' 40 | } 41 | },<%} else { %> 42 | js: { 43 | files: ['<%%= config.app %>/scripts/{,*/}*.js'], 44 | tasks: ['jshint'], 45 | options: { 46 | livereload: '<%%= connect.options.livereload %>' 47 | } 48 | },<% } %><% if (compass) { %> 49 | compass: { 50 | files: ['<%%= config.app %>/styles/{,*/}*.{scss,sass}'], 51 | tasks: ['compass:chrome'] 52 | },<% } %> 53 | gruntfile: { 54 | files: ['Gruntfile.js'] 55 | }, 56 | styles: { 57 | files: ['<%%= config.app %>/styles/{,*/}*.css'], 58 | tasks: [], 59 | options: { 60 | livereload: '<%%= connect.options.livereload %>' 61 | } 62 | }, 63 | livereload: { 64 | options: { 65 | livereload: '<%%= connect.options.livereload %>' 66 | }, 67 | files: [ 68 | '<%%= config.app %>/*.html', 69 | '<%%= config.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 70 | '<%%= config.app %>/manifest.json', 71 | '<%%= config.app %>/_locales/{,*/}*.json' 72 | ] 73 | } 74 | }, 75 | 76 | // Grunt server and debug server setting 77 | connect: { 78 | options: { 79 | port: 9000, 80 | livereload: 35729, 81 | // change this to '0.0.0.0' to access the server from outside 82 | hostname: 'localhost' 83 | }, 84 | chrome: { 85 | options: { 86 | open: false, 87 | base: [ 88 | '<%%= config.app %>' 89 | ] 90 | } 91 | }, 92 | test: { 93 | options: { 94 | open: false, 95 | base: [ 96 | 'test', 97 | '<%%= config.app %>' 98 | ] 99 | } 100 | } 101 | }, 102 | 103 | // Empties folders to start fresh 104 | clean: { 105 | chrome: { 106 | }, 107 | dist: { 108 | files: [{ 109 | dot: true, 110 | src: [ 111 | '<%%= config.dist %>/*', 112 | '!<%%= config.dist %>/.git*' 113 | ] 114 | }] 115 | } 116 | }, 117 | 118 | // Make sure code styles are up to par and there are no obvious mistakes 119 | jshint: { 120 | options: { 121 | jshintrc: '.jshintrc', 122 | reporter: require('jshint-stylish') 123 | }, 124 | all: [ 125 | 'Gruntfile.js', 126 | '<%%= config.app %>/scripts/{,*/}*.js', 127 | '!<%%= config.app %>/scripts/vendor/*', 128 | 'test/spec/{,*/}*.js' 129 | ] 130 | },<% if (testFramework === 'mocha') { %> 131 | mocha: { 132 | all: { 133 | options: { 134 | run: true, 135 | urls: ['http://localhost:<%%= connect.options.port %>/index.html'] 136 | } 137 | } 138 | },<% } else if (testFramework === 'jasmine') { %> 139 | jasmine: { 140 | all: { 141 | options: { 142 | specs: 'test/spec/{,*/}*.js' 143 | } 144 | } 145 | },<% } %><% if (coffee) { %> 146 | 147 | // Compiles CoffeeScript to JavaScript 148 | coffee: { 149 | chrome: { 150 | files: [{ 151 | expand: true, 152 | cwd: '<%%= config.app %>/scripts', 153 | src: '{,*/}*.{coffee,litcoffee,coffee.md}', 154 | dest: '<%%= config.app %>/scripts', 155 | ext: '.js' 156 | }] 157 | }, 158 | dist: { 159 | files: [{ 160 | expand: true, 161 | cwd: '<%%= config.app %>/scripts', 162 | src: '{,*/}*.{coffee,litcoffee,coffee.md}', 163 | dest: '<%%= config.app %>/scripts', 164 | ext: '.js' 165 | }] 166 | }, 167 | test: { 168 | files: [{ 169 | expand: true, 170 | cwd: 'test/spec', 171 | src: '{,*/}*.coffee', 172 | dest: './spec', 173 | ext: '.js' 174 | }] 175 | } 176 | },<% } %><% if (compass) { %> 177 | 178 | // Compiles Sass to CSS and generates necessary files if requested 179 | compass: { 180 | options: { 181 | sassDir: '<%%= config.app %>/styles', 182 | cssDir: '<%%= config.dist %>/styles', 183 | generatedImagesDir: '<%%= config.dist %>/images/generated', 184 | imagesDir: '<%%= config.app %>/images', 185 | javascriptsDir: '<%%= config.app %>/scripts', 186 | fontsDir: '<%%= config.app %>/styles/fonts', 187 | importPath: '<%%= config.app %>/bower_components', 188 | httpImagesPath: '/images', 189 | httpGeneratedImagesPath: '/images/generated', 190 | httpFontsPath: '/styles/fonts', 191 | relativeAssets: false, 192 | assetCacheBuster: false 193 | }, 194 | chrome: { 195 | options: { 196 | cssDir: '<%%= config.app %>/styles', 197 | generatedImagesDir: '<%%= config.app %>/images/generated', 198 | debugInfo: true 199 | } 200 | }, 201 | dist: { 202 | }, 203 | test: { 204 | } 205 | },<% } %> 206 | 207 | // Automatically inject Bower components into the HTML file 208 | bowerInstall: { 209 | app: { 210 | src: [ 211 | '<%%= config.app %>/*.html' 212 | ] 213 | }<% if (compass) { %>, 214 | sass: { 215 | src: ['<%%= config.app %>/styles/{,*/}*.{scss,sass}'], 216 | ignorePath: '<%%= config.app %>/bower_components/' 217 | }<% } %> 218 | }, 219 | 220 | // Reads HTML for usemin blocks to enable smart builds that automatically 221 | // concat, minify and revision files. Creates configurations in memory so 222 | // additional tasks can operate on them 223 | useminPrepare: { 224 | options: { 225 | dest: '<%%= config.dist %>' 226 | }, 227 | html: [ 228 | '<%%= config.app %>/popup.html', 229 | '<%%= config.app %>/options.html' 230 | ] 231 | }, 232 | 233 | // Performs rewrites based on rev and the useminPrepare configuration 234 | usemin: { 235 | options: { 236 | assetsDirs: ['<%%= config.dist %>', '<%%= config.dist %>/images'] 237 | }, 238 | html: ['<%%= config.dist %>/{,*/}*.html'], 239 | css: ['<%%= config.dist %>/styles/{,*/}*.css'] 240 | }, 241 | 242 | // The following *-min tasks produce minifies files in the dist folder 243 | imagemin: { 244 | dist: { 245 | files: [{ 246 | expand: true, 247 | cwd: '<%%= config.app %>/images', 248 | src: '{,*/}*.{gif,jpeg,jpg,png}', 249 | dest: '<%%= config.dist %>/images' 250 | }] 251 | } 252 | }, 253 | 254 | svgmin: { 255 | dist: { 256 | files: [{ 257 | expand: true, 258 | cwd: '<%%= config.app %>/images', 259 | src: '{,*/}*.svg', 260 | dest: '<%%= config.dist %>/images' 261 | }] 262 | } 263 | }, 264 | 265 | htmlmin: { 266 | dist: { 267 | options: { 268 | // removeCommentsFromCDATA: true, 269 | // collapseWhitespace: true, 270 | // collapseBooleanAttributes: true, 271 | // removeAttributeQuotes: true, 272 | // removeRedundantAttributes: true, 273 | // useShortDoctype: true, 274 | // removeEmptyAttributes: true, 275 | // removeOptionalTags: true 276 | }, 277 | files: [{ 278 | expand: true, 279 | cwd: '<%%= config.app %>', 280 | src: '*.html', 281 | dest: '<%%= config.dist %>' 282 | }] 283 | } 284 | }, 285 | 286 | // By default, your `index.html`'s will take care of 287 | // minification. These next options are pre-configured if you do not wish 288 | // to use the Usemin blocks. 289 | // cssmin: { 290 | // dist: { 291 | // files: { 292 | // '<%%= config.dist %>/styles/main.css': [ 293 | // '<%%= config.app %>/styles/{,*/}*.css' 294 | // ] 295 | // } 296 | // } 297 | // }, 298 | // uglify: { 299 | // dist: { 300 | // files: { 301 | // '<%%= config.dist %>/scripts/scripts.js': [ 302 | // '<%%= config.dist %>/scripts/scripts.js' 303 | // ] 304 | // } 305 | // } 306 | // }, 307 | // concat: { 308 | // dist: {} 309 | // }, 310 | 311 | // Copies remaining files to places other tasks can use 312 | copy: { 313 | dist: { 314 | files: [{ 315 | expand: true, 316 | dot: true, 317 | cwd: '<%%= config.app %>', 318 | dest: '<%%= config.dist %>', 319 | src: [ 320 | '*.{ico,png,txt}', 321 | 'images/{,*/}*.{webp,gif}', 322 | '{,*/}*.html', 323 | 'styles/{,*/}*.css', 324 | 'styles/fonts/{,*/}*.*', 325 | '_locales/{,*/}*.json', 326 | ] 327 | }] 328 | } 329 | }, 330 | 331 | // Run some tasks in parallel to speed up build process 332 | concurrent: { 333 | chrome: [<%if (coffee) { %> 334 | 'coffee:chrome',<% } if (compass) { %> 335 | 'compass:chrome',<% } %> 336 | ], 337 | dist: [<% if (coffee) { %> 338 | 'coffee:dist',<% } if (compass) { %> 339 | 'compass:dist',<% } %> 340 | 'imagemin', 341 | 'svgmin' 342 | ], 343 | test: [<%if (coffee) { %> 344 | 'coffee:test',<% } if (compass) { %> 345 | 'compass:test',<% } %> 346 | ] 347 | }, 348 | 349 | // Auto buildnumber, exclude debug files. smart builds that event pages 350 | chromeManifest: { 351 | dist: { 352 | options: { 353 | buildnumber: true, 354 | indentSize: 2, 355 | background: { 356 | target: 'scripts/background.js', 357 | exclude: [ 358 | 'scripts/chromereload.js' 359 | ] 360 | } 361 | }, 362 | src: '<%%= config.app %>', 363 | dest: '<%%= config.dist %>' 364 | } 365 | }, 366 | 367 | // Compres dist files to package 368 | compress: { 369 | dist: { 370 | options: { 371 | archive: function() { 372 | var manifest = grunt.file.readJSON('app/manifest.json'); 373 | return 'package/<%= appname %>-' + manifest.version + '.zip'; 374 | } 375 | }, 376 | files: [{ 377 | expand: true, 378 | cwd: 'dist/', 379 | src: ['**'], 380 | dest: '' 381 | }] 382 | } 383 | } 384 | }); 385 | 386 | grunt.registerTask('debug', function () { 387 | grunt.task.run([ 388 | 'jshint', 389 | 'concurrent:chrome', 390 | 'connect:chrome', 391 | 'watch' 392 | ]); 393 | }); 394 | 395 | grunt.registerTask('test', [ 396 | 'connect:test',<% if (testFramework === 'mocha') { %> 397 | 'mocha'<% } else if (testFramework === 'jasmine') { %> 398 | 'jasmine'<% } %> 399 | ]); 400 | 401 | grunt.registerTask('build', [ 402 | 'clean:dist', 403 | 'chromeManifest:dist', 404 | 'useminPrepare', 405 | 'concurrent:dist', 406 | <% if (manifest.action === 0) { %>// No UI feature selected, cssmin task will be commented 407 | // <% } %>'cssmin', 408 | 'concat', 409 | 'uglify', 410 | 'copy', 411 | 'usemin', 412 | 'compress' 413 | ]); 414 | 415 | grunt.registerTask('default', [ 416 | 'jshint', 417 | 'test', 418 | 'build' 419 | ]); 420 | }; 421 | --------------------------------------------------------------------------------