├── .gitignore ├── .npmrc ├── .travis.yml ├── LICENSE ├── README.md ├── assets └── icon.png ├── bin.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | tmp/ 4 | dist/ 5 | npm-debug.log* 6 | .DS_Store 7 | .nyc_output 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - "6" 3 | - "8" 4 | sudo: false 5 | language: node_js 6 | script: "npm run test" 7 | # after_success: "npm i -g codecov && npm run coverage && codecov" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yoshua Wuyts 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-choo-electron [![stability][0]][1] 2 | [![npm version][2]][3] [![build status][4]][5] 3 | [![downloads][8]][9] [![js-standard-style][10]][11] 4 | 5 | Create a fresh Choo Electron application. 6 | 7 | Development kindly funded by [nearForm](http://nearform.com/opensource). 8 | 9 | ## Features 10 | - Zero configuration. 11 | - Ready to be deployed out of the box. 12 | - Low footprint UI using [Choo frontend framework](https://github.com/choojs/choo). 13 | 14 | ## Usage 15 | ```sh 16 | $ npx create-choo-electron 17 | ``` 18 | 19 | ## API 20 | ```txt 21 | $ create-choo-electron [options] 22 | 23 | Options: 24 | 25 | -h, --help print usage 26 | -v, --version print version 27 | -q, --quiet don't output any logs 28 | 29 | Examples: 30 | 31 | Create a new Choo Electron application 32 | $ create-choo-electron 33 | 34 | Running into trouble? Feel free to file an issue: 35 | https://github.com/choojs/create-choo-electron/issues/new 36 | 37 | Do you enjoy using this software? Become a backer: 38 | https://opencollective.com/choo 39 | ``` 40 | 41 | ## License 42 | [MIT](https://tldrlegal.com/license/mit-license) 43 | 44 | [0]: https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square 45 | [1]: https://nodejs.org/api/documentation.html#documentation_stability_index 46 | [2]: https://img.shields.io/npm/v/create-choo-electron.svg?style=flat-square 47 | [3]: https://npmjs.org/package/create-choo-electron 48 | [4]: https://img.shields.io/travis/choojs/create-choo-electron/master.svg?style=flat-square 49 | [5]: https://travis-ci.org/choojs/create-choo-electron 50 | [6]: https://img.shields.io/codecov/c/github/choojs/create-choo-electron/master.svg?style=flat-square 51 | [7]: https://codecov.io/github/choojs/create-choo-electron 52 | [8]: http://img.shields.io/npm/dm/create-choo-electron.svg?style=flat-square 53 | [9]: https://npmjs.org/package/create-choo-electron 54 | [10]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square 55 | [11]: https://github.com/feross/standard 56 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choojs/create-choo-electron/fb4ba943b679f2730b0a458a90ed3c4e89a18996/assets/icon.png -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var mapLimit = require('async-collection/map-limit') 4 | var series = require('async-collection/series') 5 | var ansi = require('ansi-escape-sequences') 6 | var minimist = require('minimist') 7 | var dedent = require('dedent') 8 | var rimraf = require('rimraf') 9 | var path = require('path') 10 | 11 | var lib = require('./') 12 | 13 | var TRAIN = '🚂🚋🚋' 14 | 15 | var USAGE = ` 16 | $ ${clr('create-choo-electron', 'bold')} ${clr('', 'green')} [options] 17 | 18 | Options: 19 | 20 | -h, --help print usage 21 | -v, --version print version 22 | -q, --quiet don't output any logs 23 | 24 | Examples: 25 | 26 | Create a new Choo application 27 | ${clr('$ create-choo-electron', 'cyan')} 28 | 29 | Running into trouble? Feel free to file an issue: 30 | ${clr('https://github.com/choojs/create-choo-electron/issues/new', 'cyan')} 31 | 32 | Do you enjoy using this software? Become a backer: 33 | ${clr('https://opencollective.com/choo', 'cyan')} 34 | `.replace(/\n$/, '').replace(/^\n/, '') 35 | 36 | var NODIR = ` 37 | Please specify the project directory: 38 | ${clr('$ create-choo-electron', 'cyan')} ${clr('', 'green')} 39 | 40 | For example: 41 | ${clr('$ create-choo-electron', 'cyan')} ${clr('my-choo-electron', 'green')} 42 | 43 | Run ${clr('create-choo-electron --help', 'cyan')} to see all options. 44 | `.replace(/\n$/, '').replace(/^\n/, '') 45 | 46 | var argv = minimist(process.argv.slice(2), { 47 | alias: { 48 | help: 'h', 49 | quiet: 'q', 50 | version: 'v' 51 | }, 52 | boolean: [ 53 | 'help', 54 | 'quiet', 55 | 'version' 56 | ] 57 | }) 58 | 59 | ;(function main (argv) { 60 | var dir = argv._[0] 61 | 62 | if (argv.help) { 63 | console.log(USAGE) 64 | } else if (argv.version) { 65 | console.log(require('./package.json').version) 66 | } else if (!dir) { 67 | console.log(NODIR) 68 | process.exit(1) 69 | } else { 70 | create(path.join(process.cwd(), dir), argv) 71 | } 72 | })(argv) 73 | 74 | function create (dir, argv) { 75 | var written = [] 76 | var cmds = [ 77 | function (done) { 78 | print('Creating a new Choo Electron app in ' + clr(dir, 'green') + '.\n') 79 | lib.mkdir(dir, done) 80 | }, 81 | function (done) { 82 | var filename = 'package.json' 83 | printFile(filename) 84 | written.push(path.join(dir, filename)) 85 | lib.writePackage(dir, done) 86 | }, 87 | function (done) { 88 | print('\nInstalling packages, this might take a couple of minutes.') 89 | written.push(path.join(dir, 'node_modules')) 90 | var pkgs = [ 91 | 'choo', 92 | 'choo-devtools', 93 | 'electron-collection', 94 | 'tachyons' 95 | ] 96 | var msg = clrInstall(pkgs) 97 | print('Installing ' + msg + '…') 98 | lib.install(dir, pkgs, done) 99 | }, 100 | function (done) { 101 | var pkgs = [ 102 | 'bankai@9', 103 | 'electron', 104 | 'cross-env', 105 | 'electron-builder', 106 | 'electron-builder-squirrel-windows', 107 | 'standard' 108 | ] 109 | var msg = clrInstall(pkgs) 110 | print('Installing ' + msg + '…') 111 | lib.devInstall(dir, pkgs, done) 112 | }, 113 | function (done) { 114 | print('') // print newline 115 | var filename = '.gitignore' 116 | printFile(filename) 117 | written.push(path.join(dir, filename)) 118 | lib.writeIgnore(dir, done) 119 | }, 120 | function (done) { 121 | var filename = 'README.md' 122 | printFile(filename) 123 | written.push(path.join(dir, filename)) 124 | lib.writeReadme(dir, done) 125 | }, 126 | function (done) { 127 | var filename = 'index.js' 128 | printFile(filename) 129 | written.push(path.join(dir, filename)) 130 | lib.writeIndex(dir, done) 131 | }, 132 | function (done) { 133 | var filename = 'index.html' 134 | printFile(filename) 135 | written.push(path.join(dir, filename)) 136 | lib.writeHtml(dir, done) 137 | }, 138 | function (done) { 139 | var filename = 'main.js' 140 | printFile(filename) 141 | written.push(path.join(dir, filename)) 142 | lib.writeMain(dir, done) 143 | }, 144 | function (done) { 145 | var filename = 'views/main.js' 146 | printFile(filename) 147 | written.push(path.join(dir, filename)) 148 | lib.writeMainView(dir, done) 149 | }, 150 | function (done) { 151 | var filename = 'views/404.js' 152 | printFile(filename) 153 | written.push(path.join(dir, filename)) 154 | lib.writeNotFoundView(dir, done) 155 | }, 156 | function (done) { 157 | var filename = 'assets/icon.png' 158 | printFile(filename) 159 | written.push(path.join(dir, filename)) 160 | lib.writeIcon(dir, done) 161 | }, 162 | function (done) { 163 | var message = 'The train has departed! :steam_locomotive::train::train::train:' 164 | print('\nInitializing ' + clr('git', 'cyan')) 165 | written.push(path.join(dir, '.git')) 166 | lib.createGit(dir, message, done) 167 | } 168 | ] 169 | 170 | series(cmds, function (err) { 171 | if (err) { 172 | print('\nAborting installation. The following error occured:') 173 | print(' ' + clr(err.message, 'red') + '\n') 174 | mapLimit(written, 1, cleanFile, function (err) { 175 | if (err) throw err 176 | console.log('Cleanup completed, please try again sometime.') 177 | process.exit(1) 178 | }) 179 | } else { 180 | var msg = dedent` 181 | App created in ${clr(dir, 'green')}. 182 | ${clr('All done, good job!', 'magenta')} ${TRAIN} 183 | 184 | The following commands are available: 185 | ${clr('npm start', 'cyan')} Start the Electron process 186 | ${clr('npm test', 'cyan')} Lint, validate deps & run tests 187 | ${clr('npm run build', 'cyan')} Compile all files to ${clr('dist/', 'green')} 188 | ${clr('npm run dev', 'cyan')} Start the development server 189 | ${clr('npm run inspect', 'cyan')} Inspect the bundle dependencies 190 | 191 | Do you enjoy using this software? Become a backer: 192 | ${clr('https://opencollective.com/choo', 'cyan')} 193 | `.replace(/\n$/g, '') 194 | print('\n' + msg) 195 | } 196 | }) 197 | 198 | function print (val) { 199 | if (!argv.quiet) console.log(val) 200 | } 201 | 202 | function printFile (filename) { 203 | print('Creating file ' + clr(filename, 'cyan') + '…') 204 | } 205 | } 206 | 207 | function clr (text, color) { 208 | return process.stdout.isTTY ? ansi.format(text, color) : text 209 | } 210 | 211 | function clrInstall (pkgs) { 212 | return pkgs.reduce(function (str, pkg, i) { 213 | pkg = clr(pkg, 'cyan') 214 | if (i === pkgs.length - 1) { 215 | return str + pkg 216 | } else if (i === pkgs.length - 2) { 217 | return str + pkg + ', and ' 218 | } else { 219 | return str + pkg + ', ' 220 | } 221 | }, '') 222 | } 223 | 224 | function cleanFile (file, cb) { 225 | console.log('Deleting generated file… ' + clr(path.basename(file), 'cyan')) 226 | rimraf(file, cb) 227 | } 228 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec 2 | var dedent = require('dedent') 3 | var mkdirp = require('mkdirp') 4 | var path = require('path') 5 | var pump = require('pump') 6 | var fs = require('fs') 7 | 8 | var TRAIN = '🚂🚋🚋' 9 | 10 | exports.mkdir = function (dir, cb) { 11 | mkdirp(dir, function (err) { 12 | if (err) return cb(new Error('Could not create directory ' + dir)) 13 | fs.readdir(dir, function (err, files) { 14 | if (err) return cb(new Error('Could not read directory ' + dir)) 15 | if (files.length) return cb(new Error('Directory contains files. This might create conflicts.')) 16 | cb() 17 | }) 18 | }) 19 | } 20 | 21 | exports.writePackage = function (dir, cb) { 22 | var filename = path.join(dir, 'package.json') 23 | var name = path.basename(dir) 24 | var file = dedent` 25 | { 26 | "name": "${name}", 27 | "version": "1.0.0", 28 | "private": true, 29 | "main": "main.js", 30 | "scripts": { 31 | "build": "bankai build && build", 32 | "dev": "bankai start index.js", 33 | "inspect": "bankai inspect index.js", 34 | "pack": "bankai build && build --dir", 35 | "start": "cross-env NODE_ENV=development electron main.js", 36 | "test": "standard && test-deps", 37 | "test-deps": "dependency-check . && dependency-check . --extra --no-dev -i tachyons" 38 | }, 39 | "build": { 40 | "appId": "${name}", 41 | "files": [ 42 | "**/*" 43 | ], 44 | "win": { 45 | "target": [ 46 | "squirrel" 47 | ] 48 | } 49 | } 50 | } 51 | ` 52 | write(filename, file, cb) 53 | } 54 | 55 | exports.writeIgnore = function (dir, cb) { 56 | var filename = path.join(dir, '.gitignore') 57 | var file = dedent` 58 | node_modules/ 59 | .nyc_output/ 60 | coverage/ 61 | dist/ 62 | tmp/ 63 | npm-debug.log* 64 | .DS_Store 65 | ` 66 | 67 | write(filename, file, cb) 68 | } 69 | 70 | exports.writeReadme = function (dir, cb) { 71 | var filename = path.join(dir, 'README.md') 72 | var name = path.basename(dir) 73 | var file = dedent` 74 | # ${name} 75 | A very cute Electron app 76 | 77 | ## Routes 78 | Route | File | Description | 79 | -------------------|--------------------|---------------------------------| 80 | \`/\` | \`views/main.js\` | The main view 81 | \`/*\` | \`views/404.js\` | Display unhandled routes 82 | 83 | ## Commands 84 | Command | Description | 85 | -----------------------|--------------------------------------------------| 86 | \`$ npm start\` | Start the Electron process 87 | \`$ npm test\` | Lint, validate deps & run tests 88 | \`$ npm run build\` | Compile all files into \`dist/\` 89 | \`$ npm run dev\` | Start the development server 90 | \`$ npm run inspect\` | Inspect the bundle's dependencies 91 | ` 92 | 93 | write(filename, file, cb) 94 | } 95 | 96 | exports.writeHtml = function (dir, cb) { 97 | var filename = path.join(dir, 'index.html') 98 | var file = dedent` 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ` 110 | 111 | write(filename, file, cb) 112 | } 113 | 114 | exports.writeIndex = function (dir, cb) { 115 | var filename = path.join(dir, 'index.js') 116 | var file = dedent` 117 | var css = require('sheetify') 118 | var choo = require('choo') 119 | 120 | css('tachyons') 121 | 122 | var app = choo() 123 | if (process.env.NODE_ENV !== 'production') { 124 | app.use(require('choo-devtools')()) 125 | } 126 | 127 | app.route('/', require('./views/main')) 128 | app.route('/*', require('./views/404')) 129 | 130 | if (!module.parent) app.mount('body') 131 | else module.exports = app 132 | ` 133 | 134 | write(filename, file, cb) 135 | } 136 | 137 | exports.writeMain = function (dir, cb) { 138 | var filename = path.join(dir, 'main.js') 139 | var name = path.basename(dir) 140 | var file = dedent` 141 | var resolvePath = require('electron-collection/resolve-path') 142 | var defaultMenu = require('electron-collection/default-menu') 143 | var electron = require('electron') 144 | 145 | var BrowserWindow = electron.BrowserWindow 146 | var Menu = electron.Menu 147 | var app = electron.app 148 | 149 | var win 150 | 151 | var windowStyles = { 152 | width: 800, 153 | height: 1000, 154 | titleBarStyle: 'hidden-inset', 155 | minWidth: 640, 156 | minHeight: 395 157 | } 158 | 159 | app.setName('${name}') 160 | 161 | var shouldQuit = app.makeSingleInstance(createInstance) 162 | if (shouldQuit) app.quit() 163 | 164 | app.on('ready', function () { 165 | win = new BrowserWindow(windowStyles) 166 | var root = process.env.NODE_ENV === 'development' 167 | ? 'https://localhost:8080' 168 | : 'file://' + resolvePath('./index.html') 169 | win.loadURL(root) 170 | 171 | win.webContents.on('did-finish-load', function () { 172 | win.show() 173 | var menu = Menu.buildFromTemplate(defaultMenu(app, electron.shell)) 174 | Menu.setApplicationMenu(menu) 175 | if (process.env.NODE_ENV === 'development') { 176 | win.webContents.openDevTools({ mode: 'detach' }) 177 | } 178 | }) 179 | 180 | win.on('closed', function () { 181 | win = null 182 | }) 183 | }) 184 | 185 | if (process.env.NODE_ENV === 'development') { 186 | app.on('certificate-error', function (event, webContents, url, err, cert, cb) { 187 | if (url.match('https://localhost')) { 188 | event.preventDefault() 189 | cb(true) 190 | } else { 191 | cb(false) 192 | } 193 | }) 194 | } 195 | 196 | app.on('window-all-closed', function () { 197 | app.quit() 198 | }) 199 | 200 | function createInstance () { 201 | if (win) { 202 | if (win.isMinimized()) win.restore() 203 | win.focus() 204 | } 205 | } 206 | ` 207 | 208 | write(filename, file, cb) 209 | } 210 | 211 | exports.writeNotFoundView = function (dir, cb) { 212 | var dirname = path.join(dir, 'views') 213 | var filename = path.join(dirname, '404.js') 214 | var file = dedent` 215 | var html = require('choo/html') 216 | 217 | var TITLE = '${TRAIN} - route not found' 218 | 219 | module.exports = view 220 | 221 | function view (state, emit) { 222 | if (state.title !== TITLE) emit(state.events.DOMTITLECHANGE, TITLE) 223 | return html\` 224 | 225 |

226 | 404 - route not found 227 |

228 | 229 | Back to main 230 | 231 | 232 | \` 233 | } 234 | ` 235 | 236 | mkdirp(dirname, function (err) { 237 | if (err) return cb(new Error('Could not create directory ' + dirname)) 238 | write(filename, file, cb) 239 | }) 240 | } 241 | 242 | exports.writeMainView = function (dir, cb) { 243 | var dirname = path.join(dir, 'views') 244 | var filename = path.join(dirname, 'main.js') 245 | var file = dedent` 246 | var html = require('choo/html') 247 | 248 | var TITLE = '${TRAIN}' 249 | 250 | module.exports = view 251 | 252 | function view (state, emit) { 253 | if (state.title !== TITLE) emit(state.events.DOMTITLECHANGE, TITLE) 254 | return html\` 255 | 256 |

257 | Choo choo! 258 |

259 | 260 | \` 261 | } 262 | ` 263 | 264 | mkdirp(dirname, function (err) { 265 | if (err) return cb(new Error('Could not create directory ' + dirname)) 266 | write(filename, file, cb) 267 | }) 268 | } 269 | 270 | exports.writeIcon = function (dir, cb) { 271 | var iconPath = path.join(__dirname, 'assets/icon.png') 272 | var dirname = path.join(dir, 'assets') 273 | var filename = path.join(dirname, 'icon.png') 274 | mkdirp(dirname, function (err) { 275 | if (err) return cb(new Error('Could not create directory ' + dirname)) 276 | var source = fs.createReadStream(iconPath) 277 | var sink = fs.createWriteStream(filename) 278 | pump(source, sink, function (err) { 279 | if (err) return cb(new Error('Could not write file ' + filename)) 280 | cb() 281 | }) 282 | }) 283 | } 284 | 285 | exports.install = function (dir, packages, cb) { 286 | packages = packages.join(' ') 287 | var cmd = 'npm install --save --cache-min Infinity --loglevel error ' + packages 288 | var popd = pushd(dir) 289 | exec(cmd, function (err) { 290 | if (err) return cb(new Error(cmd)) 291 | popd() 292 | cb() 293 | }) 294 | } 295 | 296 | exports.devInstall = function (dir, packages, cb) { 297 | packages = packages.join(' ') 298 | var cmd = 'npm install --save-dev --cache-min Infinity --loglevel error ' + packages 299 | var popd = pushd(dir) 300 | exec(cmd, function (err) { 301 | if (err) return cb(new Error(cmd)) 302 | popd() 303 | cb() 304 | }) 305 | } 306 | 307 | exports.createGit = function (dir, message, cb) { 308 | var init = 'git init' 309 | var add = 'git add -A' 310 | var config = 'git config user.email' 311 | var commit = 'git commit -m "' + message + '"' 312 | 313 | var popd = pushd(dir) 314 | exec(init, function (err) { 315 | if (err) return cb(new Error(init)) 316 | 317 | exec(add, function (err) { 318 | if (err) return cb(new Error(add)) 319 | 320 | exec(config, function (err) { 321 | if (err) return cb(new Error(config)) 322 | 323 | exec(commit, function (err) { 324 | if (err) return cb(new Error(commit)) 325 | popd() 326 | cb() 327 | }) 328 | }) 329 | }) 330 | }) 331 | } 332 | 333 | function pushd (dir) { 334 | var prev = process.cwd() 335 | process.chdir(dir) 336 | return function popd () { 337 | process.chdir(prev) 338 | } 339 | } 340 | 341 | function write (filename, file, cb) { 342 | fs.writeFile(filename, file, function (err) { 343 | if (err) return cb(new Error('Could not write file ' + filename)) 344 | cb() 345 | }) 346 | } 347 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-choo-electron", 3 | "description": "Create a fresh choo application", 4 | "repository": "choojs/create-choo-electron", 5 | "version": "2.0.0", 6 | "bin": { 7 | "create-choo-electron": "./bin.js" 8 | }, 9 | "scripts": { 10 | "deps": "dependency-check . && dependency-check . -i ./bin.js --extra --no-dev", 11 | "start": "node .", 12 | "test": "standard && npm run deps && npm run test-fast", 13 | "test-fast": "rm -rf tmp && ./bin.js tmp" 14 | }, 15 | "dependencies": { 16 | "ansi-escape-sequences": "^3.0.0", 17 | "async-collection": "^1.0.1", 18 | "dedent": "^0.7.0", 19 | "minimist": "^1.2.0", 20 | "mkdirp": "^0.5.1", 21 | "pump": "^1.0.2", 22 | "rimraf": "^2.6.1" 23 | }, 24 | "devDependencies": { 25 | "dependency-check": "~2.6.0", 26 | "standard": "^10.0.2" 27 | }, 28 | "keywords": [ 29 | "generate", 30 | "choo", 31 | "create" 32 | ], 33 | "license": "MIT" 34 | } 35 | --------------------------------------------------------------------------------