├── .gitignore ├── LICENSE ├── demo.gif ├── gulpfile.babel.js ├── package.json ├── readme.md └── src ├── app └── index.js └── build ├── index.js └── templates ├── _locales └── en │ └── messages.json ├── images ├── icon-128.png ├── icon-16.png ├── icon-19.png └── icon-38.png ├── manifest.json ├── options.html ├── popup.html ├── react.babelrc ├── scripts ├── es5 │ └── background.js └── es6 │ └── background.js ├── styles └── index.css └── tasks ├── es5 ├── assets.js ├── browserify.js ├── build.js ├── clean.js ├── compress.js ├── config.js ├── develop.js └── manifest.js └── es6 ├── assets.js ├── browserify.js ├── build.js ├── clean.js ├── compress.js ├── config.js ├── develop.js └── manifest.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | generators 17 | 18 | .yo-rc.json 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ironSource 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironSource/node-generator-chrome-ninja/495a3cec94d96b66bdbdf3dc96827449a3b4515e/demo.gif -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , gulpif = require('gulp-if') 3 | , babel = require('gulp-babel') 4 | , trash = require('trash') 5 | , pathExists = require('path-exists') 6 | 7 | gulp.task('trash', (done) => { 8 | if (pathExists.sync('./generators')) trash(['./generators'], done) 9 | else setImmediate(done) 10 | }) 11 | 12 | gulp.task('build', ['trash'], () => { 13 | let condition = (vinyl) => { 14 | let file = vinyl.relative 15 | return file.slice(-3) === '.js' && file.indexOf('templates') < 0 16 | } 17 | 18 | return gulp.src('src/**/*') 19 | .pipe(gulpif(condition, babel())) 20 | .pipe(gulp.dest('generators')) 21 | }) 22 | 23 | gulp.task('default', ['build']) 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-chrome-ninja", 3 | "version": "2.0.0", 4 | "description": "Generator for Chrome extensions", 5 | "main": "generators/app/index.js", 6 | "scripts": { 7 | "build": "gulp build", 8 | "prepublish": "npm run build" 9 | }, 10 | "files": [ 11 | "generators" 12 | ], 13 | "keywords": [ 14 | "yeoman-generator", 15 | "chrome", 16 | "extension" 17 | ], 18 | "author": "Vincent Weevers", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "babel-core": "~5.8.25", 22 | "gulp": "~3.9.0", 23 | "gulp-babel": "~5.2.1", 24 | "gulp-if": "~2.0.0", 25 | "gulp-util": "~3.0.7", 26 | "path-exists": "~2.1.0", 27 | "trash": "~2.0.0" 28 | }, 29 | "dependencies": { 30 | "bootswatch": "3.3.5", 31 | "chalk": "~1.1.1", 32 | "generator-bare-react": "~2.0.1", 33 | "generator-nom": "~2.0.0", 34 | "param-case": "~1.1.1", 35 | "yeoman-generator": "~0.21.1" 36 | }, 37 | "repository": "ironSource/node-generator-chrome-ninja", 38 | "bugs": "https://github.com/ironSource/node-generator-chrome-ninja/issues", 39 | "homepage": "https://github.com/ironSource/node-generator-chrome-ninja" 40 | } 41 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # generator-chrome-ninja 2 | 3 | **Chrome extension generator.** Comes with feature and permission picker, React scaffolding (ES6 or ES5) via [bare-react](https://github.com/ironSource/node-generator-bare-react), [hot reloading](https://github.com/vweevers/livereactload-chrome), [gulp](http://gulpjs.com) tasks (ES6 or ES5), packaging via [nom](https://github.com/ironSource/node-generator-nom), [browserify](https://github.com/substack/node-browserify), [babelify](https://github.com/babel/babelify), [image minification](https://github.com/sindresorhus/gulp-imagemin), l18n, zipping and lastly pre-build configuration with [config-prompt](https://github.com/ironSource/node-config-prompt). 4 | 5 | [![npm status](http://img.shields.io/npm/v/generator-chrome-ninja.svg?style=flat-square)](https://www.npmjs.org/package/generator-chrome-ninja) [![Dependency status](https://img.shields.io/david/ironsource/node-generator-chrome-ninja.svg?style=flat-square)](https://david-dm.org/ironsource/node-generator-chrome-ninja) 6 | 7 | ## demo 8 | 9 | *Note: outdated.* 10 | 11 | ![demo](https://github.com/ironSource/node-generator-chrome-ninja/raw/master/demo.gif) 12 | 13 | ## hot reloading 14 | 15 | ![hot reloading demo](https://github.com/vweevers/livereactload-chrome/raw/master/demo.gif) 16 | 17 | ## usage 18 | 19 | ``` 20 | mkdir my-extension 21 | cd my-extension 22 | yo chrome-ninja 23 | gulp develop 24 | ``` 25 | 26 | Then go to `chrome://extensions`, enable developer mode and load `./dist` as an unpacked extension. 27 | 28 | ## options 29 | 30 | ``` 31 | --help -h # Print the generator's options and usage 32 | --skip-cache # Do not remember prompt answers 33 | --skip-install # Do not automatically install dependencies 34 | ``` 35 | 36 | ## install 37 | 38 | Install Yeoman and generator-chrome-ninja globally with [npm](https://npmjs.org): 39 | 40 | ``` 41 | npm i yo generator-chrome-ninja -g 42 | ``` 43 | 44 | ## changelog 45 | 46 | ### 2.0.0 47 | 48 | - Hot reloading of background scripts, content scripts, popups, and option pages. 49 | - Offer choice between ES5 and ES6, make ES6 modules opt-in. This 50 | applies to extension scripts, React components, the gulpfile and gulp tasks. 51 | - Add ES7 decorator support 52 | - Have version field of `manifest.json` follow `package.json` version 53 | - Pin Babel to 5. We can't move to Babel 6 until [babel-plugin-react-transform#46](https://github.com/gaearon/babel-plugin-react-transform/issues/46) and [livereactload#78](https://github.com/milankinen/livereactload/issues/78) have been resolved. 54 | - Remove all things CSS: live reloading and bundling CSS (parcelify), preprocessing (LESS/SASS). We're likely to move to CSS modules, please follow [#3](https://github.com/ironSource/node-generator-chrome-ninja/issues/3) for updates. Or, for an example of PostCSS postprocessing, check out the [cssnext](https://github.com/ironSource/node-generator-chrome-ninja/tree/feature-cssnext) branch. 55 | - Upgrade to [bare-react](https://github.com/ironSource/node-generator-bare-react) 2.0.0 and [nom](https://github.com/ironSource/node-generator-nom) 2.0.0 56 | 57 | ## license and acknowledgments 58 | 59 | [MIT](http://opensource.org/licenses/MIT) © [ironSource](http://www.ironsrc.com/). Templates and questions adapted from [generator-chrome-extension](https://github.com/yeoman/generator-chrome-extension) under [BSD license](http://opensource.org/licenses/bsd-license.php) © Yeoman. Yeoman icons are [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/) © Yeoman. 60 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | var { Base } = require('yeoman-generator') 2 | 3 | const self = module.exports = class ChromeGenerator extends Base { 4 | constructor(args, options, config) { 5 | super(args, options, config) 6 | } 7 | 8 | build() { 9 | // This doesn't make much sense, until we split build into several subgenerators 10 | this.composeWith('chrome-ext:build', { options: this.options, args: [] }, { 11 | local: require.resolve('../build'), 12 | link: 'strong' 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/build/index.js: -------------------------------------------------------------------------------- 1 | const paramCase = require('param-case') 2 | , bootswatch = require('bootswatch/api/3.json').themes 3 | , bareReact = require.resolve('generator-bare-react') 4 | , colors = require('chalk') 5 | 6 | const { Base } = require('yeoman-generator') 7 | 8 | const PERMISSIONS = 9 | [ 'tabs' 10 | , 'bookmarks' 11 | , 'cookies' 12 | , 'history' 13 | , 'management' ] 14 | 15 | const DEV_DEPENDENCIES = 16 | { 'babelify': '~6.4.0' 17 | , 'browserify': null // use latest 18 | , 'envify': null 19 | , 'trash': '~2.0.0' // promisified as of 3.0 20 | , 'gulp-zip': null 21 | , 'run-sequence': null 22 | , 'through2': null 23 | , 'uglifyify': null 24 | , 'watchify': null 25 | , 'concat-stream': null 26 | , 'path-exists': null 27 | , 'gulp-imagemin': null 28 | , 'config-prompt': '~1.0.0' 29 | , 'in-production': null 30 | , 'vinyl-imitate': '~1.0.0' 31 | , 'add-transforms': '~1.0.0' } 32 | 33 | const REACT_DEV_DEPENDENCIES = 34 | { 'babel-plugin-react-transform': '~1.1.1' 35 | , 'livereactload': '~2.1.0' 36 | , 'react-proxy': '~1.1.1' 37 | , 'livereactload-chrome': '~1.0.2' } 38 | 39 | const MODULE_FORMATS = 40 | { commonjs: { name: 'CommonJS' 41 | , snippet: `const assign = require('object-assign')` } 42 | , es6: { name: 'ES6 modules' 43 | , snippet: `import assign from 'object-assign'` }} 44 | 45 | function bootswatchNames() { 46 | return bootswatch.map(theme => theme.name) 47 | } 48 | 49 | const self = module.exports = class ChromeGenerator extends Base { 50 | // TODO: rename all methods, use priorities 51 | askFor() { 52 | // In case a module was already generated or created 53 | let { name, description } = this.fs.readJSON('package.json', {}) 54 | 55 | // Use existing manifest values as defaults 56 | let { permissions = [], browser_action, page_action, options_page, omnibox 57 | , content_scripts } 58 | = this.fs.readJSON('app/manifest.json', {}) 59 | 60 | let questions = 61 | [ { name: 'name' 62 | , message: 'What would you like to call this extension?' 63 | , default: paramCase(name || this.appname || 'my-chrome-extension') 64 | , validate: (v) => paramCase(v).length ? true : 'You have to provide a name' 65 | , filter: paramCase 66 | },{ name: 'description' 67 | , message: 'How would you like to describe this extension?' 68 | , default: description || 'My Chrome Extension' 69 | , validate: (v) => v.trim().length ? true : 'You have to provide a description' 70 | , filter: (s) => s.trim() 71 | },{ type: 'list' 72 | , name: 'action' 73 | , message: 'Would you like to use UI Action?' 74 | , default: browser_action ? 'Browser' : page_action ? 'Page' : 'No' 75 | , choices: [ 'No', 'Browser', 'Page' ] 76 | },{ type: 'checkbox' 77 | , name: 'uifeatures' 78 | , message: 'Would you like more UI Features?' 79 | , choices: [ { value: 'options', name: 'Options Page' 80 | , checked: !!options_page } 81 | , { value: 'contentscript', name: 'Content Scripts' 82 | , checked: !!content_scripts } 83 | , { value: 'omnibox', name: 'Omnibox' 84 | , checked: !!omnibox }] 85 | },{ type: 'confirm' 86 | , name: 'bootstrap' 87 | , store: true 88 | , message: 'Would you like to use Bootstrap?' 89 | , default: false 90 | },{ type: 'list' 91 | , name: 'bootstrapTheme' 92 | , message: 'Which Bootstrap theme would you like?' 93 | , when: (answers) => answers.bootstrap === true 94 | , default: 'none' 95 | , store: true 96 | , choices: [ 'none', 'default' ].concat(bootswatchNames()) 97 | },{ type: 'checkbox' 98 | , name: 'permissions' 99 | , message: 'Which permissions do you need?' 100 | , choices: PERMISSIONS.map(name => { 101 | let checked = permissions.indexOf(name) >= 0 102 | return { value: name, name, checked } 103 | }) 104 | },{ type: 'confirm' 105 | , name: 'esnext' 106 | , message: 'Do you prefer ES6 over ES5?' 107 | , default: true 108 | , store: true 109 | },{ type: 'list' 110 | , name: 'modules' 111 | , when: (answers) => answers.esnext 112 | , message: 'Which module format do you prefer?' 113 | , default: 'commonjs' 114 | , store: true 115 | , choices: Object.keys(MODULE_FORMATS).map(key => { 116 | let { name, snippet } = MODULE_FORMATS[key] 117 | return { name: `${name} ${colors.gray(snippet)}`, value: key } 118 | }) 119 | } 120 | ] 121 | 122 | let done = this.async() 123 | 124 | this.prompt(questions, (answers) => { 125 | let isChecked = (choices, value) => choices.indexOf(value) >= 0 126 | , escape = (s) => s.replace(/\"/g, '\\"') 127 | , name = this.appname = escape(answers.name) 128 | , description = escape(answers.description) 129 | 130 | // TODO: set display value for question 131 | if (answers.bootstrapTheme === 'none') answers.bootstrapTheme = null 132 | 133 | this.ctx = answers 134 | this.manifest = { name, description, permissions: {} } 135 | 136 | this.manifest.action = answers.action === 'No' ? 0 : answers.action === 'Browser' ? 1 : 2 137 | this.manifest.options = isChecked(answers.uifeatures, 'options') 138 | this.manifest.omnibox = isChecked(answers.uifeatures, 'omnibox') 139 | this.manifest.contentscript = isChecked(answers.uifeatures, 'contentscript') 140 | this.manifest.permissions.tabs = isChecked(answers.permissions, 'tabs') 141 | this.manifest.permissions.bookmarks = isChecked(answers.permissions, 'bookmarks') 142 | this.manifest.permissions.cookies = isChecked(answers.permissions, 'cookies') 143 | this.manifest.permissions.history = isChecked(answers.permissions, 'history') 144 | this.manifest.permissions.management = isChecked(answers.permissions, 'management') 145 | 146 | let dependencies = {} 147 | , devDependencies = { ...DEV_DEPENDENCIES } 148 | 149 | if (this.manifest.action > 0 || this.manifest.options || this.manifest.contentscript) { 150 | // Put additional babelrc in app dir, with livereactload config 151 | this._copy('react.babelrc', 'app/.babelrc') 152 | 153 | // Add livereactload dependencies 154 | devDependencies = { ...devDependencies, ...REACT_DEV_DEPENDENCIES } 155 | } 156 | 157 | let tasks = answers.esnext 158 | ? this.templatePath('tasks/es6') 159 | : this.templatePath('tasks/es5') 160 | 161 | this.composeWith('nom' 162 | , { options: { name 163 | , description 164 | , skipInstall: this.options.skipInstall 165 | , skipCache: this.options.skipCache 166 | , esnext: answers.esnext 167 | , modules: answers.modules 168 | , enable: [ 'gulp' 169 | , 'npm' ] 170 | , gulp: { tasks: tasks + '/**/*' 171 | , ctx: this.ctx } 172 | , npm: { dependencies 173 | , main: false 174 | , devDependencies }}} 175 | , { local: require.resolve('generator-nom') 176 | , link: 'strong' }) 177 | 178 | done() 179 | }) 180 | } 181 | 182 | _addReact(options) { 183 | let { esnext, modules } = this.ctx 184 | let link = { local: bareReact, link: 'strong' }, style 185 | 186 | options = 187 | { skipInstall: this.options.skipInstall 188 | , skipCache: this.options.skipCache 189 | , esnext 190 | , modules 191 | , ...options } 192 | 193 | this.composeWith('bare-react', { options }, link) 194 | } 195 | 196 | // TODO: remove legacy code and just stringify manifest as a whole 197 | manifest() { 198 | let manifest = {} 199 | , permissions = [] 200 | , items = [] 201 | 202 | // add browser / page action field 203 | if (this.manifest.action > 0) { 204 | let action = { default_icon: { 19: 'images/icon-19.png' 205 | , 38: 'images/icon-38.png' } 206 | , default_title: this.manifest.name 207 | , default_popup: 'lib/popup/popup.html' } 208 | 209 | let type = this.manifest.action === 1 ? 'browser_action' : 'page_action' 210 | 211 | manifest[type] = JSON.stringify(action, null, 2).replace(/\n/g, '\n ') 212 | 213 | this._addReact 214 | ({ type: 'app' 215 | , dest: 'app/lib/popup' 216 | , name: 'Popup' 217 | , router: false 218 | , bootstrap: this.ctx.bootstrap 219 | , children: [ 'PopupNinja' ] }) 220 | } 221 | 222 | // add options page field. 223 | if (this.manifest.options) { 224 | let options_ui = { page: 'lib/options/options.html', chrome_style: true } 225 | 226 | manifest.options_page = '"lib/options/options.html"' 227 | manifest.options_ui = JSON.stringify(options_ui, null, 2).replace(/\n/g, '\n ') 228 | 229 | this._addReact 230 | ({ type: 'app' 231 | , bootstrap: this.ctx.bootstrap 232 | , dest: 'app/lib/options' 233 | , name: 'Options' }) 234 | } 235 | 236 | // add omnibox keyword field. 237 | if (this.manifest.omnibox) { 238 | let omnibox = { keyword: this.manifest.name } 239 | manifest.omnibox = JSON.stringify(omnibox, null, 2).replace(/\n/g, '\n ') 240 | } 241 | 242 | // add contentscript field. 243 | if (this.manifest.contentscript) { 244 | let contentscript = { matches: ['http://*/*', 'https://*/*'] 245 | , js: ['lib/content-script/index.js'] 246 | , run_at: 'document_end' 247 | , all_frames: false } 248 | 249 | manifest.content_scripts = 250 | JSON.stringify([contentscript], null, 2).replace(/\n/g, '\n ') 251 | 252 | this._addReact 253 | ({ type: 'app' 254 | , dest: 'app/lib/content-script' 255 | , name: 'ContentScript' 256 | , append: true 257 | , router: false 258 | , bootstrap: false }) 259 | } 260 | 261 | // add generate permission field. 262 | for (let p in this.manifest.permissions) { 263 | if (this.manifest.permissions[p]) { 264 | permissions.push(p) 265 | } 266 | } 267 | 268 | // add generic match pattern field. 269 | if (this.manifest.permissions.tabs) { 270 | permissions.push('http://*/*') 271 | permissions.push('https://*/*') 272 | } 273 | 274 | if (permissions.length > 0) { 275 | manifest.permissions = 276 | JSON.stringify(permissions, null, 2).replace(/\n/g, '\n ') 277 | } 278 | 279 | for (let k in manifest) { 280 | items.push([' "', k, '": ', manifest[k]].join('')) 281 | } 282 | 283 | this.manifest.items = items.length > 0 ? ',\n' + items.join(',\n') : '' 284 | 285 | this._copyTpl('manifest.json', 'app/manifest.json', this.manifest) 286 | } 287 | 288 | actions() { 289 | if (this.manifest.action !== 0) { 290 | this._copy('popup.html', 'app/lib/popup/popup.html') 291 | this._copy('images/icon-19.png', 'app/images/icon-19.png') 292 | this._copy('images/icon-38.png', 'app/images/icon-38.png') 293 | this._copyCSS('app/lib/popup') 294 | } 295 | } 296 | 297 | eventpage() { 298 | let pageName = 'background page' 299 | , withAction = false 300 | , action = this.manifest.action 301 | 302 | if (action === 2) { 303 | pageName = 'event page for Page Action' 304 | withAction = true 305 | } else if (action === 1) { 306 | pageName = 'event page for Browser Action' 307 | withAction = true 308 | } 309 | 310 | let ctx = { pageName, withAction } 311 | let dir = this.ctx.esnext ? 'es6' : 'es5' 312 | 313 | this._copyJS(`${dir}/background.js`, 'app/lib/background/index.js', ctx) 314 | } 315 | 316 | optionsHtml() { 317 | if (this.manifest.options) { 318 | this._copy('options.html', 'app/lib/options/options.html') 319 | this._copyCSS('app/lib/options') 320 | } 321 | } 322 | 323 | contentscript() { 324 | if (this.manifest.contentscript) { 325 | this._copyCSS('app/lib/content-script') 326 | } 327 | } 328 | 329 | assets() { 330 | let messages = '_locales/en/messages.json' 331 | 332 | this._copyTpl(messages, `app/${messages}`, this.manifest) 333 | this._copy('images/icon-16.png', 'app/images/icon-16.png') 334 | this._copy('images/icon-128.png', 'app/images/icon-128.png') 335 | } 336 | 337 | _copy(src, dest = src) { 338 | this.fs.copy(this.templatePath(src), this.destinationPath(dest)) 339 | } 340 | 341 | _copyTpl(src, dest = src, ctx = {}) { 342 | this.fs.copyTpl(this.templatePath(src), this.destinationPath(dest), ctx) 343 | } 344 | 345 | _copyJS(src, dest = src, ctx = {}) { 346 | this._copyTpl('scripts/' + src, dest, ctx) 347 | } 348 | 349 | _copyCSS(dest) { 350 | this._copyTpl('styles/index.css', dest + '/index.css', this.ctx) 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/build/templates/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "<%= name %>", 4 | "description": "The name of the application" 5 | }, 6 | "appDescription": { 7 | "message": "<%= description %>", 8 | "description": "The description of the application" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/build/templates/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironSource/node-generator-chrome-ninja/495a3cec94d96b66bdbdf3dc96827449a3b4515e/src/build/templates/images/icon-128.png -------------------------------------------------------------------------------- /src/build/templates/images/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironSource/node-generator-chrome-ninja/495a3cec94d96b66bdbdf3dc96827449a3b4515e/src/build/templates/images/icon-16.png -------------------------------------------------------------------------------- /src/build/templates/images/icon-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironSource/node-generator-chrome-ninja/495a3cec94d96b66bdbdf3dc96827449a3b4515e/src/build/templates/images/icon-19.png -------------------------------------------------------------------------------- /src/build/templates/images/icon-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironSource/node-generator-chrome-ninja/495a3cec94d96b66bdbdf3dc96827449a3b4515e/src/build/templates/images/icon-38.png -------------------------------------------------------------------------------- /src/build/templates/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_appName__", 3 | "manifest_version": 2, 4 | "description": "__MSG_appDescription__", 5 | "icons": { 6 | "16": "images/icon-16.png", 7 | "128": "images/icon-128.png" 8 | }, 9 | "default_locale": "en", 10 | "background": { 11 | "scripts": [ 12 | "lib/background/index.js" 13 | ] 14 | }<%- items %> 15 | } 16 | -------------------------------------------------------------------------------- /src/build/templates/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/build/templates/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/build/templates/react.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "blacklist": [ "regenerator" ], 3 | "optional": [ 4 | "es7.objectRestSpread", 5 | "es7.classProperties", 6 | "es7.decorators" 7 | ], 8 | "env": { 9 | "development": { 10 | "plugins": [ 11 | "react-transform" 12 | ], 13 | "extra": { 14 | "react-transform": { 15 | "transforms": [{ 16 | "transform": "livereactload/babel-transform", 17 | "imports": ["react"] 18 | }] 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/build/templates/scripts/es5/background.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EXAMPLE_FLAG = process.env.EXAMPLE_FLAG 4 | , EXAMPLE_STRING = process.env.EXAMPLE_STRING 5 | 6 | chrome.runtime.onInstalled.addListener(function(details) { 7 | console.log('Previous version is %o', details.previousVersion) 8 | }) 9 | 10 | <% if (withAction) { -%> 11 | chrome.browserAction.setBadgeText({ text: 'ninja' }) 12 | <% } -%> 13 | 14 | var ascii = 15 | [ "" 16 | , " ____ _ ___ _ ___ _" 17 | , " / __ \\/ / __ \\ / / __ `/" 18 | , " / / / / / / / / / / /_/ /" 19 | , "/_/ /_/_/_/ /_/_/ /\\__,_/" 20 | ," /___/ " ].join('\n') 21 | 22 | console.log('%c' + ascii + '%c\n\n%s', 'color: #ddd', '', 'This is the <%= pageName %>.') 23 | console.log('Configuration: exampleFlag is %o, exampleString is %o', EXAMPLE_FLAG, EXAMPLE_STRING) 24 | -------------------------------------------------------------------------------- /src/build/templates/scripts/es6/background.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EXAMPLE_FLAG = process.env.EXAMPLE_FLAG 4 | , EXAMPLE_STRING = process.env.EXAMPLE_STRING 5 | 6 | chrome.runtime.onInstalled.addListener(details => { 7 | console.log('Previous version is %o', details.previousVersion) 8 | }) 9 | 10 | <% if (withAction) { -%> 11 | chrome.browserAction.setBadgeText({ text: 'ninja' }) 12 | <% } -%> 13 | 14 | const ascii = ` 15 | ____ _ ___ _ ___ _ 16 | / __ \\/ / __ \\ / / __ \`/ 17 | / / / / / / / / / / /_/ / 18 | /_/ /_/_/_/ /_/_/ /\\__,_/ 19 | /___/ ` 20 | 21 | console.log('%c' + ascii + '%c\n\n%s', 'color: #ddd', '', 'This is the <%= pageName %>.') 22 | console.log('Configuration: exampleFlag is %o, exampleString is %o', EXAMPLE_FLAG, EXAMPLE_STRING) 23 | -------------------------------------------------------------------------------- /src/build/templates/styles/index.css: -------------------------------------------------------------------------------- 1 | <% if (bootstrap) { %> 2 | <% if (!bootstrapTheme || bootstrapTheme === 'default') { %> 3 | @import url("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"); 4 | 5 | <% } else if (bootstrapTheme) { %> 6 | @import url("https://bootswatch.com/<%= bootstrapTheme.toLowerCase() %>/bootstrap.min.css"); 7 | 8 | <% }} %>body { 9 | padding: 10px; 10 | } 11 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es5/assets.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , imagemin = require('gulp-imagemin') 3 | 4 | var IMAGES = 'app/**/*.{jpg,png,jpeg,gif}' 5 | , REST = [ 'app/**/*.{ico,txt,html,webp,svg}' 6 | , 'app/**/*.css' 7 | , 'app/_locales/**/*.json' 8 | , 'app/styles/fonts/**/*.*' ] 9 | 10 | gulp.task('assets:imagemin', function() { 11 | return gulp.src(IMAGES) 12 | .pipe(imagemin({ progressive: true })) 13 | .pipe(gulp.dest('dist')) 14 | }) 15 | 16 | gulp.task('assets:rest', function() { 17 | return gulp.src(REST, { base: 'app' }).pipe(gulp.dest('dist')) 18 | }) 19 | 20 | gulp.task('assets:watch', function(done) { 21 | gulp.watch(IMAGES, ['assets:imagemin']) 22 | gulp.watch(REST, ['assets:rest']) 23 | 24 | done() 25 | }) 26 | 27 | gulp.task('assets', ['assets:imagemin', 'assets:rest']) 28 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es5/browserify.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , browserify = require('browserify') 3 | , watchify = require('watchify') 4 | , envify = require('envify/custom') 5 | , inProduction = require('in-production/function') 6 | , config = require('./config') 7 | , through2 = require('through2') 8 | , concat = require('concat-stream') 9 | , imitate = require('vinyl-imitate') 10 | , gutil = require('gulp-util') 11 | , assign = require('object-assign') 12 | 13 | var log = gutil.log 14 | , colors = gutil.colors 15 | 16 | var SRC = 'app/lib/*/index.js' 17 | , DEST = 'dist' 18 | 19 | gulp.task('browserify', function() { 20 | return bundleAll() 21 | }) 22 | 23 | gulp.task('browserify:watch', function() { 24 | return bundleAll(true) 25 | }) 26 | 27 | function bundleAll(watch) { 28 | var src = gulp.src(SRC, { read: false, base: 'app' }) 29 | , initial = gulp.dest(DEST) 30 | , endless = gulp.dest(DEST) 31 | , hotreloadPort = 4474 32 | 33 | return src.pipe(through2.obj(function(file, _, next) { 34 | var b = bundleEntry(file.path) 35 | 36 | if (watch) { 37 | b.plugin('livereactload', { 38 | client: 'livereactload-chrome', 39 | port: hotreloadPort++ 40 | }) 41 | 42 | b.plugin(watchify).on('update', function() { 43 | log('Bundling %s', colors.magenta(file.relative)) 44 | 45 | var contents = b.bundle() 46 | 47 | function swallow(err) { 48 | log(colors.red(err.message)) 49 | log(err.stack) 50 | contents.unpipe() 51 | } 52 | 53 | function write(buffer) { 54 | endless.write(imitate(file, buffer)) 55 | } 56 | 57 | contents.once('error', swallow).pipe(concat(write)) 58 | }) 59 | } 60 | 61 | var initialContents = b.bundle() 62 | next(null, imitate(file, initialContents)) 63 | })).pipe(initial) 64 | } 65 | 66 | function bundleEntry(file) { 67 | var opts = assign({}, watchify.args, { debug: !inProduction() }) 68 | , b = browserify(file, opts).transform('babelify') 69 | 70 | // Substitute process.env.*, strip undefined variables 71 | var env = config.env({ NODE_ENV: process.env.NODE_ENV, _: 'purge' }) 72 | b.transform(envify(env)) 73 | 74 | // Add transform to modules that use process.env without envify. This is 75 | // safer than making envify a global transform. 76 | b.plugin('add-transforms', { 'react-bootstrap': ['envify'], 'uncontrollable': ['envify'] }) 77 | 78 | if (inProduction()) b.transform({ global: true }, 'uglifyify') 79 | 80 | return b 81 | } 82 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es5/build.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , sequence = require('run-sequence') 3 | 4 | gulp.task('build', function(done) { 5 | sequence( 'config:prompt' 6 | , 'config:print' 7 | , 'clean' 8 | , [ 'browserify', 'assets', 'manifest:prod' ] 9 | , 'compress' 10 | , done ) 11 | }) 12 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es5/clean.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , trash = require('trash') 3 | , pathExists = require('path-exists') 4 | 5 | gulp.task('clean', function(done) { 6 | if (pathExists.sync('./dist')) trash(['./dist'], done) 7 | else setImmediate(done) 8 | }) 9 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es5/compress.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , zip = require('gulp-zip') 3 | , pkg = require('../package.json') 4 | 5 | gulp.task('compress', function() { 6 | var filename = pkg.name + '-' + pkg.version + '.zip' 7 | 8 | return gulp.src('dist/**/*') 9 | .pipe(zip(filename)) 10 | .pipe(gulp.dest('package')) 11 | }) 12 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es5/config.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var config = require('config-prompt')({ 3 | exampleString: { type: 'string', required: true }, 4 | exampleFlag: { type: 'boolean', default: true } 5 | }) 6 | 7 | // Show all config entries 8 | gulp.task('config:print', function(done) { 9 | config.print(done) 10 | }) 11 | 12 | // Move the config file to trash 13 | gulp.task('config:trash', function(done) { 14 | config.trash(done) 15 | }) 16 | 17 | // Prompt for missing config entries. Run this before any other task. 18 | gulp.task('config:prompt', function(done) { 19 | config.prompt(done) 20 | }) 21 | 22 | // Allow other tasks to consume config 23 | module.exports = config 24 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es5/develop.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , sequence = require('run-sequence') 3 | 4 | gulp.task('develop', function(done) { 5 | sequence( 'config:prompt' 6 | , 'config:print' 7 | , 'clean' 8 | , [ 'browserify:watch', 'assets', 'manifest:dev' ] 9 | , [ 'compress', 'assets:watch' ] 10 | , done ) 11 | }) 12 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es5/manifest.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , manifest = require('../app/manifest.json') 3 | , assign = require('object-assign') 4 | , fs = require('fs') 5 | , version = require('../package.json').version 6 | , path = require('path') 7 | 8 | gulp.task('manifest:dev', function(done) { 9 | gulp.watch('app/manifest.json', ['manifest:dev']) 10 | 11 | // Allow eval() for livereactload 12 | var policy = "script-src 'self' 'unsafe-eval'; object-src 'self'" 13 | build(done, { content_security_policy: policy }) 14 | }) 15 | 16 | gulp.task('manifest:prod', function(done) { 17 | build(done) 18 | }) 19 | 20 | function build(done, extra) { 21 | var mani = assign({}, manifest, { version: version }, extra) 22 | , json = JSON.stringify(mani, null, ' ') 23 | , dist = path.resolve(__dirname, '..', 'dist') 24 | , file = path.join(dist, 'manifest.json') 25 | 26 | fs.mkdir(dist, function(err) { 27 | if (err && err.code !== 'EEXIST') done(err) 28 | else fs.writeFile(file, json, done) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es6/assets.js: -------------------------------------------------------------------------------- 1 | <% if (modules === 'es6') { -%> 2 | import gulp from 'gulp' 3 | import imagemin from 'gulp-imagemin' 4 | <% } else { -%> 5 | const gulp = require('gulp') 6 | , imagemin = require('gulp-imagemin') 7 | <% } -%> 8 | 9 | const IMAGES = 'app/**/*.{jpg,png,jpeg,gif}' 10 | , REST = [ 'app/**/*.{ico,txt,html,webp,svg}' 11 | , 'app/**/*.css' 12 | , 'app/_locales/**/*.json' 13 | , 'app/styles/fonts/**/*.*' ] 14 | 15 | gulp.task('assets:imagemin', () => { 16 | return gulp.src(IMAGES) 17 | .pipe(imagemin({ progressive: true })) 18 | .pipe(gulp.dest('dist')) 19 | }) 20 | 21 | gulp.task('assets:rest', () => { 22 | return gulp.src(REST, { base: 'app' }).pipe(gulp.dest('dist')) 23 | }) 24 | 25 | gulp.task('assets:watch', (done) => { 26 | gulp.watch(IMAGES, ['assets:imagemin']) 27 | gulp.watch(REST, ['assets:rest']) 28 | 29 | done() 30 | }) 31 | 32 | gulp.task('assets', ['assets:imagemin', 'assets:rest']) 33 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es6/browserify.js: -------------------------------------------------------------------------------- 1 | <% if (modules === 'es6') { -%> 2 | import gulp from 'gulp' 3 | import browserify from 'browserify' 4 | import watchify from 'watchify' 5 | import envify from 'envify/custom' 6 | import inProduction from 'in-production/function' 7 | import config from './config' 8 | import through2 from 'through2' 9 | import concat from 'concat-stream' 10 | import imitate from 'vinyl-imitate' 11 | import { log, colors } from 'gulp-util' 12 | <% } else { -%> 13 | const gulp = require('gulp') 14 | , browserify = require('browserify') 15 | , watchify = require('watchify') 16 | , envify = require('envify/custom') 17 | , inProduction = require('in-production/function') 18 | , config = require('./config') 19 | , through2 = require('through2') 20 | , concat = require('concat-stream') 21 | , imitate = require('vinyl-imitate') 22 | , { log, colors } = require('gulp-util') 23 | <% } -%> 24 | 25 | const SRC = 'app/lib/*/index.js' 26 | , DEST = 'dist' 27 | 28 | gulp.task('browserify', () => bundleAll()) 29 | gulp.task('browserify:watch', () => bundleAll(true)) 30 | 31 | function bundleAll(watch = false) { 32 | let src = gulp.src(SRC, { read: false, base: 'app' }) 33 | , initial = gulp.dest(DEST) 34 | , endless = gulp.dest(DEST) 35 | , hotreloadPort = 4474 36 | 37 | return src.pipe(through2.obj(function(file, _, next) { 38 | let b = bundleEntry(file.path) 39 | 40 | if (watch) { 41 | b.plugin('livereactload', { 42 | client: 'livereactload-chrome', 43 | port: hotreloadPort++ 44 | }) 45 | 46 | b.plugin(watchify).on('update', () => { 47 | log('Bundling %s', colors.magenta(file.relative)) 48 | 49 | let contents = b.bundle() 50 | let swallow = (err) => { 51 | log(colors.red(err.message)) 52 | log(err.stack) 53 | contents.unpipe() 54 | } 55 | 56 | contents.once('error', swallow).pipe(concat(buffer => { 57 | endless.write(imitate(file, buffer)) 58 | })) 59 | }) 60 | } 61 | 62 | let initialContents = b.bundle() 63 | next(null, imitate(file, initialContents)) 64 | })).pipe(initial) 65 | } 66 | 67 | function bundleEntry(file) { 68 | let opts = { ...watchify.args, debug: !inProduction() } 69 | , b = browserify(file, opts).transform('babelify') 70 | 71 | // Substitute process.env.*, strip undefined variables 72 | let env = config.env({ NODE_ENV: process.env.NODE_ENV, _: 'purge' }) 73 | b.transform(envify(env)) 74 | 75 | // Add transform to modules that use process.env without envify. This is 76 | // safer than making envify a global transform. 77 | b.plugin('add-transforms', { 'react-bootstrap': ['envify'], 'uncontrollable': ['envify'] }) 78 | 79 | if (inProduction()) b.transform({ global: true }, 'uglifyify') 80 | 81 | return b 82 | } 83 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es6/build.js: -------------------------------------------------------------------------------- 1 | <% if (modules === 'es6') { -%> 2 | import gulp from 'gulp' 3 | import sequence from 'run-sequence' 4 | <% } else { -%> 5 | const gulp = require('gulp') 6 | , sequence = require('run-sequence') 7 | <% } -%> 8 | 9 | gulp.task('build', (done) => { 10 | sequence( 'config:prompt' 11 | , 'config:print' 12 | , 'clean' 13 | , [ 'browserify', 'assets', 'manifest:prod' ] 14 | , 'compress' 15 | , done ) 16 | }) 17 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es6/clean.js: -------------------------------------------------------------------------------- 1 | <% if (modules === 'es6') { -%> 2 | import gulp from 'gulp' 3 | import trash from 'trash' 4 | import pathExists from 'path-exists' 5 | <% } else { -%> 6 | const gulp = require('gulp') 7 | , trash = require('trash') 8 | , pathExists = require('path-exists') 9 | <% } -%> 10 | 11 | gulp.task('clean', (done) => { 12 | if (pathExists.sync('./dist')) trash(['./dist'], done) 13 | else setImmediate(done) 14 | }) 15 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es6/compress.js: -------------------------------------------------------------------------------- 1 | <% if (modules === 'es6') { -%> 2 | import gulp from 'gulp' 3 | import zip from 'gulp-zip' 4 | <% } else { -%> 5 | const gulp = require('gulp') 6 | , zip = require('gulp-zip') 7 | <% } -%> 8 | 9 | gulp.task('compress', () => { 10 | let { name, version } = require('../package.json') 11 | 12 | return gulp.src('dist/**/*') 13 | .pipe(zip(`${name}-${version}.zip`)) 14 | .pipe(gulp.dest('package')) 15 | }) 16 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es6/config.js: -------------------------------------------------------------------------------- 1 | <% if (modules === 'es6') { -%> 2 | import gulp from 'gulp' 3 | import Config from 'config-prompt' 4 | 5 | const config = Config({ 6 | exampleString: { type: 'string', required: true }, 7 | exampleFlag: { type: 'boolean', default: true } 8 | }) 9 | <% } else { -%> 10 | const gulp = require('gulp') 11 | const config = require('config-prompt')({ 12 | exampleString: { type: 'string', required: true }, 13 | exampleFlag: { type: 'boolean', default: true } 14 | }) 15 | <% } -%> 16 | 17 | // Show all config entries 18 | gulp.task('config:print', (done) => config.print(done)) 19 | 20 | // Move the config file to trash 21 | gulp.task('config:trash', (done) => config.trash(done)) 22 | 23 | // Prompt for missing config entries. Run this before any other task. 24 | gulp.task('config:prompt', (done) => config.prompt(done)) 25 | 26 | // Allow other tasks to consume config 27 | <% if (modules === 'es6') { -%> 28 | export default config 29 | <% } else { -%> 30 | module.exports = config 31 | <% } -%> 32 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es6/develop.js: -------------------------------------------------------------------------------- 1 | <% if (modules === 'es6') { -%> 2 | import gulp from 'gulp' 3 | import sequence from 'run-sequence' 4 | <% } else { -%> 5 | const gulp = require('gulp') 6 | , sequence = require('run-sequence') 7 | <% } -%> 8 | 9 | gulp.task('develop', (done) => { 10 | sequence( 'config:prompt' 11 | , 'config:print' 12 | , 'clean' 13 | , [ 'browserify:watch', 'assets', 'manifest:dev' ] 14 | , [ 'compress', 'assets:watch' ] 15 | , done ) 16 | }) 17 | -------------------------------------------------------------------------------- /src/build/templates/tasks/es6/manifest.js: -------------------------------------------------------------------------------- 1 | <% if (modules === 'es6') { -%> 2 | import gulp from 'gulp' 3 | import manifest from '../app/manifest.json' 4 | import { mkdir, writeFile } from 'fs' 5 | import { version } from '../package.json' 6 | import { resolve, join } from 'path' 7 | <% } else { -%> 8 | const gulp = require('gulp') 9 | , manifest = require('../app/manifest.json') 10 | , { mkdir, writeFile } = require('fs') 11 | , { version } = require('../package.json') 12 | , { resolve, join } = require('path') 13 | <% } -%> 14 | 15 | gulp.task('manifest:dev', (done) => { 16 | gulp.watch('app/manifest.json', ['manifest:dev']) 17 | 18 | // Allow eval() for livereactload 19 | let policy = "script-src 'self' 'unsafe-eval'; object-src 'self'" 20 | build(done, { content_security_policy: policy }) 21 | }) 22 | 23 | gulp.task('manifest:prod', (done) => build(done)) 24 | 25 | function build(done, extra = {}) { 26 | let mani = { ...manifest, version, ...extra } 27 | , json = JSON.stringify(mani, null, ' ') 28 | , dist = resolve(__dirname, '..', 'dist') 29 | , file = join(dist, 'manifest.json') 30 | 31 | mkdir(dist, (err) => { 32 | if (err && err.code !== 'EEXIST') done(err) 33 | else writeFile(file, json, done) 34 | }) 35 | } 36 | --------------------------------------------------------------------------------