├── .editorconfig ├── .gitignore ├── README.md ├── app ├── app.html ├── app.js ├── assets │ ├── baddie.png │ ├── diamond.png │ ├── dude.png │ ├── firstaid.png │ ├── platform.png │ ├── sky.png │ └── star.png ├── background.js ├── env.js ├── hello_world │ ├── boilerplate.spec.js │ ├── hello_universe.js │ ├── hello_universe.spec.js │ ├── hello_world.js │ └── hello_world.spec.js ├── helpers │ ├── context_menu.js │ ├── dev_menu_template.js │ ├── edit_menu_template.js │ ├── external_links.js │ └── window.js ├── libs │ └── phaser.min.js ├── package.json └── stylesheets │ └── main.less ├── config ├── env_development.json ├── env_production.json └── env_test.json ├── gulpfile.js ├── package.json ├── resources ├── icon.png ├── linux │ ├── DEBIAN │ │ └── control │ └── app.desktop ├── osx │ ├── Info.plist │ ├── appdmg.json │ ├── child.plist │ ├── dmg-background.png │ ├── dmg-background@2x.png │ ├── dmg-icon.icns │ ├── helper_apps │ │ ├── Info EH.plist │ │ ├── Info NP.plist │ │ └── Info.plist │ ├── icon.icns │ └── parent.plist └── windows │ ├── icon.ico │ ├── installer.nsi │ ├── setup-banner.bmp │ └── setup-icon.ico └── tasks ├── build ├── build.js ├── bundle.js └── generate_spec_imports.js ├── install_native_module.js ├── rebuild_native_modules.js ├── release ├── linux.js ├── osx.js ├── release.js └── windows.js ├── start.js └── utils.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.json] 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | Thumbs.db 5 | *.autogenerated 6 | /build/ 7 | /releases/ 8 | /tmp/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Electron boilerplate + Phaser tutorial 2 | 3 | This repository is a merge of two different repositories : 4 | 5 | - [electron-boilerplate](https://github.com/szwacz/electron-boilerplate.git) 6 | - Phaser html5 game library [making your first game tutorial](http://phaser.io/tutorials/making-your-first-phaser-game) 7 | 8 | ``` 9 | git clone https://github.com/Fax/electron-phaser 10 | cd electron-phaser 11 | npm install 12 | npm start 13 | ``` 14 | 15 | # Usages 16 | 17 | Follow the README on the repository https://github.com/szwacz/electron-boilerplate.git. 18 | 19 | # License 20 | 21 | MIT License 22 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Electron Boilerplate 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | // Here is the starting point for your application code. 2 | // All stuff below is just to show you how it works. You can delete all of it. 3 | 4 | // Use new ES6 modules syntax for everything. 5 | import os from 'os'; // native node.js module 6 | import { remote } from 'electron'; // native electron module 7 | import jetpack from 'fs-jetpack'; // module loaded from npm 8 | import { greet } from './hello_world/hello_world'; // code authored by you in this project 9 | import env from './env'; 10 | 11 | 12 | console.log('Loaded environment variables:', env); 13 | 14 | var app = remote.app; 15 | var appDir = jetpack.cwd(app.getAppPath()); 16 | 17 | // Holy crap! This is browser window with HTML and stuff, but I can read 18 | // here files like it is node.js! Welcome to Electron world :) 19 | console.log('The author of this app is:', appDir.read('package.json', 'json').author); 20 | 21 | document.addEventListener('DOMContentLoaded', function () { 22 | 23 | var game = new Phaser.Game(800, 600, Phaser.AUTO, 'main', { preload: preload, create: create, update: update }); 24 | 25 | function preload() { 26 | 27 | game.load.image('sky', 'assets/sky.png'); 28 | game.load.image('ground', 'assets/platform.png'); 29 | game.load.image('star', 'assets/star.png'); 30 | game.load.spritesheet('dude', 'assets/dude.png', 32, 48); 31 | 32 | } 33 | 34 | var player; 35 | var platforms; 36 | var cursors; 37 | 38 | var stars; 39 | var score = 0; 40 | var scoreText; 41 | 42 | function create() { 43 | 44 | // We're going to be using physics, so enable the Arcade Physics system 45 | game.physics.startSystem(Phaser.Physics.ARCADE); 46 | 47 | // A simple background for our game 48 | game.add.sprite(0, 0, 'sky'); 49 | 50 | // The platforms group contains the ground and the 2 ledges we can jump on 51 | platforms = game.add.group(); 52 | 53 | // We will enable physics for any object that is created in this group 54 | platforms.enableBody = true; 55 | 56 | // Here we create the ground. 57 | var ground = platforms.create(0, game.world.height - 64, 'ground'); 58 | 59 | // Scale it to fit the width of the game (the original sprite is 400x32 in size) 60 | ground.scale.setTo(2, 2); 61 | 62 | // This stops it from falling away when you jump on it 63 | ground.body.immovable = true; 64 | 65 | // Now let's create two ledges 66 | var ledge = platforms.create(400, 400, 'ground'); 67 | ledge.body.immovable = true; 68 | 69 | ledge = platforms.create(-150, 250, 'ground'); 70 | ledge.body.immovable = true; 71 | 72 | // The player and its settings 73 | player = game.add.sprite(32, game.world.height - 150, 'dude'); 74 | 75 | // We need to enable physics on the player 76 | game.physics.arcade.enable(player); 77 | 78 | // Player physics properties. Give the little guy a slight bounce. 79 | player.body.bounce.y = 0.2; 80 | player.body.gravity.y = 300; 81 | player.body.collideWorldBounds = true; 82 | 83 | // Our two animations, walking left and right. 84 | player.animations.add('left', [0, 1, 2, 3], 10, true); 85 | player.animations.add('right', [5, 6, 7, 8], 10, true); 86 | 87 | // Finally some stars to collect 88 | stars = game.add.group(); 89 | 90 | // We will enable physics for any star that is created in this group 91 | stars.enableBody = true; 92 | 93 | // Here we'll create 12 of them evenly spaced apart 94 | for (var i = 0; i < 12; i++) 95 | { 96 | // Create a star inside of the 'stars' group 97 | var star = stars.create(i * 70, 0, 'star'); 98 | 99 | // Let gravity do its thing 100 | star.body.gravity.y = 300; 101 | 102 | // This just gives each star a slightly random bounce value 103 | star.body.bounce.y = 0.7 + Math.random() * 0.2; 104 | } 105 | 106 | // The score 107 | scoreText = game.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' }); 108 | 109 | // Our controls. 110 | cursors = game.input.keyboard.createCursorKeys(); 111 | 112 | } 113 | 114 | function update() { 115 | 116 | // Collide the player and the stars with the platforms 117 | game.physics.arcade.collide(player, platforms); 118 | game.physics.arcade.collide(stars, platforms); 119 | 120 | // Checks to see if the player overlaps with any of the stars, if he does call the collectStar function 121 | game.physics.arcade.overlap(player, stars, collectStar, null, this); 122 | 123 | // Reset the players velocity (movement) 124 | player.body.velocity.x = 0; 125 | 126 | if (cursors.left.isDown) 127 | { 128 | // Move to the left 129 | player.body.velocity.x = -150; 130 | 131 | player.animations.play('left'); 132 | } 133 | else if (cursors.right.isDown) 134 | { 135 | // Move to the right 136 | player.body.velocity.x = 150; 137 | 138 | player.animations.play('right'); 139 | } 140 | else 141 | { 142 | // Stand still 143 | player.animations.stop(); 144 | 145 | player.frame = 4; 146 | } 147 | 148 | // Allow the player to jump if they are touching the ground. 149 | if (cursors.up.isDown && player.body.touching.down) 150 | { 151 | player.body.velocity.y = -350; 152 | } 153 | 154 | } 155 | 156 | function collectStar (player, star) { 157 | 158 | // Removes the star from the screen 159 | star.kill(); 160 | 161 | // Add and update the score 162 | score += 10; 163 | scoreText.text = 'Score: ' + score; 164 | 165 | } 166 | }); 167 | -------------------------------------------------------------------------------- /app/assets/baddie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/app/assets/baddie.png -------------------------------------------------------------------------------- /app/assets/diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/app/assets/diamond.png -------------------------------------------------------------------------------- /app/assets/dude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/app/assets/dude.png -------------------------------------------------------------------------------- /app/assets/firstaid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/app/assets/firstaid.png -------------------------------------------------------------------------------- /app/assets/platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/app/assets/platform.png -------------------------------------------------------------------------------- /app/assets/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/app/assets/sky.png -------------------------------------------------------------------------------- /app/assets/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/app/assets/star.png -------------------------------------------------------------------------------- /app/background.js: -------------------------------------------------------------------------------- 1 | // This is main process of Electron, started as first thing when your 2 | // app starts. This script is running through entire life of your application. 3 | // It doesn't have any windows which you can see on screen, but we can open 4 | // window from here. 5 | 6 | import { app, Menu } from 'electron'; 7 | import { devMenuTemplate } from './helpers/dev_menu_template'; 8 | import { editMenuTemplate } from './helpers/edit_menu_template'; 9 | import createWindow from './helpers/window'; 10 | 11 | // Special module holding environment variables which you declared 12 | // in config/env_xxx.json file. 13 | import env from './env'; 14 | 15 | var mainWindow; 16 | 17 | var setApplicationMenu = function () { 18 | var menus = [editMenuTemplate]; 19 | if (env.name !== 'production') { 20 | menus.push(devMenuTemplate); 21 | } 22 | Menu.setApplicationMenu(Menu.buildFromTemplate(menus)); 23 | }; 24 | 25 | app.on('ready', function () { 26 | setApplicationMenu(); 27 | 28 | var mainWindow = createWindow('main', { 29 | width: 1000, 30 | height: 600 31 | }); 32 | 33 | mainWindow.loadURL('file://' + __dirname + '/app.html'); 34 | 35 | if (env.name !== 'production') { 36 | //mainWindow.openDevTools(); 37 | } 38 | }); 39 | 40 | app.on('window-all-closed', function () { 41 | app.quit(); 42 | }); 43 | -------------------------------------------------------------------------------- /app/env.js: -------------------------------------------------------------------------------- 1 | // Simple wrapper exposing environment variables to rest of the code. 2 | 3 | import jetpack from 'fs-jetpack'; 4 | 5 | // The variables have been written to `env.json` by the build process. 6 | var env = jetpack.cwd(__dirname).read('env.json', 'json'); 7 | 8 | export default env; 9 | -------------------------------------------------------------------------------- /app/hello_world/boilerplate.spec.js: -------------------------------------------------------------------------------- 1 | // Tests here are for easier maintenance of this boilerplate. 2 | // Feel free to delete this file in your own project. 3 | 4 | import { expect } from 'chai'; 5 | import env from '../env'; 6 | 7 | describe("boilerplate tests", function () { 8 | 9 | it("environment variables should be on their place", function () { 10 | expect(env.name).to.equal('test'); 11 | expect(env.description).to.equal('Add here any environment specific stuff you like.'); 12 | }); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /app/hello_world/hello_universe.js: -------------------------------------------------------------------------------- 1 | var greet = function () { 2 | return 'Hello Universe!'; 3 | }; 4 | 5 | export default greet; 6 | -------------------------------------------------------------------------------- /app/hello_world/hello_universe.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import greet from './hello_universe'; 3 | 4 | describe("hello universe", function () { 5 | 6 | it("greets better than hello world", function () { 7 | expect(greet()).to.equal('Hello Universe!'); 8 | }); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /app/hello_world/hello_world.js: -------------------------------------------------------------------------------- 1 | export var greet = function () { 2 | return 'Hello World!'; 3 | }; 4 | 5 | export var bye = function () { 6 | return 'See ya!'; 7 | }; 8 | -------------------------------------------------------------------------------- /app/hello_world/hello_world.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { greet, bye } from './hello_world'; 3 | 4 | describe("hello world", function () { 5 | 6 | it("greets", function () { 7 | expect(greet()).to.equal('Hello World!'); 8 | }); 9 | 10 | it("says goodbye", function () { 11 | expect(bye()).to.equal('See ya!'); 12 | }); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /app/helpers/context_menu.js: -------------------------------------------------------------------------------- 1 | // This gives you default context menu (cut, copy, paste) 2 | // in all input fields and textareas across your app. 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | var remote = require('electron').remote; 8 | var Menu = remote.Menu; 9 | var MenuItem = remote.MenuItem; 10 | 11 | var isAnyTextSelected = function () { 12 | return window.getSelection().toString() !== ''; 13 | }; 14 | 15 | var cut = new MenuItem({ 16 | label: "Cut", 17 | click: function () { 18 | document.execCommand("cut"); 19 | } 20 | }); 21 | 22 | var copy = new MenuItem({ 23 | label: "Copy", 24 | click: function () { 25 | document.execCommand("copy"); 26 | } 27 | }); 28 | 29 | var paste = new MenuItem({ 30 | label: "Paste", 31 | click: function () { 32 | document.execCommand("paste"); 33 | } 34 | }); 35 | 36 | var normalMenu = new Menu(); 37 | normalMenu.append(copy); 38 | 39 | var textEditingMenu = new Menu(); 40 | textEditingMenu.append(cut); 41 | textEditingMenu.append(copy); 42 | textEditingMenu.append(paste); 43 | 44 | document.addEventListener('contextmenu', function (e) { 45 | switch (e.target.nodeName) { 46 | case 'TEXTAREA': 47 | case 'INPUT': 48 | e.preventDefault(); 49 | textEditingMenu.popup(remote.getCurrentWindow()); 50 | break; 51 | default: 52 | if (isAnyTextSelected()) { 53 | e.preventDefault(); 54 | normalMenu.popup(remote.getCurrentWindow()); 55 | } 56 | } 57 | }, false); 58 | 59 | }()); 60 | -------------------------------------------------------------------------------- /app/helpers/dev_menu_template.js: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron'; 2 | 3 | export var devMenuTemplate = { 4 | label: 'Development', 5 | submenu: [{ 6 | label: 'Reload', 7 | accelerator: 'CmdOrCtrl+R', 8 | click: function () { 9 | BrowserWindow.getFocusedWindow().webContents.reloadIgnoringCache(); 10 | } 11 | },{ 12 | label: 'Toggle DevTools', 13 | accelerator: 'Alt+CmdOrCtrl+I', 14 | click: function () { 15 | BrowserWindow.getFocusedWindow().toggleDevTools(); 16 | } 17 | },{ 18 | label: 'Quit', 19 | accelerator: 'CmdOrCtrl+Q', 20 | click: function () { 21 | app.quit(); 22 | } 23 | }] 24 | }; 25 | -------------------------------------------------------------------------------- /app/helpers/edit_menu_template.js: -------------------------------------------------------------------------------- 1 | export var editMenuTemplate = { 2 | label: 'Edit', 3 | submenu: [ 4 | { label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" }, 5 | { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" }, 6 | { type: "separator" }, 7 | { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" }, 8 | { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" }, 9 | { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" }, 10 | { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" } 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /app/helpers/external_links.js: -------------------------------------------------------------------------------- 1 | // Convenient way for opening links in external browser, not in the app. 2 | // Useful especially if you have a lot of links to deal with. 3 | // 4 | // Usage: 5 | // 6 | // Every link with class ".js-external-link" will be opened in external browser. 7 | // google 8 | // 9 | // The same behaviour for many links can be achieved by adding 10 | // this class to any parent tag of an anchor tag. 11 | // 15 | 16 | (function () { 17 | 'use strict'; 18 | 19 | var shell = require('electron').shell; 20 | 21 | var supportExternalLinks = function (e) { 22 | var href; 23 | var isExternal = false; 24 | 25 | var checkDomElement = function (element) { 26 | if (element.nodeName === 'A') { 27 | href = element.getAttribute('href'); 28 | } 29 | if (element.classList.contains('js-external-link')) { 30 | isExternal = true; 31 | } 32 | if (href && isExternal) { 33 | shell.openExternal(href); 34 | e.preventDefault(); 35 | } else if (element.parentElement) { 36 | checkDomElement(element.parentElement); 37 | } 38 | }; 39 | 40 | checkDomElement(e.target); 41 | }; 42 | 43 | document.addEventListener('click', supportExternalLinks, false); 44 | }()); 45 | -------------------------------------------------------------------------------- /app/helpers/window.js: -------------------------------------------------------------------------------- 1 | // This helper remembers the size and position of your windows (and restores 2 | // them in that place after app relaunch). 3 | // Can be used for more than one window, just construct many 4 | // instances of it and give each different name. 5 | 6 | import { app, BrowserWindow, screen } from 'electron'; 7 | import jetpack from 'fs-jetpack'; 8 | 9 | export default function (name, options) { 10 | 11 | var userDataDir = jetpack.cwd(app.getPath('userData')); 12 | var stateStoreFile = 'window-state-' + name +'.json'; 13 | var defaultSize = { 14 | width: options.width, 15 | height: options.height 16 | }; 17 | var state = {}; 18 | var win; 19 | 20 | var restore = function () { 21 | var restoredState = {}; 22 | try { 23 | restoredState = userDataDir.read(stateStoreFile, 'json'); 24 | } catch (err) { 25 | // For some reason json can't be read (might be corrupted). 26 | // No worries, we have defaults. 27 | } 28 | return Object.assign({}, defaultSize, restoredState); 29 | }; 30 | 31 | var getCurrentPosition = function () { 32 | var position = win.getPosition(); 33 | var size = win.getSize(); 34 | return { 35 | x: position[0], 36 | y: position[1], 37 | width: size[0], 38 | height: size[1] 39 | }; 40 | }; 41 | 42 | var windowWithinBounds = function (windowState, bounds) { 43 | return windowState.x >= bounds.x && 44 | windowState.y >= bounds.y && 45 | windowState.x + windowState.width <= bounds.x + bounds.width && 46 | windowState.y + windowState.height <= bounds.y + bounds.height; 47 | }; 48 | 49 | var resetToDefaults = function (windowState) { 50 | var bounds = screen.getPrimaryDisplay().bounds; 51 | return Object.assign({}, defaultSize, { 52 | x: (bounds.width - defaultSize.width) / 2, 53 | y: (bounds.height - defaultSize.height) / 2 54 | }); 55 | }; 56 | 57 | var ensureVisibleOnSomeDisplay = function (windowState) { 58 | var visible = screen.getAllDisplays().some(function (display) { 59 | return windowWithinBounds(windowState, display.bounds); 60 | }); 61 | if (!visible) { 62 | // Window is partially or fully not visible now. 63 | // Reset it to safe defaults. 64 | return resetToDefaults(windowState); 65 | } 66 | return windowState; 67 | }; 68 | 69 | var saveState = function () { 70 | if (!win.isMinimized() && !win.isMaximized()) { 71 | Object.assign(state, getCurrentPosition()); 72 | } 73 | userDataDir.write(stateStoreFile, state, { atomic: true }); 74 | }; 75 | 76 | state = ensureVisibleOnSomeDisplay(restore()); 77 | 78 | win = new BrowserWindow(Object.assign({}, options, state)); 79 | 80 | win.on('close', saveState); 81 | 82 | return win; 83 | } 84 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-phaser", 3 | "productName": "Electron Phaser Boilerplate", 4 | "description": "The first tutorial taken from the phaser website and embedded in electron", 5 | "version": "0.1.0", 6 | "author": "Fax", 7 | "copyright": "© 2016, Fabrizio Rapelli", 8 | "main": "background.js", 9 | "dependencies": { 10 | "fs-jetpack": "^0.9.0" 11 | }, 12 | "packageNameTemplate": "{{name}}-v{{version}}-{{platform}}-{{arch}}", 13 | "osx": { 14 | "build": "1", 15 | "identifier": "com.example.electron-boilerplate", 16 | "LSApplicationCategoryType": "public.app-category.productivity", 17 | "//codeSignIdentitiy": { 18 | "dmg": "Developer ID Application: Company Name (APPIDENTITY)", 19 | "MAS": "3rd Party Mac Developer Application: Company Name (APPIDENTITY)", 20 | "MASInstaller": "3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/stylesheets/main.less: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | body { 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | font-family: sans-serif; 13 | } 14 | 15 | a { 16 | text-decoration: none; 17 | } 18 | 19 | .container { 20 | text-align: center; 21 | } 22 | 23 | .subtitle { 24 | color: gray; 25 | } 26 | -------------------------------------------------------------------------------- /config/env_development.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "development", 3 | "description": "Add here any environment specific stuff you like." 4 | } 5 | -------------------------------------------------------------------------------- /config/env_production.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "production", 3 | "description": "Add here any environment specific stuff you like." 4 | } 5 | -------------------------------------------------------------------------------- /config/env_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "description": "Add here any environment specific stuff you like." 4 | } 5 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./tasks/build/build'); 4 | require('./tasks/release/release'); 5 | require('./tasks/start'); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "asar": "^0.11.0", 4 | "chai": "^3.5.0", 5 | "electron-mocha": "^2.0.0", 6 | "electron-prebuilt": "^1.0.1", 7 | "fs-jetpack": "^0.9.0", 8 | "gulp": "^3.9.1", 9 | "gulp-batch": "^1.0.5", 10 | "gulp-less": "^3.0.3", 11 | "gulp-plumber": "^1.1.0", 12 | "gulp-util": "^3.0.6", 13 | "gulp-watch": "^4.3.5", 14 | "q": "^1.4.1", 15 | "rollup": "^0.26.3", 16 | "yargs": "^4.2.0" 17 | }, 18 | "optionalDependencies": { 19 | "appdmg": "^0.3.2", 20 | "rcedit": "^0.5.0" 21 | }, 22 | "scripts": { 23 | "postinstall": "cd app && npm install", 24 | "build": "gulp build", 25 | "release": "gulp release --env=production", 26 | "start": "gulp start", 27 | "pretest": "gulp build --env=test", 28 | "test": "electron-mocha build --renderer", 29 | "install-native": "node ./tasks/install_native_module" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/resources/icon.png -------------------------------------------------------------------------------- /resources/linux/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: {{name}} 2 | Version: {{version}} 3 | Maintainer: {{author}} 4 | Priority: optional 5 | Architecture: amd64 6 | Installed-Size: {{size}} 7 | Description: {{description}} 8 | -------------------------------------------------------------------------------- /resources/linux/app.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Encoding=UTF-8 5 | Name={{productName}} 6 | Comment={{description}} 7 | Exec=/opt/{{name}}/{{name}} 8 | Path=/opt/{{name}}/ 9 | Icon=/opt/{{name}}/icon.png 10 | Terminal=false 11 | Categories=Application; 12 | -------------------------------------------------------------------------------- /resources/osx/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | {{productName}} 7 | CFBundleExecutable 8 | {{productName}} 9 | CFBundleIconFile 10 | icon.icns 11 | CFBundleIdentifier 12 | {{identifier}} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | {{productName}} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleVersion 20 | {{build}} 21 | CFBundleVersionString 22 | {{build}} 23 | CFBundleShortVersionString 24 | {{version}} 25 | CFBundleGetInfoString 26 | {{version}} 27 | LSMinimumSystemVersion 28 | 10.8.0 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | AtomApplication 33 | NSSupportsAutomaticGraphicsSwitching 34 | 35 | NSHumanReadableCopyright 36 | {{copyright}} 37 | LSApplicationCategoryType 38 | {{LSApplicationCategoryType}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /resources/osx/appdmg.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "{{productName}}", 3 | "icon": "{{dmgIcon}}", 4 | "background": "{{dmgBackground}}", 5 | "icon-size": 128, 6 | "contents": [ 7 | { "x": 410, "y": 220, "type": "link", "path": "/Applications" }, 8 | { "x": 130, "y": 220, "type": "file", "path": "{{appPath}}" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /resources/osx/child.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.inherit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/osx/dmg-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/resources/osx/dmg-background.png -------------------------------------------------------------------------------- /resources/osx/dmg-background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/resources/osx/dmg-background@2x.png -------------------------------------------------------------------------------- /resources/osx/dmg-icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/resources/osx/dmg-icon.icns -------------------------------------------------------------------------------- /resources/osx/helper_apps/Info EH.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | {{productName}} Helper EH 7 | CFBundleExecutable 8 | {{productName}} Helper EH 9 | CFBundleIdentifier 10 | {{identifier}}.helper.EH 11 | CFBundleName 12 | {{productName}} Helper EH 13 | CFBundlePackageType 14 | APPL 15 | DTSDKName 16 | macosx 17 | LSUIElement 18 | 19 | NSSupportsAutomaticGraphicsSwitching 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/osx/helper_apps/Info NP.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | {{productName}} Helper NP 7 | CFBundleExecutable 8 | {{productName}} Helper NP 9 | CFBundleIdentifier 10 | {{identifier}}.helper.NP 11 | CFBundleName 12 | {{productName}} Helper NP 13 | CFBundlePackageType 14 | APPL 15 | DTSDKName 16 | macosx 17 | LSUIElement 18 | 19 | NSSupportsAutomaticGraphicsSwitching 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/osx/helper_apps/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | {{identifier}}.helper 7 | CFBundleName 8 | {{productName}} Helper 9 | CFBundlePackageType 10 | APPL 11 | DTSDKName 12 | macosx 13 | LSUIElement 14 | 15 | NSSupportsAutomaticGraphicsSwitching 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/osx/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/resources/osx/icon.icns -------------------------------------------------------------------------------- /resources/osx/parent.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.camera 8 | 9 | com.apple.security.device.microphone 10 | 11 | com.apple.security.files.user-selected.read-only 12 | 13 | com.apple.security.files.user-selected.read-write 14 | 15 | com.apple.security.network.client 16 | 17 | com.apple.security.network.server 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /resources/windows/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/resources/windows/icon.ico -------------------------------------------------------------------------------- /resources/windows/installer.nsi: -------------------------------------------------------------------------------- 1 | ; NSIS packaging/install script 2 | ; Docs: http://nsis.sourceforge.net/Docs/Contents.html 3 | 4 | !include LogicLib.nsh 5 | !include nsDialogs.nsh 6 | 7 | ; -------------------------------- 8 | ; Variables 9 | ; -------------------------------- 10 | 11 | !define dest "{{dest}}" 12 | !define src "{{src}}" 13 | !define name "{{name}}" 14 | !define productName "{{productName}}" 15 | !define author "{{author}}" 16 | !define version "{{version}}" 17 | !define icon "{{icon}}" 18 | !define setupIcon "{{setupIcon}}" 19 | !define banner "{{banner}}" 20 | 21 | !define exec "{{productName}}.exe" 22 | 23 | !define regkey "Software\${productName}" 24 | !define uninstkey "Software\Microsoft\Windows\CurrentVersion\Uninstall\${productName}" 25 | 26 | !define uninstaller "uninstall.exe" 27 | 28 | ; -------------------------------- 29 | ; Installation 30 | ; -------------------------------- 31 | 32 | Unicode true 33 | SetCompressor /SOLID lzma 34 | 35 | Name "${productName}" 36 | Icon "${setupIcon}" 37 | OutFile "${dest}" 38 | InstallDir "$PROGRAMFILES\${productName}" 39 | InstallDirRegKey HKLM "${regkey}" "" 40 | 41 | RequestExecutionLevel admin 42 | CRCCheck on 43 | SilentInstall normal 44 | 45 | XPStyle on 46 | ShowInstDetails nevershow 47 | AutoCloseWindow false 48 | WindowIcon off 49 | 50 | Caption "${productName} Setup" 51 | ; Don't add sub-captions to title bar 52 | SubCaption 3 " " 53 | SubCaption 4 " " 54 | 55 | Page custom welcome 56 | Page instfiles 57 | 58 | Var Image 59 | Var ImageHandle 60 | 61 | Function .onInit 62 | 63 | ; Extract banner image for welcome page 64 | InitPluginsDir 65 | ReserveFile "${banner}" 66 | File /oname=$PLUGINSDIR\banner.bmp "${banner}" 67 | 68 | FunctionEnd 69 | 70 | ; Custom welcome page 71 | Function welcome 72 | 73 | nsDialogs::Create 1018 74 | 75 | ${NSD_CreateLabel} 185 1u 210 100% "Welcome to ${productName} version ${version} installer.$\r$\n$\r$\nClick install to begin." 76 | 77 | ${NSD_CreateBitmap} 0 0 170 210 "" 78 | Pop $Image 79 | ${NSD_SetImage} $Image $PLUGINSDIR\banner.bmp $ImageHandle 80 | 81 | nsDialogs::Show 82 | 83 | ${NSD_FreeImage} $ImageHandle 84 | 85 | FunctionEnd 86 | 87 | ; Installation declarations 88 | Section "Install" 89 | 90 | WriteRegStr HKLM "${regkey}" "Install_Dir" "$INSTDIR" 91 | WriteRegStr HKLM "${uninstkey}" "DisplayName" "${productName}" 92 | WriteRegStr HKLM "${uninstkey}" "DisplayIcon" '"$INSTDIR\icon.ico"' 93 | WriteRegStr HKLM "${uninstkey}" "UninstallString" '"$INSTDIR\${uninstaller}"' 94 | WriteRegStr HKLM "${uninstkey}" "Publisher" "${author}" 95 | WriteRegStr HKLM "${uninstkey}" "DisplayVersion" "${version}" 96 | 97 | ; Remove all application files copied by previous installation 98 | RMDir /r "$INSTDIR" 99 | 100 | SetOutPath $INSTDIR 101 | 102 | ; Include all files from /build directory 103 | File /r "${src}\*" 104 | 105 | ; Create start menu shortcut 106 | SetShellVarContext all 107 | CreateShortCut "$SMPROGRAMS\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico" 108 | ; Create desktop shortcut 109 | CreateShortCut "$DESKTOP\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico" 110 | 111 | WriteUninstaller "${uninstaller}" 112 | 113 | SectionEnd 114 | 115 | ; -------------------------------- 116 | ; Uninstaller 117 | ; -------------------------------- 118 | 119 | ShowUninstDetails nevershow 120 | 121 | UninstallCaption "Uninstall ${productName}" 122 | UninstallText "Don't like ${productName} anymore? Hit uninstall button." 123 | UninstallIcon "${icon}" 124 | 125 | UninstPage custom un.confirm un.confirmOnLeave 126 | UninstPage instfiles 127 | 128 | Var RemoveAppDataCheckbox 129 | Var RemoveAppDataCheckbox_State 130 | 131 | ; Custom uninstall confirm page 132 | Function un.confirm 133 | 134 | nsDialogs::Create 1018 135 | 136 | ${NSD_CreateLabel} 1u 1u 100% 24u "If you really want to remove ${productName} from your computer press uninstall button." 137 | 138 | ${NSD_CreateCheckbox} 1u 35u 100% 10u "Remove also my ${productName} personal data" 139 | Pop $RemoveAppDataCheckbox 140 | 141 | nsDialogs::Show 142 | 143 | FunctionEnd 144 | 145 | Function un.confirmOnLeave 146 | 147 | ; Save checkbox state on page leave 148 | ${NSD_GetState} $RemoveAppDataCheckbox $RemoveAppDataCheckbox_State 149 | 150 | FunctionEnd 151 | 152 | ; Uninstall declarations 153 | Section "Uninstall" 154 | 155 | DeleteRegKey HKLM "${uninstkey}" 156 | DeleteRegKey HKLM "${regkey}" 157 | 158 | SetShellVarContext all 159 | Delete "$SMPROGRAMS\${productName}.lnk" 160 | ; Remove desktop shortcut 161 | Delete "$DESKTOP\${productName}.lnk" 162 | ; Remove whole directory from Program Files 163 | RMDir /r "$INSTDIR" 164 | 165 | ; Remove also appData directory generated by your app if user checked this option 166 | ${If} $RemoveAppDataCheckbox_State == ${BST_CHECKED} 167 | RMDir /r "$APPDATA\${productName}" 168 | ${EndIf} 169 | 170 | SectionEnd 171 | -------------------------------------------------------------------------------- /resources/windows/setup-banner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/resources/windows/setup-banner.bmp -------------------------------------------------------------------------------- /resources/windows/setup-icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fax/electron-phaser/8b1fa0dd1c8bdc72c1b30f787dcf46be2e2143d6/resources/windows/setup-icon.ico -------------------------------------------------------------------------------- /tasks/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pathUtil = require('path'); 4 | var Q = require('q'); 5 | var gulp = require('gulp'); 6 | var less = require('gulp-less'); 7 | var watch = require('gulp-watch'); 8 | var batch = require('gulp-batch'); 9 | var plumber = require('gulp-plumber'); 10 | var jetpack = require('fs-jetpack'); 11 | 12 | var bundle = require('./bundle'); 13 | var generateSpecImportsFile = require('./generate_spec_imports'); 14 | var utils = require('../utils'); 15 | 16 | var projectDir = jetpack; 17 | var srcDir = projectDir.cwd('./app'); 18 | var destDir = projectDir.cwd('./build'); 19 | 20 | var paths = { 21 | copyFromAppDir: [ 22 | './node_modules/**', 23 | './libs/**', 24 | './assets/**', 25 | './helpers/**', 26 | './**/*.html', 27 | './**/*.+(jpg|png|svg)' 28 | ], 29 | }; 30 | 31 | // ------------------------------------- 32 | // Tasks 33 | // ------------------------------------- 34 | 35 | gulp.task('clean', function () { 36 | return destDir.dirAsync('.', { empty: true }); 37 | }); 38 | 39 | 40 | var copyTask = function () { 41 | return projectDir.copyAsync('app', destDir.path(), { 42 | overwrite: true, 43 | matching: paths.copyFromAppDir 44 | }); 45 | }; 46 | gulp.task('copy', ['clean'], copyTask); 47 | gulp.task('copy-watch', copyTask); 48 | 49 | 50 | var bundleApplication = function () { 51 | return Q.all([ 52 | bundle(srcDir.path('background.js'), destDir.path('background.js')), 53 | bundle(srcDir.path('app.js'), destDir.path('app.js')), 54 | ]); 55 | }; 56 | 57 | var bundleSpecs = function () { 58 | return generateSpecImportsFile().then(function (specEntryPointPath) { 59 | return bundle(specEntryPointPath, destDir.path('spec.js')); 60 | }); 61 | }; 62 | 63 | var bundleTask = function () { 64 | if (utils.getEnvName() === 'test') { 65 | return bundleSpecs(); 66 | } 67 | return bundleApplication(); 68 | }; 69 | gulp.task('bundle', ['clean'], bundleTask); 70 | gulp.task('bundle-watch', bundleTask); 71 | 72 | 73 | var lessTask = function () { 74 | return gulp.src('app/stylesheets/main.less') 75 | .pipe(plumber()) 76 | .pipe(less()) 77 | .pipe(gulp.dest(destDir.path('stylesheets'))); 78 | }; 79 | gulp.task('less', ['clean'], lessTask); 80 | gulp.task('less-watch', lessTask); 81 | 82 | 83 | gulp.task('environment', ['clean'], function () { 84 | var configFile = 'config/env_' + utils.getEnvName() + '.json'; 85 | projectDir.copy(configFile, destDir.path('env.json')); 86 | }); 87 | 88 | 89 | gulp.task('package-json', ['clean'], function () { 90 | var manifest = srcDir.read('package.json', 'json'); 91 | 92 | // Add "dev" suffix to name, so Electron will write all data like cookies 93 | // and localStorage in separate places for production and development. 94 | if (utils.getEnvName() === 'development') { 95 | manifest.name += '-dev'; 96 | manifest.productName += ' Dev'; 97 | } 98 | 99 | destDir.write('package.json', manifest); 100 | }); 101 | 102 | 103 | gulp.task('watch', function () { 104 | watch('app/**/*.js', batch(function (events, done) { 105 | gulp.start('bundle-watch', done); 106 | })); 107 | watch(paths.copyFromAppDir, { cwd: 'app' }, batch(function (events, done) { 108 | gulp.start('copy-watch', done); 109 | })); 110 | watch('app/**/*.less', batch(function (events, done) { 111 | gulp.start('less-watch', done); 112 | })); 113 | }); 114 | 115 | 116 | gulp.task('build', ['bundle', 'less', 'copy', 'environment', 'package-json']); 117 | -------------------------------------------------------------------------------- /tasks/build/bundle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pathUtil = require('path'); 4 | var jetpack = require('fs-jetpack'); 5 | var rollup = require('rollup'); 6 | var Q = require('q'); 7 | 8 | var nodeBuiltInModules = ['assert', 'buffer', 'child_process', 'cluster', 9 | 'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events', 10 | 'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'process', 'punycode', 11 | 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers', 12 | 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib']; 13 | 14 | var electronBuiltInModules = ['electron']; 15 | 16 | var npmModulesUsedInApp = function () { 17 | var appManifest = require('../../app/package.json'); 18 | return Object.keys(appManifest.dependencies); 19 | }; 20 | 21 | var generateExternalModulesList = function () { 22 | return [].concat(nodeBuiltInModules, electronBuiltInModules, npmModulesUsedInApp()); 23 | }; 24 | 25 | module.exports = function (src, dest) { 26 | var deferred = Q.defer(); 27 | 28 | rollup.rollup({ 29 | entry: src, 30 | external: generateExternalModulesList(), 31 | }).then(function (bundle) { 32 | var jsFile = pathUtil.basename(dest); 33 | var result = bundle.generate({ 34 | format: 'cjs', 35 | sourceMap: true, 36 | sourceMapFile: jsFile, 37 | }); 38 | // Wrap code in self invoking function so the variables don't 39 | // pollute the global namespace. 40 | var isolatedCode = '(function () {' + result.code + '\n}());'; 41 | return Q.all([ 42 | jetpack.writeAsync(dest, isolatedCode + '\n//# sourceMappingURL=' + jsFile + '.map'), 43 | jetpack.writeAsync(dest + '.map', result.map.toString()), 44 | ]); 45 | }).then(function () { 46 | deferred.resolve(); 47 | }).catch(function (err) { 48 | deferred.reject(err); 49 | }); 50 | 51 | return deferred.promise; 52 | }; 53 | -------------------------------------------------------------------------------- /tasks/build/generate_spec_imports.js: -------------------------------------------------------------------------------- 1 | // Spec files are scattered through the whole project. Here we're searching 2 | // for them and generate one entry file which will run all the tests. 3 | 4 | 'use strict'; 5 | 6 | var jetpack = require('fs-jetpack'); 7 | var srcDir = jetpack.cwd('app'); 8 | 9 | var fileName = 'spec.js.autogenerated'; 10 | var fileBanner = "// This file is generated automatically.\n" 11 | + "// All your modifications to it will be lost (so don't do it).\n"; 12 | var whatToInclude = [ 13 | '*.spec.js', 14 | '!node_modules/**', 15 | ]; 16 | 17 | module.exports = function () { 18 | return srcDir.findAsync('.', { matching: whatToInclude }) 19 | .then(function (specPaths) { 20 | var fileContent = specPaths.map(function (path) { 21 | return 'import "./' + path.replace(/\\/g, '/') + '";'; 22 | }).join('\n'); 23 | return srcDir.writeAsync(fileName, fileBanner + fileContent); 24 | }) 25 | .then(function () { 26 | return srcDir.path(fileName); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /tasks/install_native_module.js: -------------------------------------------------------------------------------- 1 | // Install native module from npm and compile it for Electron. 2 | // Usage: npm run install-native -- name_of_native_module 3 | 4 | 'use strict'; 5 | 6 | var childProcess = require('child_process'); 7 | var Q = require('q'); 8 | var appDir = require('fs-jetpack').cwd(__dirname, '../app'); 9 | var utils = require('./utils'); 10 | 11 | var ensureElectronRebuildInstalled = function () { 12 | var deferred = Q.defer(); 13 | 14 | try { 15 | // If require is successful it means module is already installed. 16 | require('electron-rebuild'); 17 | deferred.resolve(); 18 | } catch (err) { 19 | childProcess.spawn(utils.spawnablePath('npm'), [ 20 | 'install', '--save-dev', 'electron-rebuild' 21 | ], { 22 | stdio: 'inherit' 23 | }) 24 | .on('error', deferred.reject) 25 | .on('close', deferred.resolve); 26 | } 27 | 28 | return deferred.promise; 29 | }; 30 | 31 | var ensurePostinstallRunsElectronRebuild = function () { 32 | var postinstallScript = 'node ../tasks/rebuild_native_modules'; 33 | 34 | var appManifest = appDir.read('package.json', 'json'); 35 | 36 | if (typeof appManifest.scripts === 'undefined') { 37 | appManifest.scripts = {}; 38 | } 39 | 40 | // Let's do it 100% bulletproof and check if programmer didn't 41 | // pust some custom stuff into postinstall script already. 42 | if (typeof appManifest.scripts.postinstall === 'undefined') { 43 | appManifest.scripts.postinstall = postinstallScript; 44 | appDir.write('package.json', appManifest); 45 | } else if (appManifest.scripts.postinstall.indexOf(postinstallScript) === -1) { 46 | appManifest.scripts.postinstall += ' && ' + postinstallScript; 47 | appDir.write('package.json', appManifest); 48 | } 49 | 50 | return Q(); 51 | }; 52 | 53 | var installNativeModule = function () { 54 | var deferred = Q.defer(); 55 | var moduleToInstallAndOtherParams = process.argv.slice(2); 56 | 57 | if (!moduleToInstallAndOtherParams.length === 0) { 58 | deferred.reject('Module name not specified! Correct usage is "npm run install-native -- name_of_native_module" (remember about space after "--").'); 59 | } else { 60 | childProcess.spawn( 61 | utils.spawnablePath('npm'), 62 | ['install', '--save'].concat(moduleToInstallAndOtherParams), 63 | { 64 | cwd: appDir.cwd(), 65 | stdio: 'inherit' 66 | } 67 | ) 68 | .on('error', deferred.reject) 69 | .on('close', deferred.resolve); 70 | } 71 | 72 | return deferred.promise; 73 | }; 74 | 75 | var runRebuild = function () { 76 | var deferred = Q.defer(); 77 | 78 | childProcess.spawn(utils.spawnablePath('npm'), [ 79 | 'run', 'postinstall' 80 | ], { 81 | cwd: appDir.cwd(), 82 | stdio: 'inherit' 83 | }) 84 | .on('error', deferred.reject) 85 | .on('close', deferred.resolve); 86 | 87 | return deferred.promise; 88 | }; 89 | 90 | ensureElectronRebuildInstalled() 91 | .then(ensurePostinstallRunsElectronRebuild) 92 | .then(installNativeModule) 93 | .then(runRebuild) 94 | .catch(function (err) { 95 | console.error(err); 96 | }); 97 | -------------------------------------------------------------------------------- /tasks/rebuild_native_modules.js: -------------------------------------------------------------------------------- 1 | // Rebuilds native node modules for Electron. 2 | // More: https://github.com/atom/electron/blob/master/docs/tutorial/using-native-node-modules.md 3 | 4 | 'use strict'; 5 | 6 | var path = require('path'); 7 | var Q = require('q'); 8 | var electron = require('electron-prebuilt'); 9 | var electronPackage = require('electron-prebuilt/package.json'); 10 | var rebuild = require('electron-rebuild'); 11 | 12 | var pathToElectronNativeModules = path.join(__dirname, '../app/node_modules'); 13 | 14 | rebuild.shouldRebuildNativeModules(electron) 15 | .then(function (shouldBuild) { 16 | if (!shouldBuild) { 17 | return true; 18 | } 19 | 20 | console.log('Rebuilding native modules for Electron...'); 21 | 22 | return rebuild.installNodeHeaders(electronPackage.version) 23 | .then(function () { 24 | return rebuild.rebuildNativeModules(electronPackage.version, pathToElectronNativeModules); 25 | }); 26 | }) 27 | .then(function () { 28 | console.log('Rebuilding complete.'); 29 | }) 30 | .catch(function (err) { 31 | console.error("Rebuilding error!"); 32 | console.error(err); 33 | }); 34 | -------------------------------------------------------------------------------- /tasks/release/linux.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'); 4 | var gulpUtil = require('gulp-util'); 5 | var childProcess = require('child_process'); 6 | var jetpack = require('fs-jetpack'); 7 | var asar = require('asar'); 8 | var utils = require('../utils'); 9 | 10 | var projectDir; 11 | var releasesDir; 12 | var packName; 13 | var packDir; 14 | var tmpDir; 15 | var readyAppDir; 16 | var manifest; 17 | 18 | var init = function () { 19 | projectDir = jetpack; 20 | tmpDir = projectDir.dir('./tmp', { empty: true }); 21 | releasesDir = projectDir.dir('./releases'); 22 | manifest = projectDir.read('app/package.json', 'json'); 23 | packName = utils.getReleasePackageName(manifest); 24 | packDir = tmpDir.dir(packName); 25 | readyAppDir = packDir.cwd('opt', manifest.name); 26 | 27 | return new Q(); 28 | }; 29 | 30 | var copyRuntime = function () { 31 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist', readyAppDir.path(), { overwrite: true }); 32 | }; 33 | 34 | var packageBuiltApp = function () { 35 | var deferred = Q.defer(); 36 | 37 | asar.createPackageWithOptions(projectDir.path('build'), readyAppDir.path('resources/app.asar'), { 38 | dot: true 39 | }, function () { 40 | deferred.resolve(); 41 | }); 42 | 43 | return deferred.promise; 44 | }; 45 | 46 | var finalize = function () { 47 | // Create .desktop file from the template 48 | var desktop = projectDir.read('resources/linux/app.desktop'); 49 | desktop = utils.replace(desktop, { 50 | name: manifest.name, 51 | productName: manifest.productName, 52 | description: manifest.description, 53 | version: manifest.version, 54 | author: manifest.author 55 | }); 56 | packDir.write('usr/share/applications/' + manifest.name + '.desktop', desktop); 57 | 58 | // Copy icon 59 | projectDir.copy('resources/icon.png', readyAppDir.path('icon.png')); 60 | 61 | return new Q(); 62 | }; 63 | 64 | var renameApp = function () { 65 | return readyAppDir.renameAsync('electron', manifest.name); 66 | }; 67 | 68 | var packToDebFile = function () { 69 | var deferred = Q.defer(); 70 | 71 | var debFileName = packName + '.deb'; 72 | var debPath = releasesDir.path(debFileName); 73 | 74 | gulpUtil.log('Creating DEB package... (' + debFileName + ')'); 75 | 76 | // Counting size of the app in KiB 77 | var appSize = Math.round(readyAppDir.inspectTree('.').size / 1024); 78 | 79 | // Preparing debian control file 80 | var control = projectDir.read('resources/linux/DEBIAN/control'); 81 | control = utils.replace(control, { 82 | name: manifest.name, 83 | description: manifest.description, 84 | version: manifest.version, 85 | author: manifest.author, 86 | size: appSize 87 | }); 88 | packDir.write('DEBIAN/control', control); 89 | 90 | // Build the package... 91 | childProcess.exec('fakeroot dpkg-deb -Zxz --build ' + packDir.path().replace(/\s/g, '\\ ') + ' ' + debPath.replace(/\s/g, '\\ '), 92 | function (error, stdout, stderr) { 93 | if (error || stderr) { 94 | console.log('ERROR while building DEB package:'); 95 | console.log(error); 96 | console.log(stderr); 97 | } else { 98 | gulpUtil.log('DEB package ready!', debPath); 99 | } 100 | deferred.resolve(); 101 | }); 102 | 103 | return deferred.promise; 104 | }; 105 | 106 | var cleanClutter = function () { 107 | return tmpDir.removeAsync('.'); 108 | }; 109 | 110 | module.exports = function () { 111 | return init() 112 | .then(copyRuntime) 113 | .then(packageBuiltApp) 114 | .then(finalize) 115 | .then(renameApp) 116 | .then(packToDebFile) 117 | .then(cleanClutter) 118 | .catch(console.error); 119 | }; 120 | -------------------------------------------------------------------------------- /tasks/release/osx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'); 4 | var gulpUtil = require('gulp-util'); 5 | var jetpack = require('fs-jetpack'); 6 | var asar = require('asar'); 7 | var utils = require('../utils'); 8 | var child_process = require('child_process'); 9 | 10 | var projectDir; 11 | var releasesDir; 12 | var tmpDir; 13 | var finalAppDir; 14 | var manifest; 15 | 16 | var init = function () { 17 | projectDir = jetpack; 18 | tmpDir = projectDir.dir('./tmp', { empty: true }); 19 | releasesDir = projectDir.dir('./releases'); 20 | manifest = projectDir.read('app/package.json', 'json'); 21 | finalAppDir = tmpDir.cwd(manifest.productName + '.app'); 22 | 23 | return new Q(); 24 | }; 25 | 26 | var copyRuntime = function () { 27 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist/Electron.app', finalAppDir.path()); 28 | }; 29 | 30 | var cleanupRuntime = function () { 31 | finalAppDir.remove('Contents/Resources/default_app'); 32 | finalAppDir.remove('Contents/Resources/atom.icns'); 33 | return new Q(); 34 | }; 35 | 36 | var packageBuiltApp = function () { 37 | var deferred = Q.defer(); 38 | 39 | asar.createPackageWithOptions(projectDir.path('build'), finalAppDir.path('Contents/Resources/app.asar'), { 40 | dot: true 41 | }, function () { 42 | deferred.resolve(); 43 | }); 44 | 45 | return deferred.promise; 46 | }; 47 | 48 | var finalize = function () { 49 | // Prepare main Info.plist 50 | var info = projectDir.read('resources/osx/Info.plist'); 51 | info = utils.replace(info, { 52 | productName: manifest.productName, 53 | identifier: manifest.osx.identifier, 54 | version: manifest.version, 55 | build: manifest.osx.build, 56 | copyright: manifest.copyright, 57 | LSApplicationCategoryType: manifest.osx.LSApplicationCategoryType 58 | }); 59 | finalAppDir.write('Contents/Info.plist', info); 60 | 61 | // Prepare Info.plist of Helper apps 62 | [' EH', ' NP', ''].forEach(function (helper_suffix) { 63 | info = projectDir.read('resources/osx/helper_apps/Info' + helper_suffix + '.plist'); 64 | info = utils.replace(info, { 65 | productName: manifest.productName, 66 | identifier: manifest.identifier 67 | }); 68 | finalAppDir.write('Contents/Frameworks/Electron Helper' + helper_suffix + '.app/Contents/Info.plist', info); 69 | }); 70 | 71 | // Copy icon 72 | projectDir.copy('resources/osx/icon.icns', finalAppDir.path('Contents/Resources/icon.icns')); 73 | 74 | return new Q(); 75 | }; 76 | 77 | var renameApp = function () { 78 | // Rename helpers 79 | [' Helper EH', ' Helper NP', ' Helper'].forEach(function (helper_suffix) { 80 | finalAppDir.rename('Contents/Frameworks/Electron' + helper_suffix + '.app/Contents/MacOS/Electron' + helper_suffix, manifest.productName + helper_suffix ); 81 | finalAppDir.rename('Contents/Frameworks/Electron' + helper_suffix + '.app', manifest.productName + helper_suffix + '.app'); 82 | }); 83 | // Rename application 84 | finalAppDir.rename('Contents/MacOS/Electron', manifest.productName); 85 | return new Q(); 86 | }; 87 | 88 | var signApp = function () { 89 | var identity = utils.getSigningId(manifest); 90 | var MASIdentity = utils.getMASSigningId(manifest); 91 | var MASInstallerIdentity = utils.getMASInstallerSigningId(manifest); 92 | 93 | if (utils.releaseForMAS()) { 94 | if (!MASIdentity || !MASInstallerIdentity) { 95 | gulpUtil.log('--mas-sign and --mas-installer-sign are required to release for Mac App Store!'); 96 | process.exit(0); 97 | } 98 | var cmds = [ 99 | 'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib"', 100 | 'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libnode.dylib"', 101 | 'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/Electron Framework.framework/Versions/A"', 102 | 'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/' + manifest.productName + ' Helper.app/"', 103 | 'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/' + manifest.productName + ' Helper EH.app/"', 104 | 'codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist -v "' + finalAppDir.path() + '/Contents/Frameworks/' + manifest.productName + ' Helper NP.app/"' 105 | ]; 106 | 107 | if (finalAppDir.exists('Contents/Frameworks/Squirrel.framework/Versions/A')) { 108 | // # Signing a non-MAS build. 109 | cmds.push('codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist "' + finalAppDir.path() + '/Contents/Frameworks/Mantle.framework/Versions/A"'); 110 | cmds.push('codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist "' + finalAppDir.path() + '/Contents/Frameworks/ReactiveCocoa.framework/Versions/A"'); 111 | cmds.push('codesign --deep -f -s "' + MASIdentity + '" --entitlements resources/osx/child.plist "' + finalAppDir.path() + '/Contents/Frameworks/Squirrel.framework/Versions/A"'); 112 | } 113 | 114 | cmds.push('codesign -f -s "' + MASIdentity + '" --entitlements resources/osx/parent.plist -v "' + finalAppDir.path() + '"'); 115 | 116 | cmds.push('productbuild --component "' + finalAppDir.path() + '" /Applications --sign "' + MASInstallerIdentity + '" "' + releasesDir.path(manifest.productName + '.pkg') + '"'); 117 | 118 | var result = new Q(); 119 | cmds.forEach(function (cmd) { 120 | result = result.then(function(result) { 121 | gulpUtil.log('Signing with:', cmd); 122 | return Q.nfcall(child_process.exec, cmd); 123 | }); 124 | }); 125 | result = result.then(function(result) { 126 | return new Q(); 127 | }); 128 | return result; 129 | 130 | } else if (identity) { 131 | var cmd = 'codesign --deep --force --sign "' + identity + '" "' + finalAppDir.path() + '"'; 132 | gulpUtil.log('Signing with:', cmd); 133 | return Q.nfcall(child_process.exec, cmd); 134 | } else { 135 | return new Q(); 136 | } 137 | }; 138 | 139 | var packToDmgFile = function () { 140 | if (utils.releaseForMAS()) { 141 | return new Q(); 142 | } 143 | 144 | var deferred = Q.defer(); 145 | 146 | var appdmg = require('appdmg'); 147 | var dmgName = utils.getReleasePackageName(manifest) + '.dmg'; 148 | 149 | // Prepare appdmg config 150 | var dmgManifest = projectDir.read('resources/osx/appdmg.json'); 151 | dmgManifest = utils.replace(dmgManifest, { 152 | productName: manifest.productName, 153 | appPath: finalAppDir.path(), 154 | dmgIcon: projectDir.path("resources/osx/dmg-icon.icns"), 155 | dmgBackground: projectDir.path("resources/osx/dmg-background.png") 156 | }); 157 | tmpDir.write('appdmg.json', dmgManifest); 158 | 159 | // Delete DMG file with this name if already exists 160 | releasesDir.remove(dmgName); 161 | 162 | gulpUtil.log('Packaging to DMG file... (' + dmgName + ')'); 163 | 164 | var readyDmgPath = releasesDir.path(dmgName); 165 | appdmg({ 166 | source: tmpDir.path('appdmg.json'), 167 | target: readyDmgPath 168 | }) 169 | .on('error', function (err) { 170 | console.error(err); 171 | }) 172 | .on('finish', function () { 173 | gulpUtil.log('DMG file ready!', readyDmgPath); 174 | deferred.resolve(); 175 | }); 176 | 177 | return deferred.promise; 178 | }; 179 | 180 | var cleanClutter = function () { 181 | return tmpDir.removeAsync('.'); 182 | }; 183 | 184 | module.exports = function () { 185 | return init() 186 | .then(copyRuntime) 187 | .then(cleanupRuntime) 188 | .then(packageBuiltApp) 189 | .then(finalize) 190 | .then(renameApp) 191 | .then(signApp) 192 | .then(packToDmgFile) 193 | .then(cleanClutter) 194 | .catch(console.error); 195 | }; 196 | -------------------------------------------------------------------------------- /tasks/release/release.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var utils = require('../utils'); 5 | 6 | var releaseForOs = { 7 | osx: require('./osx'), 8 | linux: require('./linux'), 9 | windows: require('./windows'), 10 | }; 11 | 12 | gulp.task('release', ['build'], function () { 13 | return releaseForOs[utils.os()](); 14 | }); 15 | -------------------------------------------------------------------------------- /tasks/release/windows.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Q = require('q'); 4 | var gulpUtil = require('gulp-util'); 5 | var childProcess = require('child_process'); 6 | var jetpack = require('fs-jetpack'); 7 | var asar = require('asar'); 8 | var utils = require('../utils'); 9 | 10 | var projectDir; 11 | var tmpDir; 12 | var releasesDir; 13 | var readyAppDir; 14 | var manifest; 15 | 16 | var init = function () { 17 | projectDir = jetpack; 18 | tmpDir = projectDir.dir('./tmp', { empty: true }); 19 | releasesDir = projectDir.dir('./releases'); 20 | manifest = projectDir.read('app/package.json', 'json'); 21 | readyAppDir = tmpDir.cwd(manifest.name); 22 | 23 | return new Q(); 24 | }; 25 | 26 | var copyRuntime = function () { 27 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist', readyAppDir.path(), { overwrite: true }); 28 | }; 29 | 30 | var cleanupRuntime = function () { 31 | return readyAppDir.removeAsync('resources/default_app'); 32 | }; 33 | 34 | var packageBuiltApp = function () { 35 | var deferred = Q.defer(); 36 | 37 | asar.createPackageWithOptions(projectDir.path('build'), readyAppDir.path('resources/app.asar'), { 38 | dot: true 39 | }, function () { 40 | deferred.resolve(); 41 | }); 42 | 43 | return deferred.promise; 44 | }; 45 | 46 | var finalize = function () { 47 | var deferred = Q.defer(); 48 | 49 | projectDir.copy('resources/windows/icon.ico', readyAppDir.path('icon.ico')); 50 | 51 | // Replace Electron icon for your own. 52 | var rcedit = require('rcedit'); 53 | rcedit(readyAppDir.path('electron.exe'), { 54 | 'icon': projectDir.path('resources/windows/icon.ico'), 55 | 'version-string': { 56 | 'ProductName': manifest.productName, 57 | 'FileDescription': manifest.description, 58 | 'ProductVersion': manifest.version, 59 | 'CompanyName': manifest.author, // it might be better to add another field to package.json for this 60 | 'LegalCopyright': manifest.copyright, 61 | 'OriginalFilename': manifest.productName + '.exe' 62 | } 63 | }, function (err) { 64 | if (!err) { 65 | deferred.resolve(); 66 | } 67 | }); 68 | 69 | return deferred.promise; 70 | }; 71 | 72 | var renameApp = function () { 73 | return readyAppDir.renameAsync('electron.exe', manifest.productName + '.exe'); 74 | }; 75 | 76 | var createInstaller = function () { 77 | var deferred = Q.defer(); 78 | 79 | var finalPackageName = utils.getReleasePackageName(manifest) + '.exe'; 80 | var installScript = projectDir.read('resources/windows/installer.nsi'); 81 | 82 | installScript = utils.replace(installScript, { 83 | name: manifest.name, 84 | productName: manifest.productName, 85 | author: manifest.author, 86 | version: manifest.version, 87 | src: readyAppDir.path(), 88 | dest: releasesDir.path(finalPackageName), 89 | icon: readyAppDir.path('icon.ico'), 90 | setupIcon: projectDir.path('resources/windows/setup-icon.ico'), 91 | banner: projectDir.path('resources/windows/setup-banner.bmp'), 92 | }); 93 | tmpDir.write('installer.nsi', installScript); 94 | 95 | gulpUtil.log('Building installer with NSIS... (' + finalPackageName + ')'); 96 | 97 | // Remove destination file if already exists. 98 | releasesDir.remove(finalPackageName); 99 | 100 | // Note: NSIS have to be added to PATH (environment variables). 101 | var nsis = childProcess.spawn('makensis', [ 102 | tmpDir.path('installer.nsi') 103 | ], { 104 | stdio: 'inherit' 105 | }); 106 | nsis.on('error', function (err) { 107 | if (err.message === 'spawn makensis ENOENT') { 108 | throw "Can't find NSIS. Are you sure you've installed it and" 109 | + " added to PATH environment variable?"; 110 | } else { 111 | throw err; 112 | } 113 | }); 114 | nsis.on('close', function () { 115 | gulpUtil.log('Installer ready!', releasesDir.path(finalPackageName)); 116 | deferred.resolve(); 117 | }); 118 | 119 | return deferred.promise; 120 | }; 121 | 122 | var cleanClutter = function () { 123 | return tmpDir.removeAsync('.'); 124 | }; 125 | 126 | module.exports = function () { 127 | return init() 128 | .then(copyRuntime) 129 | .then(cleanupRuntime) 130 | .then(packageBuiltApp) 131 | .then(finalize) 132 | .then(renameApp) 133 | .then(createInstaller) 134 | .then(cleanClutter) 135 | .catch(console.error); 136 | }; 137 | -------------------------------------------------------------------------------- /tasks/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var childProcess = require('child_process'); 4 | var electron = require('electron-prebuilt'); 5 | var gulp = require('gulp'); 6 | 7 | gulp.task('start', ['build', 'watch'], function () { 8 | childProcess.spawn(electron, ['./build'], { 9 | stdio: 'inherit' 10 | }) 11 | .on('close', function () { 12 | // User closed the app. Kill the host process. 13 | process.exit(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tasks/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var argv = require('yargs').argv; 4 | var os = require('os'); 5 | 6 | module.exports.os = function () { 7 | switch (os.platform()) { 8 | case 'darwin': 9 | return 'osx'; 10 | case 'linux': 11 | return 'linux'; 12 | case 'win32': 13 | return 'windows'; 14 | } 15 | return 'unsupported'; 16 | }; 17 | 18 | module.exports.replace = function (str, patterns) { 19 | Object.keys(patterns).forEach(function (pattern) { 20 | var matcher = new RegExp('{{' + pattern + '}}', 'g'); 21 | str = str.replace(matcher, patterns[pattern]); 22 | }); 23 | return str; 24 | }; 25 | 26 | module.exports.getReleasePackageName = function(manifest) { 27 | return module.exports.replace(manifest.packageNameTemplate, { 28 | name: manifest.name, 29 | version: manifest.version, 30 | build: manifest.build, 31 | productName: manifest.productName, 32 | platform: process.platform, 33 | arch: process.arch 34 | }); 35 | }; 36 | 37 | module.exports.getEnvName = function () { 38 | return argv.env || 'development'; 39 | }; 40 | 41 | module.exports.getSigningId = function (manifest) { 42 | return argv.sign || (manifest.osx.codeSignIdentitiy ? manifest.osx.codeSignIdentitiy.dmg : undefined); 43 | }; 44 | 45 | module.exports.getMASSigningId = function (manifest) { 46 | return argv['mas-sign'] || (manifest.osx.codeSignIdentitiy ? manifest.osx.codeSignIdentitiy.MAS : undefined); 47 | }; 48 | 49 | module.exports.getMASInstallerSigningId = function (manifest) { 50 | return argv['mas-installer-sign'] || (manifest.osx.codeSignIdentitiy ? manifest.osx.codeSignIdentitiy.MASInstaller : undefined); 51 | }; 52 | 53 | module.exports.releaseForMAS = function () { 54 | return !!argv.mas; 55 | }; 56 | 57 | // Fixes https://github.com/nodejs/node-v0.x-archive/issues/2318 58 | module.exports.spawnablePath = function (path) { 59 | if (process.platform === 'win32') { 60 | return path + '.cmd'; 61 | } 62 | return path; 63 | }; 64 | --------------------------------------------------------------------------------